diff options
Diffstat (limited to 'drivers')
302 files changed, 30299 insertions, 5941 deletions
diff --git a/drivers/acpi/Kconfig b/drivers/acpi/Kconfig index 4bf68c8d479..100bd724f64 100644 --- a/drivers/acpi/Kconfig +++ b/drivers/acpi/Kconfig @@ -298,14 +298,6 @@ config ACPI_DEBUG Documentation/kernel-parameters.txt to control the type and amount of debug output. -config ACPI_DEBUG_FUNC_TRACE - bool "Additionally enable ACPI function tracing" - default n - depends on ACPI_DEBUG - help - ACPI Debug Statements slow down ACPI processing. Function trace - is about half of the penalty and is rarely useful. - config ACPI_PCI_SLOT bool "PCI slot detection driver" depends on SYSFS @@ -334,7 +326,7 @@ config X86_PM_TIMER config ACPI_CONTAINER bool "Container and Module Devices" - default (ACPI_HOTPLUG_MEMORY || ACPI_HOTPLUG_CPU || ACPI_HOTPLUG_IO) + default (ACPI_HOTPLUG_MEMORY || ACPI_HOTPLUG_CPU) help This driver supports ACPI Container and Module devices (IDs ACPI0004, PNP0A05, and PNP0A06). @@ -345,9 +337,8 @@ config ACPI_CONTAINER the module will be called container. config ACPI_HOTPLUG_MEMORY - tristate "Memory Hotplug" + bool "Memory Hotplug" depends on MEMORY_HOTPLUG - default n help This driver supports ACPI memory hotplug. The driver fields notifications on ACPI memory devices (PNP0C80), diff --git a/drivers/acpi/Makefile b/drivers/acpi/Makefile index 474fcfeba66..ecb743bf05a 100644 --- a/drivers/acpi/Makefile +++ b/drivers/acpi/Makefile @@ -39,6 +39,7 @@ acpi-y += ec.o acpi-$(CONFIG_ACPI_DOCK) += dock.o acpi-y += pci_root.o pci_link.o pci_irq.o acpi-y += csrt.o +acpi-$(CONFIG_X86_INTEL_LPSS) += acpi_lpss.o acpi-y += acpi_platform.o acpi-y += power.o acpi-y += event.o diff --git a/drivers/acpi/acpi_lpss.c b/drivers/acpi/acpi_lpss.c new file mode 100644 index 00000000000..b1c95422ce7 --- /dev/null +++ b/drivers/acpi/acpi_lpss.c @@ -0,0 +1,292 @@ +/* + * ACPI support for Intel Lynxpoint LPSS. + * + * Copyright (C) 2013, Intel Corporation + * Authors: Mika Westerberg <mika.westerberg@linux.intel.com> + * Rafael J. Wysocki <rafael.j.wysocki@intel.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/acpi.h> +#include <linux/clk.h> +#include <linux/clkdev.h> +#include <linux/clk-provider.h> +#include <linux/err.h> +#include <linux/io.h> +#include <linux/platform_device.h> +#include <linux/platform_data/clk-lpss.h> +#include <linux/pm_runtime.h> + +#include "internal.h" + +ACPI_MODULE_NAME("acpi_lpss"); + +#define LPSS_CLK_SIZE 0x04 +#define LPSS_LTR_SIZE 0x18 + +/* Offsets relative to LPSS_PRIVATE_OFFSET */ +#define LPSS_GENERAL 0x08 +#define LPSS_GENERAL_LTR_MODE_SW BIT(2) +#define LPSS_SW_LTR 0x10 +#define LPSS_AUTO_LTR 0x14 + +struct lpss_device_desc { + bool clk_required; + const char *clk_parent; + bool ltr_required; + unsigned int prv_offset; +}; + +struct lpss_private_data { + void __iomem *mmio_base; + resource_size_t mmio_size; + struct clk *clk; + const struct lpss_device_desc *dev_desc; +}; + +static struct lpss_device_desc lpt_dev_desc = { + .clk_required = true, + .clk_parent = "lpss_clk", + .prv_offset = 0x800, + .ltr_required = true, +}; + +static struct lpss_device_desc lpt_sdio_dev_desc = { + .prv_offset = 0x1000, + .ltr_required = true, +}; + +static const struct acpi_device_id acpi_lpss_device_ids[] = { + /* Lynxpoint LPSS devices */ + { "INT33C0", (unsigned long)&lpt_dev_desc }, + { "INT33C1", (unsigned long)&lpt_dev_desc }, + { "INT33C2", (unsigned long)&lpt_dev_desc }, + { "INT33C3", (unsigned long)&lpt_dev_desc }, + { "INT33C4", (unsigned long)&lpt_dev_desc }, + { "INT33C5", (unsigned long)&lpt_dev_desc }, + { "INT33C6", (unsigned long)&lpt_sdio_dev_desc }, + { "INT33C7", }, + + { } +}; + +static int is_memory(struct acpi_resource *res, void *not_used) +{ + struct resource r; + return !acpi_dev_resource_memory(res, &r); +} + +/* LPSS main clock device. */ +static struct platform_device *lpss_clk_dev; + +static inline void lpt_register_clock_device(void) +{ + lpss_clk_dev = platform_device_register_simple("clk-lpt", -1, NULL, 0); +} + +static int register_device_clock(struct acpi_device *adev, + struct lpss_private_data *pdata) +{ + const struct lpss_device_desc *dev_desc = pdata->dev_desc; + + if (!lpss_clk_dev) + lpt_register_clock_device(); + + if (!dev_desc->clk_parent || !pdata->mmio_base + || pdata->mmio_size < dev_desc->prv_offset + LPSS_CLK_SIZE) + return -ENODATA; + + pdata->clk = clk_register_gate(NULL, dev_name(&adev->dev), + dev_desc->clk_parent, 0, + pdata->mmio_base + dev_desc->prv_offset, + 0, 0, NULL); + if (IS_ERR(pdata->clk)) + return PTR_ERR(pdata->clk); + + clk_register_clkdev(pdata->clk, NULL, dev_name(&adev->dev)); + return 0; +} + +static int acpi_lpss_create_device(struct acpi_device *adev, + const struct acpi_device_id *id) +{ + struct lpss_device_desc *dev_desc; + struct lpss_private_data *pdata; + struct resource_list_entry *rentry; + struct list_head resource_list; + int ret; + + dev_desc = (struct lpss_device_desc *)id->driver_data; + if (!dev_desc) + return acpi_create_platform_device(adev, id); + + pdata = kzalloc(sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + INIT_LIST_HEAD(&resource_list); + ret = acpi_dev_get_resources(adev, &resource_list, is_memory, NULL); + if (ret < 0) + goto err_out; + + list_for_each_entry(rentry, &resource_list, node) + if (resource_type(&rentry->res) == IORESOURCE_MEM) { + pdata->mmio_size = resource_size(&rentry->res); + pdata->mmio_base = ioremap(rentry->res.start, + pdata->mmio_size); + pdata->dev_desc = dev_desc; + break; + } + + acpi_dev_free_resource_list(&resource_list); + + if (dev_desc->clk_required) { + ret = register_device_clock(adev, pdata); + if (ret) { + /* + * Skip the device, but don't terminate the namespace + * scan. + */ + kfree(pdata); + return 0; + } + } + + adev->driver_data = pdata; + ret = acpi_create_platform_device(adev, id); + if (ret > 0) + return ret; + + adev->driver_data = NULL; + + err_out: + kfree(pdata); + return ret; +} + +static int lpss_reg_read(struct device *dev, unsigned int reg, u32 *val) +{ + struct acpi_device *adev; + struct lpss_private_data *pdata; + unsigned long flags; + int ret; + + ret = acpi_bus_get_device(ACPI_HANDLE(dev), &adev); + if (WARN_ON(ret)) + return ret; + + spin_lock_irqsave(&dev->power.lock, flags); + if (pm_runtime_suspended(dev)) { + ret = -EAGAIN; + goto out; + } + pdata = acpi_driver_data(adev); + if (WARN_ON(!pdata || !pdata->mmio_base)) { + ret = -ENODEV; + goto out; + } + *val = readl(pdata->mmio_base + pdata->dev_desc->prv_offset + reg); + + out: + spin_unlock_irqrestore(&dev->power.lock, flags); + return ret; +} + +static ssize_t lpss_ltr_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + u32 ltr_value = 0; + unsigned int reg; + int ret; + + reg = strcmp(attr->attr.name, "auto_ltr") ? LPSS_SW_LTR : LPSS_AUTO_LTR; + ret = lpss_reg_read(dev, reg, <r_value); + if (ret) + return ret; + + return snprintf(buf, PAGE_SIZE, "%08x\n", ltr_value); +} + +static ssize_t lpss_ltr_mode_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + u32 ltr_mode = 0; + char *outstr; + int ret; + + ret = lpss_reg_read(dev, LPSS_GENERAL, <r_mode); + if (ret) + return ret; + + outstr = (ltr_mode & LPSS_GENERAL_LTR_MODE_SW) ? "sw" : "auto"; + return sprintf(buf, "%s\n", outstr); +} + +static DEVICE_ATTR(auto_ltr, S_IRUSR, lpss_ltr_show, NULL); +static DEVICE_ATTR(sw_ltr, S_IRUSR, lpss_ltr_show, NULL); +static DEVICE_ATTR(ltr_mode, S_IRUSR, lpss_ltr_mode_show, NULL); + +static struct attribute *lpss_attrs[] = { + &dev_attr_auto_ltr.attr, + &dev_attr_sw_ltr.attr, + &dev_attr_ltr_mode.attr, + NULL, +}; + +static struct attribute_group lpss_attr_group = { + .attrs = lpss_attrs, + .name = "lpss_ltr", +}; + +static int acpi_lpss_platform_notify(struct notifier_block *nb, + unsigned long action, void *data) +{ + struct platform_device *pdev = to_platform_device(data); + struct lpss_private_data *pdata; + struct acpi_device *adev; + const struct acpi_device_id *id; + int ret = 0; + + id = acpi_match_device(acpi_lpss_device_ids, &pdev->dev); + if (!id || !id->driver_data) + return 0; + + if (acpi_bus_get_device(ACPI_HANDLE(&pdev->dev), &adev)) + return 0; + + pdata = acpi_driver_data(adev); + if (!pdata || !pdata->mmio_base || !pdata->dev_desc->ltr_required) + return 0; + + if (pdata->mmio_size < pdata->dev_desc->prv_offset + LPSS_LTR_SIZE) { + dev_err(&pdev->dev, "MMIO size insufficient to access LTR\n"); + return 0; + } + + if (action == BUS_NOTIFY_ADD_DEVICE) + ret = sysfs_create_group(&pdev->dev.kobj, &lpss_attr_group); + else if (action == BUS_NOTIFY_DEL_DEVICE) + sysfs_remove_group(&pdev->dev.kobj, &lpss_attr_group); + + return ret; +} + +static struct notifier_block acpi_lpss_nb = { + .notifier_call = acpi_lpss_platform_notify, +}; + +static struct acpi_scan_handler lpss_handler = { + .ids = acpi_lpss_device_ids, + .attach = acpi_lpss_create_device, +}; + +void __init acpi_lpss_init(void) +{ + if (!lpt_clk_init()) { + bus_register_notifier(&platform_bus_type, &acpi_lpss_nb); + acpi_scan_add_handler(&lpss_handler); + } +} diff --git a/drivers/acpi/acpi_memhotplug.c b/drivers/acpi/acpi_memhotplug.c index da1f82b445e..5e6301e9492 100644 --- a/drivers/acpi/acpi_memhotplug.c +++ b/drivers/acpi/acpi_memhotplug.c @@ -1,5 +1,7 @@ /* - * Copyright (C) 2004 Intel Corporation <naveen.b.s@intel.com> + * Copyright (C) 2004, 2013 Intel Corporation + * Author: Naveen B S <naveen.b.s@intel.com> + * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> * * All rights reserved. * @@ -25,14 +27,10 @@ * ranges. */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/types.h> -#include <linux/memory_hotplug.h> -#include <linux/slab.h> #include <linux/acpi.h> -#include <acpi/acpi_drivers.h> +#include <linux/memory_hotplug.h> + +#include "internal.h" #define ACPI_MEMORY_DEVICE_CLASS "memory" #define ACPI_MEMORY_DEVICE_HID "PNP0C80" @@ -44,32 +42,28 @@ #define PREFIX "ACPI:memory_hp:" ACPI_MODULE_NAME("acpi_memhotplug"); -MODULE_AUTHOR("Naveen B S <naveen.b.s@intel.com>"); -MODULE_DESCRIPTION("Hotplug Mem Driver"); -MODULE_LICENSE("GPL"); /* Memory Device States */ #define MEMORY_INVALID_STATE 0 #define MEMORY_POWER_ON_STATE 1 #define MEMORY_POWER_OFF_STATE 2 -static int acpi_memory_device_add(struct acpi_device *device); -static int acpi_memory_device_remove(struct acpi_device *device); +static int acpi_memory_device_add(struct acpi_device *device, + const struct acpi_device_id *not_used); +static void acpi_memory_device_remove(struct acpi_device *device); static const struct acpi_device_id memory_device_ids[] = { {ACPI_MEMORY_DEVICE_HID, 0}, {"", 0}, }; -MODULE_DEVICE_TABLE(acpi, memory_device_ids); -static struct acpi_driver acpi_memory_device_driver = { - .name = "acpi_memhotplug", - .class = ACPI_MEMORY_DEVICE_CLASS, +static struct acpi_scan_handler memory_device_handler = { .ids = memory_device_ids, - .ops = { - .add = acpi_memory_device_add, - .remove = acpi_memory_device_remove, - }, + .attach = acpi_memory_device_add, + .detach = acpi_memory_device_remove, + .hotplug = { + .enabled = true, + }, }; struct acpi_memory_info { @@ -79,7 +73,6 @@ struct acpi_memory_info { unsigned short caching; /* memory cache attribute */ unsigned short write_protect; /* memory read/write attribute */ unsigned int enabled:1; - unsigned int failed:1; }; struct acpi_memory_device { @@ -153,48 +146,6 @@ acpi_memory_get_device_resources(struct acpi_memory_device *mem_device) return 0; } -static int acpi_memory_get_device(acpi_handle handle, - struct acpi_memory_device **mem_device) -{ - struct acpi_device *device = NULL; - int result = 0; - - acpi_scan_lock_acquire(); - - acpi_bus_get_device(handle, &device); - if (device) - goto end; - - /* - * Now add the notified device. This creates the acpi_device - * and invokes .add function - */ - result = acpi_bus_scan(handle); - if (result) { - acpi_handle_warn(handle, "ACPI namespace scan failed\n"); - result = -EINVAL; - goto out; - } - result = acpi_bus_get_device(handle, &device); - if (result) { - acpi_handle_warn(handle, "Missing device object\n"); - result = -EINVAL; - goto out; - } - - end: - *mem_device = acpi_driver_data(device); - if (!(*mem_device)) { - dev_err(&device->dev, "driver data not found\n"); - result = -ENODEV; - goto out; - } - - out: - acpi_scan_lock_release(); - return result; -} - static int acpi_memory_check_device(struct acpi_memory_device *mem_device) { unsigned long long current_status; @@ -249,13 +200,11 @@ static int acpi_memory_enable_device(struct acpi_memory_device *mem_device) * returns -EEXIST. If add_memory() returns the other error, it * means that this memory block is not used by the kernel. */ - if (result && result != -EEXIST) { - info->failed = 1; + if (result && result != -EEXIST) continue; - } - if (!result) - info->enabled = 1; + info->enabled = 1; + /* * Add num_enable even if add_memory() returns -EEXIST, so the * device is bound to this driver. @@ -286,16 +235,8 @@ static int acpi_memory_remove_memory(struct acpi_memory_device *mem_device) nid = acpi_get_node(mem_device->device->handle); list_for_each_entry_safe(info, n, &mem_device->res_list, list) { - if (info->failed) - /* The kernel does not use this memory block */ - continue; - if (!info->enabled) - /* - * The kernel uses this memory block, but it may be not - * managed by us. - */ - return -EBUSY; + continue; if (nid < 0) nid = memory_add_physaddr_to_nid(info->start_addr); @@ -310,95 +251,21 @@ static int acpi_memory_remove_memory(struct acpi_memory_device *mem_device) return result; } -static void acpi_memory_device_notify(acpi_handle handle, u32 event, void *data) -{ - struct acpi_memory_device *mem_device; - struct acpi_device *device; - struct acpi_eject_event *ej_event = NULL; - u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */ - acpi_status status; - - switch (event) { - case ACPI_NOTIFY_BUS_CHECK: - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "\nReceived BUS CHECK notification for device\n")); - /* Fall Through */ - case ACPI_NOTIFY_DEVICE_CHECK: - if (event == ACPI_NOTIFY_DEVICE_CHECK) - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "\nReceived DEVICE CHECK notification for device\n")); - if (acpi_memory_get_device(handle, &mem_device)) { - acpi_handle_err(handle, "Cannot find driver data\n"); - break; - } - - ost_code = ACPI_OST_SC_SUCCESS; - break; - - case ACPI_NOTIFY_EJECT_REQUEST: - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "\nReceived EJECT REQUEST notification for device\n")); - - status = AE_ERROR; - acpi_scan_lock_acquire(); - - if (acpi_bus_get_device(handle, &device)) { - acpi_handle_err(handle, "Device doesn't exist\n"); - goto unlock; - } - mem_device = acpi_driver_data(device); - if (!mem_device) { - acpi_handle_err(handle, "Driver Data is NULL\n"); - goto unlock; - } - - ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL); - if (!ej_event) { - pr_err(PREFIX "No memory, dropping EJECT\n"); - goto unlock; - } - - get_device(&device->dev); - ej_event->device = device; - ej_event->event = ACPI_NOTIFY_EJECT_REQUEST; - /* The eject is carried out asynchronously. */ - status = acpi_os_hotplug_execute(acpi_bus_hot_remove_device, - ej_event); - if (ACPI_FAILURE(status)) { - put_device(&device->dev); - kfree(ej_event); - } - - unlock: - acpi_scan_lock_release(); - if (ACPI_SUCCESS(status)) - return; - default: - ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "Unsupported event [0x%x]\n", event)); - - /* non-hotplug event; possibly handled by other handler */ - return; - } - - /* Inform firmware that the hotplug operation has completed */ - (void) acpi_evaluate_hotplug_ost(handle, event, ost_code, NULL); -} - static void acpi_memory_device_free(struct acpi_memory_device *mem_device) { if (!mem_device) return; acpi_memory_free_device_resources(mem_device); + mem_device->device->driver_data = NULL; kfree(mem_device); } -static int acpi_memory_device_add(struct acpi_device *device) +static int acpi_memory_device_add(struct acpi_device *device, + const struct acpi_device_id *not_used) { + struct acpi_memory_device *mem_device; int result; - struct acpi_memory_device *mem_device = NULL; - if (!device) return -EINVAL; @@ -423,147 +290,36 @@ static int acpi_memory_device_add(struct acpi_device *device) /* Set the device state */ mem_device->state = MEMORY_POWER_ON_STATE; - pr_debug("%s\n", acpi_device_name(device)); + result = acpi_memory_check_device(mem_device); + if (result) { + acpi_memory_device_free(mem_device); + return 0; + } - if (!acpi_memory_check_device(mem_device)) { - /* call add_memory func */ - result = acpi_memory_enable_device(mem_device); - if (result) { - dev_err(&device->dev, - "Error in acpi_memory_enable_device\n"); - acpi_memory_device_free(mem_device); - } + result = acpi_memory_enable_device(mem_device); + if (result) { + dev_err(&device->dev, "acpi_memory_enable_device() error\n"); + acpi_memory_device_free(mem_device); + return -ENODEV; } - return result; + + dev_dbg(&device->dev, "Memory device configured by ACPI\n"); + return 1; } -static int acpi_memory_device_remove(struct acpi_device *device) +static void acpi_memory_device_remove(struct acpi_device *device) { - struct acpi_memory_device *mem_device = NULL; - int result; + struct acpi_memory_device *mem_device; if (!device || !acpi_driver_data(device)) - return -EINVAL; + return; mem_device = acpi_driver_data(device); - - result = acpi_memory_remove_memory(mem_device); - if (result) - return result; - + acpi_memory_remove_memory(mem_device); acpi_memory_device_free(mem_device); - - return 0; -} - -/* - * Helper function to check for memory device - */ -static acpi_status is_memory_device(acpi_handle handle) -{ - char *hardware_id; - acpi_status status; - struct acpi_device_info *info; - - status = acpi_get_object_info(handle, &info); - if (ACPI_FAILURE(status)) - return status; - - if (!(info->valid & ACPI_VALID_HID)) { - kfree(info); - return AE_ERROR; - } - - hardware_id = info->hardware_id.string; - if ((hardware_id == NULL) || - (strcmp(hardware_id, ACPI_MEMORY_DEVICE_HID))) - status = AE_ERROR; - - kfree(info); - return status; -} - -static acpi_status -acpi_memory_register_notify_handler(acpi_handle handle, - u32 level, void *ctxt, void **retv) -{ - acpi_status status; - - - status = is_memory_device(handle); - if (ACPI_FAILURE(status)) - return AE_OK; /* continue */ - - status = acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, - acpi_memory_device_notify, NULL); - /* continue */ - return AE_OK; -} - -static acpi_status -acpi_memory_deregister_notify_handler(acpi_handle handle, - u32 level, void *ctxt, void **retv) -{ - acpi_status status; - - - status = is_memory_device(handle); - if (ACPI_FAILURE(status)) - return AE_OK; /* continue */ - - status = acpi_remove_notify_handler(handle, - ACPI_SYSTEM_NOTIFY, - acpi_memory_device_notify); - - return AE_OK; /* continue */ -} - -static int __init acpi_memory_device_init(void) -{ - int result; - acpi_status status; - - - result = acpi_bus_register_driver(&acpi_memory_device_driver); - - if (result < 0) - return -ENODEV; - - status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, - acpi_memory_register_notify_handler, NULL, - NULL, NULL); - - if (ACPI_FAILURE(status)) { - ACPI_EXCEPTION((AE_INFO, status, "walk_namespace failed")); - acpi_bus_unregister_driver(&acpi_memory_device_driver); - return -ENODEV; - } - - return 0; } -static void __exit acpi_memory_device_exit(void) +void __init acpi_memory_hotplug_init(void) { - acpi_status status; - - - /* - * Adding this to un-install notification handlers for all the device - * handles. - */ - status = acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, - ACPI_UINT32_MAX, - acpi_memory_deregister_notify_handler, NULL, - NULL, NULL); - - if (ACPI_FAILURE(status)) - ACPI_EXCEPTION((AE_INFO, status, "walk_namespace failed")); - - acpi_bus_unregister_driver(&acpi_memory_device_driver); - - return; + acpi_scan_add_handler_with_hotplug(&memory_device_handler, "memory"); } - -module_init(acpi_memory_device_init); -module_exit(acpi_memory_device_exit); diff --git a/drivers/acpi/acpi_pad.c b/drivers/acpi/acpi_pad.c index 31de1043eea..27bb6a91de5 100644 --- a/drivers/acpi/acpi_pad.c +++ b/drivers/acpi/acpi_pad.c @@ -236,7 +236,7 @@ static int create_power_saving_task(void) ps_tsks[ps_tsk_num] = kthread_run(power_saving_thread, (void *)(unsigned long)ps_tsk_num, "acpi_pad/%d", ps_tsk_num); - rc = IS_ERR(ps_tsks[ps_tsk_num]) ? PTR_ERR(ps_tsks[ps_tsk_num]) : 0; + rc = PTR_RET(ps_tsks[ps_tsk_num]); if (!rc) ps_tsk_num++; else diff --git a/drivers/acpi/acpi_platform.c b/drivers/acpi/acpi_platform.c index 26fce4b8a63..fafec5ddf17 100644 --- a/drivers/acpi/acpi_platform.c +++ b/drivers/acpi/acpi_platform.c @@ -22,9 +22,6 @@ ACPI_MODULE_NAME("platform"); -/* Flags for acpi_create_platform_device */ -#define ACPI_PLATFORM_CLK BIT(0) - /* * The following ACPI IDs are known to be suitable for representing as * platform devices. @@ -33,33 +30,9 @@ static const struct acpi_device_id acpi_platform_device_ids[] = { { "PNP0D40" }, - /* Haswell LPSS devices */ - { "INT33C0", ACPI_PLATFORM_CLK }, - { "INT33C1", ACPI_PLATFORM_CLK }, - { "INT33C2", ACPI_PLATFORM_CLK }, - { "INT33C3", ACPI_PLATFORM_CLK }, - { "INT33C4", ACPI_PLATFORM_CLK }, - { "INT33C5", ACPI_PLATFORM_CLK }, - { "INT33C6", ACPI_PLATFORM_CLK }, - { "INT33C7", ACPI_PLATFORM_CLK }, - { } }; -static int acpi_create_platform_clks(struct acpi_device *adev) -{ - static struct platform_device *pdev; - - /* Create Lynxpoint LPSS clocks */ - if (!pdev && !strncmp(acpi_device_hid(adev), "INT33C", 6)) { - pdev = platform_device_register_simple("clk-lpt", -1, NULL, 0); - if (IS_ERR(pdev)) - return PTR_ERR(pdev); - } - - return 0; -} - /** * acpi_create_platform_device - Create platform device for ACPI device node * @adev: ACPI device node to create a platform device for. @@ -71,10 +44,9 @@ static int acpi_create_platform_clks(struct acpi_device *adev) * * Name of the platform device will be the same as @adev's. */ -static int acpi_create_platform_device(struct acpi_device *adev, - const struct acpi_device_id *id) +int acpi_create_platform_device(struct acpi_device *adev, + const struct acpi_device_id *id) { - unsigned long flags = id->driver_data; struct platform_device *pdev = NULL; struct acpi_device *acpi_parent; struct platform_device_info pdevinfo; @@ -83,14 +55,6 @@ static int acpi_create_platform_device(struct acpi_device *adev, struct resource *resources; int count; - if (flags & ACPI_PLATFORM_CLK) { - int ret = acpi_create_platform_clks(adev); - if (ret) { - dev_err(&adev->dev, "failed to create clocks\n"); - return ret; - } - } - /* If the ACPI node already has a physical device attached, skip it. */ if (adev->physical_node_count) return 0; diff --git a/drivers/acpi/acpica/Makefile b/drivers/acpi/acpica/Makefile index a1b9bf5085a..7ddf29eca9f 100644 --- a/drivers/acpi/acpica/Makefile +++ b/drivers/acpi/acpica/Makefile @@ -83,6 +83,7 @@ acpi-$(ACPI_FUTURE_USAGE) += hwtimer.o acpi-y += \ nsaccess.o \ nsalloc.o \ + nsconvert.o \ nsdump.o \ nseval.o \ nsinit.o \ @@ -160,6 +161,7 @@ acpi-y += \ utobject.o \ utosi.o \ utownerid.o \ + utpredef.o \ utresrc.o \ utstate.o \ utstring.o \ diff --git a/drivers/acpi/acpica/acglobal.h b/drivers/acpi/acpica/acglobal.h index ecb49927b81..07160928ca2 100644 --- a/drivers/acpi/acpica/acglobal.h +++ b/drivers/acpi/acpica/acglobal.h @@ -224,6 +224,7 @@ ACPI_EXTERN u8 acpi_gbl_global_lock_pending; */ ACPI_EXTERN acpi_spinlock acpi_gbl_gpe_lock; /* For GPE data structs and registers */ ACPI_EXTERN acpi_spinlock acpi_gbl_hardware_lock; /* For ACPI H/W except GPE registers */ +ACPI_EXTERN acpi_spinlock acpi_gbl_reference_count_lock; /* Mutex for _OSI support */ @@ -413,10 +414,12 @@ ACPI_EXTERN u8 acpi_gbl_db_output_flags; #ifdef ACPI_DISASSEMBLER -u8 ACPI_INIT_GLOBAL(acpi_gbl_ignore_noop_operator, FALSE); +ACPI_EXTERN u8 ACPI_INIT_GLOBAL(acpi_gbl_ignore_noop_operator, FALSE); ACPI_EXTERN u8 acpi_gbl_db_opt_disasm; ACPI_EXTERN u8 acpi_gbl_db_opt_verbose; +ACPI_EXTERN u8 acpi_gbl_num_external_methods; +ACPI_EXTERN u32 acpi_gbl_resolved_external_methods; ACPI_EXTERN struct acpi_external_list *acpi_gbl_external_list; ACPI_EXTERN struct acpi_external_file *acpi_gbl_external_file_list; #endif diff --git a/drivers/acpi/acpica/aclocal.h b/drivers/acpi/acpica/aclocal.h index 805f419086a..d5bfbd331bf 100644 --- a/drivers/acpi/acpica/aclocal.h +++ b/drivers/acpi/acpica/aclocal.h @@ -294,6 +294,8 @@ acpi_status(*acpi_internal_method) (struct acpi_walk_state * walk_state); #define ACPI_BTYPE_OBJECTS_AND_REFS 0x0001FFFF /* ARG or LOCAL */ #define ACPI_BTYPE_ALL_OBJECTS 0x0000FFFF +#pragma pack(1) + /* * Information structure for ACPI predefined names. * Each entry in the table contains the following items: @@ -304,7 +306,7 @@ acpi_status(*acpi_internal_method) (struct acpi_walk_state * walk_state); */ struct acpi_name_info { char name[ACPI_NAME_SIZE]; - u8 param_count; + u16 argument_list; u8 expected_btypes; }; @@ -327,7 +329,7 @@ struct acpi_package_info { u8 count1; u8 object_type2; u8 count2; - u8 reserved; + u16 reserved; }; /* Used for ACPI_PTYPE2_FIXED */ @@ -336,6 +338,7 @@ struct acpi_package_info2 { u8 type; u8 count; u8 object_type[4]; + u8 reserved; }; /* Used for ACPI_PTYPE1_OPTION */ @@ -345,7 +348,7 @@ struct acpi_package_info3 { u8 count; u8 object_type[2]; u8 tail_object_type; - u8 reserved; + u16 reserved; }; union acpi_predefined_info { @@ -355,6 +358,10 @@ union acpi_predefined_info { struct acpi_package_info3 ret_info3; }; +/* Reset to default packing */ + +#pragma pack() + /* Data block used during object validation */ struct acpi_predefined_data { @@ -363,6 +370,7 @@ struct acpi_predefined_data { union acpi_operand_object *parent_package; struct acpi_namespace_node *node; u32 flags; + u32 return_btype; u8 node_flags; }; @@ -371,6 +379,20 @@ struct acpi_predefined_data { #define ACPI_OBJECT_REPAIRED 1 #define ACPI_OBJECT_WRAPPED 2 +/* Return object auto-repair info */ + +typedef acpi_status(*acpi_object_converter) (union acpi_operand_object + *original_object, + union acpi_operand_object + **converted_object); + +struct acpi_simple_repair_info { + char name[ACPI_NAME_SIZE]; + u32 unexpected_btypes; + u32 package_index; + acpi_object_converter object_converter; +}; + /* * Bitmapped return value types * Note: the actual data types must be contiguous, a loop in nspredef.c @@ -1037,6 +1059,7 @@ struct acpi_external_list { u16 length; u8 type; u8 flags; + u8 resolved; }; /* Values for Flags field above */ diff --git a/drivers/acpi/acpica/acmacros.h b/drivers/acpi/acpica/acmacros.h index ed7943b9044..53666bd9193 100644 --- a/drivers/acpi/acpica/acmacros.h +++ b/drivers/acpi/acpica/acmacros.h @@ -322,10 +322,12 @@ * where a pointer to an object of type union acpi_operand_object can also * appear. This macro is used to distinguish them. * - * The "Descriptor" field is the first field in both structures. + * The "DescriptorType" field is the second field in both structures. */ +#define ACPI_GET_DESCRIPTOR_PTR(d) (((union acpi_descriptor *)(void *)(d))->common.common_pointer) +#define ACPI_SET_DESCRIPTOR_PTR(d, p) (((union acpi_descriptor *)(void *)(d))->common.common_pointer = (p)) #define ACPI_GET_DESCRIPTOR_TYPE(d) (((union acpi_descriptor *)(void *)(d))->common.descriptor_type) -#define ACPI_SET_DESCRIPTOR_TYPE(d, t) (((union acpi_descriptor *)(void *)(d))->common.descriptor_type = t) +#define ACPI_SET_DESCRIPTOR_TYPE(d, t) (((union acpi_descriptor *)(void *)(d))->common.descriptor_type = (t)) /* * Macros for the master AML opcode table diff --git a/drivers/acpi/acpica/acnamesp.h b/drivers/acpi/acpica/acnamesp.h index 02cd5482ff8..d2e491876bc 100644 --- a/drivers/acpi/acpica/acnamesp.h +++ b/drivers/acpi/acpica/acnamesp.h @@ -167,6 +167,29 @@ void acpi_ns_delete_children(struct acpi_namespace_node *parent); int acpi_ns_compare_names(char *name1, char *name2); /* + * nsconvert - Dynamic object conversion routines + */ +acpi_status +acpi_ns_convert_to_integer(union acpi_operand_object *original_object, + union acpi_operand_object **return_object); + +acpi_status +acpi_ns_convert_to_string(union acpi_operand_object *original_object, + union acpi_operand_object **return_object); + +acpi_status +acpi_ns_convert_to_buffer(union acpi_operand_object *original_object, + union acpi_operand_object **return_object); + +acpi_status +acpi_ns_convert_to_unicode(union acpi_operand_object *original_object, + union acpi_operand_object **return_object); + +acpi_status +acpi_ns_convert_to_resource(union acpi_operand_object *original_object, + union acpi_operand_object **return_object); + +/* * nsdump - Namespace dump/print utilities */ #ifdef ACPI_FUTURE_USAGE @@ -208,10 +231,6 @@ acpi_ns_check_predefined_names(struct acpi_namespace_node *node, acpi_status return_status, union acpi_operand_object **return_object); -const union acpi_predefined_info *acpi_ns_check_for_predefined_name(struct - acpi_namespace_node - *node); - void acpi_ns_check_parameter_count(char *pathname, struct acpi_namespace_node *node, @@ -289,7 +308,7 @@ acpi_ns_get_attached_data(struct acpi_namespace_node *node, * predefined methods/objects */ acpi_status -acpi_ns_repair_object(struct acpi_predefined_data *data, +acpi_ns_simple_repair(struct acpi_predefined_data *data, u32 expected_btypes, u32 package_index, union acpi_operand_object **return_object_ptr); diff --git a/drivers/acpi/acpica/acpredef.h b/drivers/acpi/acpica/acpredef.h index 752cc40cdc1..b22b70944fd 100644 --- a/drivers/acpi/acpica/acpredef.h +++ b/drivers/acpi/acpica/acpredef.h @@ -56,7 +56,7 @@ * object type * count * - * ACPI_PTYPE1_VAR: Variable-length length: + * ACPI_PTYPE1_VAR: Variable-length length. Zero-length package is allowed: * object type (Int/Buf/Ref) * * ACPI_PTYPE1_OPTION: Package has some required and some optional elements @@ -66,14 +66,16 @@ * 2) PTYPE2 packages contain a Variable-length number of sub-packages. Each * of the different types describe the contents of each of the sub-packages. * - * ACPI_PTYPE2: Each subpackage contains 1 or 2 object types: + * ACPI_PTYPE2: Each subpackage contains 1 or 2 object types. Zero-length + * parent package is allowed: * object type * count * object type * count * (Used for _ALR,_MLS,_PSS,_TRT,_TSS) * - * ACPI_PTYPE2_COUNT: Each subpackage has a count as first element: + * ACPI_PTYPE2_COUNT: Each subpackage has a count as first element. + * Zero-length parent package is allowed: * object type * (Used for _CSD,_PSD,_TSD) * @@ -84,17 +86,19 @@ * count * (Used for _CST) * - * ACPI_PTYPE2_FIXED: Each subpackage is of Fixed-length + * ACPI_PTYPE2_FIXED: Each subpackage is of Fixed-length. Zero-length + * parent package is allowed. * (Used for _PRT) * - * ACPI_PTYPE2_MIN: Each subpackage has a Variable-length but minimum length + * ACPI_PTYPE2_MIN: Each subpackage has a Variable-length but minimum length. + * Zero-length parent package is allowed: * (Used for _HPX) * * ACPI_PTYPE2_REV_FIXED: Revision at start, each subpackage is Fixed-length * (Used for _ART, _FPS) * * ACPI_PTYPE2_FIX_VAR: Each subpackage consists of some fixed-length elements - * followed by an optional element + * followed by an optional element. Zero-length parent package is allowed. * object type * count * object type @@ -116,8 +120,47 @@ enum acpi_return_package_types { ACPI_PTYPE2_FIX_VAR = 10 }; +/* Support macros for users of the predefined info table */ + +#define METHOD_PREDEF_ARGS_MAX 4 +#define METHOD_ARG_BIT_WIDTH 3 +#define METHOD_ARG_MASK 0x0007 +#define ARG_COUNT_IS_MINIMUM 0x8000 +#define METHOD_MAX_ARG_TYPE ACPI_TYPE_PACKAGE + +#define METHOD_GET_COUNT(arg_list) (arg_list & METHOD_ARG_MASK) +#define METHOD_GET_NEXT_ARG(arg_list) (arg_list >> METHOD_ARG_BIT_WIDTH) + +/* Macros used to build the predefined info table */ + +#define METHOD_0ARGS 0 +#define METHOD_1ARGS(a1) (1 | (a1 << 3)) +#define METHOD_2ARGS(a1,a2) (2 | (a1 << 3) | (a2 << 6)) +#define METHOD_3ARGS(a1,a2,a3) (3 | (a1 << 3) | (a2 << 6) | (a3 << 9)) +#define METHOD_4ARGS(a1,a2,a3,a4) (4 | (a1 << 3) | (a2 << 6) | (a3 << 9) | (a4 << 12)) + +#define METHOD_RETURNS(type) (type) +#define METHOD_NO_RETURN_VALUE 0 + +#define PACKAGE_INFO(a,b,c,d,e,f) {{{(a),(b),(c),(d)}, ((((u16)(f)) << 8) | (e)), 0}} + +/* Support macros for the resource descriptor info table */ + +#define WIDTH_1 0x0001 +#define WIDTH_2 0x0002 +#define WIDTH_3 0x0004 +#define WIDTH_8 0x0008 +#define WIDTH_16 0x0010 +#define WIDTH_32 0x0020 +#define WIDTH_64 0x0040 +#define VARIABLE_DATA 0x0080 +#define NUM_RESOURCE_WIDTHS 8 + +#define WIDTH_ADDRESS WIDTH_16 | WIDTH_32 | WIDTH_64 + #ifdef ACPI_CREATE_PREDEFINED_TABLE -/* +/****************************************************************************** + * * Predefined method/object information table. * * These are the names that can actually be evaluated via acpi_evaluate_object. @@ -125,23 +168,24 @@ enum acpi_return_package_types { * * 1) Predefined/Reserved names that are never evaluated via * acpi_evaluate_object: - * _Lxx and _Exx GPE methods - * _Qxx EC methods - * _T_x compiler temporary variables + * _Lxx and _Exx GPE methods + * _Qxx EC methods + * _T_x compiler temporary variables + * _Wxx wake events * * 2) Predefined names that never actually exist within the AML code: - * Predefined resource descriptor field names + * Predefined resource descriptor field names * * 3) Predefined names that are implemented within ACPICA: - * _OSI - * - * 4) Some predefined names that are not documented within the ACPI spec. - * _WDG, _WED + * _OSI * * The main entries in the table each contain the following items: * * name - The ACPI reserved name - * param_count - Number of arguments to the method + * argument_list - Contains (in 16 bits), the number of required + * arguments to the method (3 bits), and a 3-bit type + * field for each argument (up to 4 arguments). The + * METHOD_?ARGS macros generate the correct packed data. * expected_btypes - Allowed type(s) for the return value. * 0 means that no return value is expected. * @@ -151,256 +195,511 @@ enum acpi_return_package_types { * overall size of the stored data. * * Note: The additional braces are intended to promote portability. - */ -static const union acpi_predefined_info predefined_names[] = { - {{"_AC0", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC1", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC2", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC3", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC4", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC5", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC6", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC7", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC8", 0, ACPI_RTYPE_INTEGER}}, - {{"_AC9", 0, ACPI_RTYPE_INTEGER}}, - {{"_ADR", 0, ACPI_RTYPE_INTEGER}}, - {{"_AEI", 0, ACPI_RTYPE_BUFFER}}, - {{"_AL0", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL1", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL2", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL3", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL4", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL5", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL6", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL7", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL8", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_AL9", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_ALC", 0, ACPI_RTYPE_INTEGER}}, - {{"_ALI", 0, ACPI_RTYPE_INTEGER}}, - {{"_ALP", 0, ACPI_RTYPE_INTEGER}}, - {{"_ALR", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each 2 (Ints) */ - {{{ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 2,0}, 0,0}}, - - {{"_ALT", 0, ACPI_RTYPE_INTEGER}}, - {{"_ART", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (1 Int(rev), n Pkg (2 Ref/11 Int) */ - {{{ACPI_PTYPE2_REV_FIXED, ACPI_RTYPE_REFERENCE, 2, ACPI_RTYPE_INTEGER}, - 11, 0}}, - - {{"_BBN", 0, ACPI_RTYPE_INTEGER}}, - {{"_BCL", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Ints) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0,0}, 0,0}}, - - {{"_BCM", 1, 0}}, - {{"_BCT", 1, ACPI_RTYPE_INTEGER}}, - {{"_BDN", 0, ACPI_RTYPE_INTEGER}}, - {{"_BFS", 1, 0}}, - {{"_BIF", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (9 Int),(4 Str) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 9, ACPI_RTYPE_STRING}, 4, 0}}, - - {{"_BIX", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (16 Int),(4 Str) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 16, ACPI_RTYPE_STRING}, 4, - 0}}, - - {{"_BLT", 3, 0}}, - {{"_BMA", 1, ACPI_RTYPE_INTEGER}}, - {{"_BMC", 1, 0}}, - {{"_BMD", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (5 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 5,0}, 0,0}}, - - {{"_BMS", 1, ACPI_RTYPE_INTEGER}}, - {{"_BQC", 0, ACPI_RTYPE_INTEGER}}, - {{"_BST", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (4 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4,0}, 0,0}}, - - {{"_BTM", 1, ACPI_RTYPE_INTEGER}}, - {{"_BTP", 1, 0}}, - {{"_CBA", 0, ACPI_RTYPE_INTEGER}}, /* See PCI firmware spec 3.0 */ - {{"_CDM", 0, ACPI_RTYPE_INTEGER}}, - {{"_CID", 0, ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | ACPI_RTYPE_PACKAGE}}, /* Variable-length (Ints/Strs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING, 0, 0}, 0, - 0}}, - - {{"_CLS", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (3 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3, 0}, 0, 0}}, - - {{"_CPC", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Ints/Bufs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER | ACPI_RTYPE_BUFFER, 0, 0}, 0, - 0}}, - - {{"_CRS", 0, ACPI_RTYPE_BUFFER}}, - {{"_CRT", 0, ACPI_RTYPE_INTEGER}}, - {{"_CSD", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (1 Int(n), n-1 Int) */ - {{{ACPI_PTYPE2_COUNT, ACPI_RTYPE_INTEGER, 0,0}, 0,0}}, - - {{"_CST", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (1 Int(n), n Pkg (1 Buf/3 Int) */ - {{{ACPI_PTYPE2_PKG_COUNT, ACPI_RTYPE_BUFFER, 1, ACPI_RTYPE_INTEGER}, 3, - 0}}, - - {{"_CWS", 1, ACPI_RTYPE_INTEGER}}, - {{"_DCK", 1, ACPI_RTYPE_INTEGER}}, - {{"_DCS", 0, ACPI_RTYPE_INTEGER}}, - {{"_DDC", 1, ACPI_RTYPE_INTEGER | ACPI_RTYPE_BUFFER}}, - {{"_DDN", 0, ACPI_RTYPE_STRING}}, - {{"_DEP", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0}, 0, 0}}, - - {{"_DGS", 0, ACPI_RTYPE_INTEGER}}, - {{"_DIS", 0, 0}}, - - {{"_DLM", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each (1 Ref, 0/1 Optional Buf/Ref) */ - {{{ACPI_PTYPE2_FIX_VAR, ACPI_RTYPE_REFERENCE, 1, - ACPI_RTYPE_REFERENCE | ACPI_RTYPE_BUFFER}, 0, 0}}, - - {{"_DMA", 0, ACPI_RTYPE_BUFFER}}, - {{"_DOD", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Ints) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0,0}, 0,0}}, - - {{"_DOS", 1, 0}}, - {{"_DSM", 4, ACPI_RTYPE_ALL}}, /* Must return a type, but it can be of any type */ - {{"_DSS", 1, 0}}, - {{"_DSW", 3, 0}}, - {{"_DTI", 1, 0}}, - {{"_EC_", 0, ACPI_RTYPE_INTEGER}}, - {{"_EDL", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs)*/ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_EJ0", 1, 0}}, - {{"_EJ1", 1, 0}}, - {{"_EJ2", 1, 0}}, - {{"_EJ3", 1, 0}}, - {{"_EJ4", 1, 0}}, - {{"_EJD", 0, ACPI_RTYPE_STRING}}, - {{"_EVT", 1, 0}}, - {{"_FDE", 0, ACPI_RTYPE_BUFFER}}, - {{"_FDI", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (16 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 16,0}, 0,0}}, - - {{"_FDM", 1, 0}}, - {{"_FIF", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (4 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4, 0}, 0, 0}}, - - {{"_FIX", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Ints) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0,0}, 0,0}}, - - {{"_FPS", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (1 Int(rev), n Pkg (5 Int) */ - {{{ACPI_PTYPE2_REV_FIXED, ACPI_RTYPE_INTEGER, 5, 0}, 0, 0}}, - - {{"_FSL", 1, 0}}, - {{"_FST", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (3 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3, 0}, 0, 0}}, - - {{"_GAI", 0, ACPI_RTYPE_INTEGER}}, - {{"_GCP", 0, ACPI_RTYPE_INTEGER}}, - {{"_GHL", 0, ACPI_RTYPE_INTEGER}}, - {{"_GLK", 0, ACPI_RTYPE_INTEGER}}, - {{"_GPD", 0, ACPI_RTYPE_INTEGER}}, - {{"_GPE", 0, ACPI_RTYPE_INTEGER}}, /* _GPE method, not _GPE scope */ - {{"_GRT", 0, ACPI_RTYPE_BUFFER}}, - {{"_GSB", 0, ACPI_RTYPE_INTEGER}}, - {{"_GTF", 0, ACPI_RTYPE_BUFFER}}, - {{"_GTM", 0, ACPI_RTYPE_BUFFER}}, - {{"_GTS", 1, 0}}, - {{"_GWS", 1, ACPI_RTYPE_INTEGER}}, - {{"_HID", 0, ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING}}, - {{"_HOT", 0, ACPI_RTYPE_INTEGER}}, - {{"_HPP", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (4 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4,0}, 0,0}}, + * + * Note2: Table is used by the kernel-resident subsystem, the iASL compiler, + * and the acpi_help utility. + * + * TBD: _PRT - currently ignore reversed entries. Attempt to fix in nsrepair. + * Possibly fixing package elements like _BIF, etc. + * + *****************************************************************************/ + +const union acpi_predefined_info acpi_gbl_predefined_methods[] = { + {{"_AC0", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC1", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC2", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC3", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC4", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC5", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC6", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC7", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC8", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AC9", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_ADR", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_AEI", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_AL0", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL1", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL2", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL3", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL4", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL5", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL6", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL7", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL8", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_AL9", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_ALC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_ALI", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_ALP", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_ALR", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each 2 (Ints) */ + PACKAGE_INFO(ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 2, 0, 0, 0), + + {{"_ALT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_ART", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(rev), n Pkg (2 Ref/11 Int) */ + PACKAGE_INFO(ACPI_PTYPE2_REV_FIXED, ACPI_RTYPE_REFERENCE, 2, + ACPI_RTYPE_INTEGER, 11, 0), + + {{"_BBN", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_BCL", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0, 0, 0, 0), + + {{"_BCM", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_BCT", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_BDN", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_BFS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_BIF", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (9 Int),(4 Str) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 9, + ACPI_RTYPE_STRING, 4, 0), + + {{"_BIX", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (16 Int),(4 Str) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 16, + ACPI_RTYPE_STRING, 4, 0), + + {{"_BLT", + METHOD_3ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_BMA", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_BMC", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_BMD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (5 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 5, 0, 0, 0), + + {{"_BMS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_BQC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_BST", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (4 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4, 0, 0, 0), + + {{"_BTM", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_BTP", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_CBA", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, /* See PCI firmware spec 3.0 */ + + {{"_CDM", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_CID", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints/Strs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING, 0, + 0, 0, 0), + + {{"_CLS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (3 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3, 0, 0, 0), + + {{"_CPC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints/Bufs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER | ACPI_RTYPE_BUFFER, 0, + 0, 0, 0), + + {{"_CRS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_CRT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_CSD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(n), n-1 Int) */ + PACKAGE_INFO(ACPI_PTYPE2_COUNT, ACPI_RTYPE_INTEGER, 0, 0, 0, 0), + + {{"_CST", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(n), n Pkg (1 Buf/3 Int) */ + PACKAGE_INFO(ACPI_PTYPE2_PKG_COUNT, ACPI_RTYPE_BUFFER, 1, + ACPI_RTYPE_INTEGER, 3, 0), + + {{"_CWS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_DCK", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_DCS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_DDC", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER | ACPI_RTYPE_BUFFER)}}, + + {{"_DDN", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_STRING)}}, + + {{"_DEP", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_DGS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_DIS", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_DLM", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (1 Ref, 0/1 Optional Buf/Ref) */ + PACKAGE_INFO(ACPI_PTYPE2_FIX_VAR, ACPI_RTYPE_REFERENCE, 1, + ACPI_RTYPE_REFERENCE | ACPI_RTYPE_BUFFER, 0, 0), + + {{"_DMA", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_DOD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0, 0, 0, 0), + + {{"_DOS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_DSM", + METHOD_4ARGS(ACPI_TYPE_BUFFER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, + ACPI_TYPE_PACKAGE), + METHOD_RETURNS(ACPI_RTYPE_ALL)}}, /* Must return a value, but it can be of any type */ + + {{"_DSS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_DSW", + METHOD_3ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_DTI", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EC_", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_EDL", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_EJ0", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJ1", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJ2", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJ3", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJ4", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_EJD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_STRING)}}, + + {{"_ERR", + METHOD_3ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_STRING, ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, /* Internal use only, used by ACPICA test suites */ + + {{"_EVT", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_FDE", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_FDI", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (16 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 16, 0, 0, 0), + + {{"_FDM", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_FIF", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (4 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4, 0, 0, 0), + + {{"_FIX", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Ints) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 0, 0, 0, 0), + + {{"_FPS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (1 Int(rev), n Pkg (5 Int) */ + PACKAGE_INFO(ACPI_PTYPE2_REV_FIXED, ACPI_RTYPE_INTEGER, 5, 0, 0, 0), + + {{"_FSL", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_FST", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (3 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3, 0, 0, 0), + + {{"_GAI", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_GCP", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_GHL", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_GLK", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_GPD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_GPE", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, /* _GPE method, not _GPE scope */ + + {{"_GRT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_GSB", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_GTF", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_GTM", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_GTS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_GWS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_HID", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING)}}, + + {{"_HOT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_HPP", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (4 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4, 0, 0, 0), /* - * For _HPX, a single package is returned, containing a Variable-length number + * For _HPX, a single package is returned, containing a variable-length number * of sub-packages. Each sub-package contains a PCI record setting. * There are several different type of record settings, of different * lengths, but all elements of all settings are Integers. */ - {{"_HPX", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each (var Ints) */ - {{{ACPI_PTYPE2_MIN, ACPI_RTYPE_INTEGER, 5,0}, 0,0}}, - - {{"_HRV", 0, ACPI_RTYPE_INTEGER}}, - {{"_IFT", 0, ACPI_RTYPE_INTEGER}}, /* See IPMI spec */ - {{"_INI", 0, 0}}, - {{"_IRC", 0, 0}}, - {{"_LCK", 1, 0}}, - {{"_LID", 0, ACPI_RTYPE_INTEGER}}, - {{"_MAT", 0, ACPI_RTYPE_BUFFER}}, - {{"_MBM", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (8 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 8, 0}, 0, 0}}, - - {{"_MLS", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each (1 Str/1 Buf) */ - {{{ACPI_PTYPE2, ACPI_RTYPE_STRING, 1, ACPI_RTYPE_BUFFER}, 1, 0}}, - - {{"_MSG", 1, 0}}, - {{"_MSM", 4, ACPI_RTYPE_INTEGER}}, - {{"_NTT", 0, ACPI_RTYPE_INTEGER}}, - {{"_OFF", 0, 0}}, - {{"_ON_", 0, 0}}, - {{"_OS_", 0, ACPI_RTYPE_STRING}}, - {{"_OSC", 4, ACPI_RTYPE_BUFFER}}, - {{"_OST", 3, 0}}, - {{"_PAI", 1, ACPI_RTYPE_INTEGER}}, - {{"_PCL", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_PCT", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (2 Buf) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_BUFFER, 2,0}, 0,0}}, - - {{"_PDC", 1, 0}}, - {{"_PDL", 0, ACPI_RTYPE_INTEGER}}, - {{"_PIC", 1, 0}}, - {{"_PIF", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (3 Int),(3 Str) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3, ACPI_RTYPE_STRING}, 3, 0}}, - - {{"_PLD", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Bufs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_BUFFER, 0,0}, 0,0}}, - - {{"_PMC", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (11 Int),(3 Str) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 11, ACPI_RTYPE_STRING}, 3, - 0}}, - - {{"_PMD", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0}, 0, 0}}, - - {{"_PMM", 0, ACPI_RTYPE_INTEGER}}, - {{"_PPC", 0, ACPI_RTYPE_INTEGER}}, - {{"_PPE", 0, ACPI_RTYPE_INTEGER}}, /* See dig64 spec */ - {{"_PR0", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_PR1", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_PR2", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_PR3", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0}, 0, 0}}, - - {{"_PRE", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0}, 0, 0}}, - - {{"_PRL", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0}, 0, 0}}, - - {{"_PRS", 0, ACPI_RTYPE_BUFFER}}, + {{"_HPX", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (var Ints) */ + PACKAGE_INFO(ACPI_PTYPE2_MIN, ACPI_RTYPE_INTEGER, 5, 0, 0, 0), + + {{"_HRV", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_IFT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, /* See IPMI spec */ + + {{"_INI", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_IRC", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_LCK", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_LID", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_MAT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_MBM", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (8 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 8, 0, 0, 0), + + {{"_MLS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (1 Str/1 Buf) */ + PACKAGE_INFO(ACPI_PTYPE2, ACPI_RTYPE_STRING, 1, ACPI_RTYPE_BUFFER, 1, + 0), + + {{"_MSG", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_MSM", + METHOD_4ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, + ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_NTT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_OFF", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_ON_", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_OS_", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_STRING)}}, + + {{"_OSC", + METHOD_4ARGS(ACPI_TYPE_BUFFER, ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, + ACPI_TYPE_BUFFER), + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_OST", + METHOD_3ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER, ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PAI", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_PCL", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PCT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (2 Buf) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_BUFFER, 2, 0, 0, 0), + + {{"_PDC", METHOD_1ARGS(ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PDL", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_PIC", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PIF", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (3 Int),(3 Str) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 3, + ACPI_RTYPE_STRING, 3, 0), + + {{"_PLD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Bufs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_BUFFER, 0, 0, 0, 0), + + {{"_PMC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (11 Int),(3 Str) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 11, + ACPI_RTYPE_STRING, 3, 0), + + {{"_PMD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PMM", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_PPC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_PPE", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, /* See dig64 spec */ + + {{"_PR0", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PR1", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PR2", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PR3", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PRE", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PRL", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PRS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, /* * For _PRT, many BIOSs reverse the 3rd and 4th Package elements (Source @@ -410,47 +709,89 @@ static const union acpi_predefined_info predefined_names[] = { * warning, add the ACPI_RTYPE_REFERENCE type to the 4th element (index 3) * in the statement below. */ - {{"_PRT", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each (4): Int,Int,Int/Ref,Int */ - {{{ACPI_PTYPE2_FIXED, 4, ACPI_RTYPE_INTEGER,ACPI_RTYPE_INTEGER}, - ACPI_RTYPE_INTEGER | ACPI_RTYPE_REFERENCE, - ACPI_RTYPE_INTEGER}}, - - {{"_PRW", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each: Pkg/Int,Int,[Variable-length Refs] (Pkg is Ref/Int) */ - {{{ACPI_PTYPE1_OPTION, 2, ACPI_RTYPE_INTEGER | ACPI_RTYPE_PACKAGE, - ACPI_RTYPE_INTEGER}, ACPI_RTYPE_REFERENCE,0}}, - - {{"_PS0", 0, 0}}, - {{"_PS1", 0, 0}}, - {{"_PS2", 0, 0}}, - {{"_PS3", 0, 0}}, - {{"_PSC", 0, ACPI_RTYPE_INTEGER}}, - {{"_PSD", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each (5 Int) with count */ - {{{ACPI_PTYPE2_COUNT, ACPI_RTYPE_INTEGER,0,0}, 0,0}}, - - {{"_PSE", 1, 0}}, - {{"_PSL", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_PSR", 0, ACPI_RTYPE_INTEGER}}, - {{"_PSS", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each (6 Int) */ - {{{ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 6,0}, 0,0}}, - - {{"_PSV", 0, ACPI_RTYPE_INTEGER}}, - {{"_PSW", 1, 0}}, - {{"_PTC", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (2 Buf) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_BUFFER, 2,0}, 0,0}}, - - {{"_PTP", 2, ACPI_RTYPE_INTEGER}}, - {{"_PTS", 1, 0}}, - {{"_PUR", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (2 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 2, 0}, 0, 0}}, - - {{"_PXM", 0, ACPI_RTYPE_INTEGER}}, - {{"_REG", 2, 0}}, - {{"_REV", 0, ACPI_RTYPE_INTEGER}}, - {{"_RMV", 0, ACPI_RTYPE_INTEGER}}, - {{"_ROM", 2, ACPI_RTYPE_BUFFER}}, - {{"_RTV", 0, ACPI_RTYPE_INTEGER}}, + {{"_PRT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (4): Int,Int,Int/Ref,Int */ + PACKAGE_INFO(ACPI_PTYPE2_FIXED, 4, ACPI_RTYPE_INTEGER, + ACPI_RTYPE_INTEGER, + ACPI_RTYPE_INTEGER | ACPI_RTYPE_REFERENCE, + ACPI_RTYPE_INTEGER), + + {{"_PRW", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each: Pkg/Int,Int,[Variable-length Refs] (Pkg is Ref/Int) */ + PACKAGE_INFO(ACPI_PTYPE1_OPTION, 2, + ACPI_RTYPE_INTEGER | ACPI_RTYPE_PACKAGE, + ACPI_RTYPE_INTEGER, ACPI_RTYPE_REFERENCE, 0), + + {{"_PS0", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_PS1", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_PS2", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_PS3", METHOD_0ARGS, + METHOD_NO_RETURN_VALUE}}, + + {{"_PSC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_PSD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (5 Int) with count */ + PACKAGE_INFO(ACPI_PTYPE2_COUNT, ACPI_RTYPE_INTEGER, 0, 0, 0, 0), + + {{"_PSE", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PSL", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_PSR", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_PSS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each (6 Int) */ + PACKAGE_INFO(ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 6, 0, 0, 0), + + {{"_PSV", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_PSW", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PTC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (2 Buf) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_BUFFER, 2, 0, 0, 0), + + {{"_PTP", METHOD_2ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_PTS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_PUR", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (2 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 2, 0, 0, 0), + + {{"_PXM", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_REG", METHOD_2ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_REV", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_RMV", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_ROM", METHOD_2ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_RTV", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, /* * For _S0_ through _S5_, the ACPI spec defines a return Package @@ -458,111 +799,285 @@ static const union acpi_predefined_info predefined_names[] = { * Allow this by making the objects "Variable-length length", but all elements * must be Integers. */ - {{"_S0_", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (1 Int) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0}, 0,0}}, - - {{"_S1_", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (1 Int) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0}, 0,0}}, - - {{"_S2_", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (1 Int) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0}, 0,0}}, - - {{"_S3_", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (1 Int) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0}, 0,0}}, - - {{"_S4_", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (1 Int) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0}, 0,0}}, - - {{"_S5_", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (1 Int) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1,0}, 0,0}}, - - {{"_S1D", 0, ACPI_RTYPE_INTEGER}}, - {{"_S2D", 0, ACPI_RTYPE_INTEGER}}, - {{"_S3D", 0, ACPI_RTYPE_INTEGER}}, - {{"_S4D", 0, ACPI_RTYPE_INTEGER}}, - {{"_S0W", 0, ACPI_RTYPE_INTEGER}}, - {{"_S1W", 0, ACPI_RTYPE_INTEGER}}, - {{"_S2W", 0, ACPI_RTYPE_INTEGER}}, - {{"_S3W", 0, ACPI_RTYPE_INTEGER}}, - {{"_S4W", 0, ACPI_RTYPE_INTEGER}}, - {{"_SBS", 0, ACPI_RTYPE_INTEGER}}, - {{"_SCP", 0x13, 0}}, /* Acpi 1.0 allowed 1 arg. Acpi 3.0 expanded to 3 args. Allow both. */ - /* Note: the 3-arg definition may be removed for ACPI 4.0 */ - {{"_SDD", 1, 0}}, - {{"_SEG", 0, ACPI_RTYPE_INTEGER}}, - {{"_SHL", 1, ACPI_RTYPE_INTEGER}}, - {{"_SLI", 0, ACPI_RTYPE_BUFFER}}, - {{"_SPD", 1, ACPI_RTYPE_INTEGER}}, - {{"_SRS", 1, 0}}, - {{"_SRT", 1, ACPI_RTYPE_INTEGER}}, - {{"_SRV", 0, ACPI_RTYPE_INTEGER}}, /* See IPMI spec */ - {{"_SST", 1, 0}}, - {{"_STA", 0, ACPI_RTYPE_INTEGER}}, - {{"_STM", 3, 0}}, - {{"_STP", 2, ACPI_RTYPE_INTEGER}}, - {{"_STR", 0, ACPI_RTYPE_BUFFER}}, - {{"_STV", 2, ACPI_RTYPE_INTEGER}}, - {{"_SUB", 0, ACPI_RTYPE_STRING}}, - {{"_SUN", 0, ACPI_RTYPE_INTEGER}}, - {{"_SWS", 0, ACPI_RTYPE_INTEGER}}, - {{"_TC1", 0, ACPI_RTYPE_INTEGER}}, - {{"_TC2", 0, ACPI_RTYPE_INTEGER}}, - {{"_TDL", 0, ACPI_RTYPE_INTEGER}}, - {{"_TIP", 1, ACPI_RTYPE_INTEGER}}, - {{"_TIV", 1, ACPI_RTYPE_INTEGER}}, - {{"_TMP", 0, ACPI_RTYPE_INTEGER}}, - {{"_TPC", 0, ACPI_RTYPE_INTEGER}}, - {{"_TPT", 1, 0}}, - {{"_TRT", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each 2 Ref/6 Int */ - {{{ACPI_PTYPE2, ACPI_RTYPE_REFERENCE, 2, ACPI_RTYPE_INTEGER}, 6, 0}}, - - {{"_TSD", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each 5 Int with count */ - {{{ACPI_PTYPE2_COUNT,ACPI_RTYPE_INTEGER, 5,0}, 0,0}}, - - {{"_TSP", 0, ACPI_RTYPE_INTEGER}}, - {{"_TSS", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Pkgs) each 5 Int */ - {{{ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 5,0}, 0,0}}, - - {{"_TST", 0, ACPI_RTYPE_INTEGER}}, - {{"_TTS", 1, 0}}, - {{"_TZD", 0, ACPI_RTYPE_PACKAGE}}, /* Variable-length (Refs) */ - {{{ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0,0}, 0,0}}, - - {{"_TZM", 0, ACPI_RTYPE_REFERENCE}}, - {{"_TZP", 0, ACPI_RTYPE_INTEGER}}, - {{"_UID", 0, ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING}}, - {{"_UPC", 0, ACPI_RTYPE_PACKAGE}}, /* Fixed-length (4 Int) */ - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4,0}, 0,0}}, - - {{"_UPD", 0, ACPI_RTYPE_INTEGER}}, - {{"_UPP", 0, ACPI_RTYPE_INTEGER}}, - {{"_VPO", 0, ACPI_RTYPE_INTEGER}}, + {{"_S0_", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1, 0, 0, 0), + + {{"_S1_", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1, 0, 0, 0), + + {{"_S2_", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1, 0, 0, 0), + + {{"_S3_", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1, 0, 0, 0), + + {{"_S4_", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1, 0, 0, 0), + + {{"_S5_", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (1 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_INTEGER, 1, 0, 0, 0), + + {{"_S1D", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_S2D", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_S3D", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_S4D", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_S0W", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_S1W", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_S2W", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_S3W", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_S4W", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_SBS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_SCP", METHOD_1ARGS(ACPI_TYPE_INTEGER) | ARG_COUNT_IS_MINIMUM, + METHOD_NO_RETURN_VALUE}}, /* Acpi 1.0 allowed 1 integer arg. Acpi 3.0 expanded to 3 args. Allow both. */ + + {{"_SDD", METHOD_1ARGS(ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_SEG", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_SHL", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_SLI", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_SPD", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_SRS", METHOD_1ARGS(ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_SRT", METHOD_1ARGS(ACPI_TYPE_BUFFER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_SRV", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, /* See IPMI spec */ + + {{"_SST", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_STA", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_STM", + METHOD_3ARGS(ACPI_TYPE_BUFFER, ACPI_TYPE_BUFFER, ACPI_TYPE_BUFFER), + METHOD_NO_RETURN_VALUE}}, + + {{"_STP", METHOD_2ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_STR", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_STV", METHOD_2ARGS(ACPI_TYPE_INTEGER, ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_SUB", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_STRING)}}, + + {{"_SUN", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_SWS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TC1", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TC2", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TDL", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TIP", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TIV", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TMP", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TPC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TPT", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_TRT", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each 2 Ref/6 Int */ + PACKAGE_INFO(ACPI_PTYPE2, ACPI_RTYPE_REFERENCE, 2, ACPI_RTYPE_INTEGER, + 6, 0), + + {{"_TSD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each 5 Int with count */ + PACKAGE_INFO(ACPI_PTYPE2_COUNT, ACPI_RTYPE_INTEGER, 5, 0, 0, 0), + + {{"_TSP", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TSS", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Pkgs) each 5 Int */ + PACKAGE_INFO(ACPI_PTYPE2, ACPI_RTYPE_INTEGER, 5, 0, 0, 0), + + {{"_TST", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_TTS", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_NO_RETURN_VALUE}}, + + {{"_TZD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Variable-length (Refs) */ + PACKAGE_INFO(ACPI_PTYPE1_VAR, ACPI_RTYPE_REFERENCE, 0, 0, 0, 0), + + {{"_TZM", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_REFERENCE)}}, + + {{"_TZP", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_UID", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING)}}, + + {{"_UPC", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_PACKAGE)}}, /* Fixed-length (4 Int) */ + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 4, 0, 0, 0), + + {{"_UPD", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_UPP", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, + + {{"_VPO", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_INTEGER)}}, /* Acpi 1.0 defined _WAK with no return value. Later, it was changed to return a package */ - {{"_WAK", 1, - ACPI_RTYPE_NONE | ACPI_RTYPE_INTEGER | ACPI_RTYPE_PACKAGE}}, - {{{ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 2,0}, 0,0}}, /* Fixed-length (2 Int), but is optional */ + {{"_WAK", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_NONE | ACPI_RTYPE_INTEGER | + ACPI_RTYPE_PACKAGE)}}, + PACKAGE_INFO(ACPI_PTYPE1_FIXED, ACPI_RTYPE_INTEGER, 2, 0, 0, 0), /* Fixed-length (2 Int), but is optional */ /* _WDG/_WED are MS extensions defined by "Windows Instrumentation" */ - {{"_WDG", 0, ACPI_RTYPE_BUFFER}}, - {{"_WED", 1, - ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | ACPI_RTYPE_BUFFER}}, + {{"_WDG", METHOD_0ARGS, + METHOD_RETURNS(ACPI_RTYPE_BUFFER)}}, + + {{"_WED", METHOD_1ARGS(ACPI_TYPE_INTEGER), + METHOD_RETURNS(ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | + ACPI_RTYPE_BUFFER)}}, - {{{0, 0, 0, 0}, 0, 0}} /* Table terminator */ + PACKAGE_INFO(0, 0, 0, 0, 0, 0) /* Table terminator */ }; +#else +extern const union acpi_predefined_info acpi_gbl_predefined_methods[]; +#endif -#if 0 +#if (defined ACPI_CREATE_RESOURCE_TABLE && defined ACPI_APPLICATION) +/****************************************************************************** + * + * Predefined names for use in Resource Descriptors. These names do not + * appear in the global Predefined Name table (since these names never + * appear in actual AML byte code, only in the original ASL) + * + * Note: Used by iASL compiler and acpi_help utility only. + * + *****************************************************************************/ - /* This is an internally implemented control method, no need to check */ -{ { -"_OSI", 1, ACPI_RTYPE_INTEGER}}, +const union acpi_predefined_info acpi_gbl_resource_names[] = { + {{"_ADR", WIDTH_16 | WIDTH_64, 0}}, + {{"_ALN", WIDTH_8 | WIDTH_16 | WIDTH_32, 0}}, + {{"_ASI", WIDTH_8, 0}}, + {{"_ASZ", WIDTH_8, 0}}, + {{"_ATT", WIDTH_64, 0}}, + {{"_BAS", WIDTH_16 | WIDTH_32, 0}}, + {{"_BM_", WIDTH_1, 0}}, + {{"_DBT", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_DEC", WIDTH_1, 0}}, + {{"_DMA", WIDTH_8, 0}}, + {{"_DPL", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_DRS", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_END", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_FLC", WIDTH_2, 0}}, /* Acpi 5.0 */ + {{"_GRA", WIDTH_ADDRESS, 0}}, + {{"_HE_", WIDTH_1, 0}}, + {{"_INT", WIDTH_16 | WIDTH_32, 0}}, + {{"_IOR", WIDTH_2, 0}}, /* Acpi 5.0 */ + {{"_LEN", WIDTH_8 | WIDTH_ADDRESS, 0}}, + {{"_LIN", WIDTH_8, 0}}, /* Acpi 5.0 */ + {{"_LL_", WIDTH_1, 0}}, + {{"_MAF", WIDTH_1, 0}}, + {{"_MAX", WIDTH_ADDRESS, 0}}, + {{"_MEM", WIDTH_2, 0}}, + {{"_MIF", WIDTH_1, 0}}, + {{"_MIN", WIDTH_ADDRESS, 0}}, + {{"_MOD", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_MTP", WIDTH_2, 0}}, + {{"_PAR", WIDTH_8, 0}}, /* Acpi 5.0 */ + {{"_PHA", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_PIN", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_PPI", WIDTH_8, 0}}, /* Acpi 5.0 */ + {{"_POL", WIDTH_1 | WIDTH_2, 0}}, /* Acpi 5.0 */ + {{"_RBO", WIDTH_8, 0}}, + {{"_RBW", WIDTH_8, 0}}, + {{"_RNG", WIDTH_1, 0}}, + {{"_RT_", WIDTH_8, 0}}, /* Acpi 3.0 */ + {{"_RW_", WIDTH_1, 0}}, + {{"_RXL", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_SHR", WIDTH_2, 0}}, + {{"_SIZ", WIDTH_2, 0}}, + {{"_SLV", WIDTH_1, 0}}, /* Acpi 5.0 */ + {{"_SPE", WIDTH_32, 0}}, /* Acpi 5.0 */ + {{"_STB", WIDTH_2, 0}}, /* Acpi 5.0 */ + {{"_TRA", WIDTH_ADDRESS, 0}}, + {{"_TRS", WIDTH_1, 0}}, + {{"_TSF", WIDTH_8, 0}}, /* Acpi 3.0 */ + {{"_TTP", WIDTH_1, 0}}, + {{"_TXL", WIDTH_16, 0}}, /* Acpi 5.0 */ + {{"_TYP", WIDTH_2 | WIDTH_16, 0}}, + {{"_VEN", VARIABLE_DATA, 0}}, /* Acpi 5.0 */ + PACKAGE_INFO(0, 0, 0, 0, 0, 0) /* Table terminator */ +}; - /* TBD: */ - _PRT - currently ignore reversed entries. attempt to fix here? - think about possibly fixing package elements like _BIF, etc. +static const union acpi_predefined_info acpi_gbl_scope_names[] = { + {{"_GPE", 0, 0}}, + {{"_PR_", 0, 0}}, + {{"_SB_", 0, 0}}, + {{"_SI_", 0, 0}}, + {{"_TZ_", 0, 0}}, + PACKAGE_INFO(0, 0, 0, 0, 0, 0) /* Table terminator */ +}; +#else +extern const union acpi_predefined_info acpi_gbl_resource_names[]; #endif #endif -#endif diff --git a/drivers/acpi/acpica/acutils.h b/drivers/acpi/acpica/acutils.h index 0082fa0a613..202f4f12d3e 100644 --- a/drivers/acpi/acpica/acutils.h +++ b/drivers/acpi/acpica/acutils.h @@ -113,9 +113,10 @@ struct acpi_pkg_info { u32 num_packages; }; +/* Object reference counts */ + #define REF_INCREMENT (u16) 0 #define REF_DECREMENT (u16) 1 -#define REF_FORCE_DELETE (u16) 2 /* acpi_ut_dump_buffer */ @@ -421,7 +422,7 @@ acpi_ut_get_object_size(union acpi_operand_object *obj, acpi_size * obj_length); */ acpi_status acpi_ut_initialize_interfaces(void); -void acpi_ut_interface_terminate(void); +acpi_status acpi_ut_interface_terminate(void); acpi_status acpi_ut_install_interface(acpi_string interface_name); @@ -432,6 +433,26 @@ struct acpi_interface_info *acpi_ut_get_interface(acpi_string interface_name); acpi_status acpi_ut_osi_implementation(struct acpi_walk_state *walk_state); /* + * utpredef - support for predefined names + */ +const union acpi_predefined_info *acpi_ut_get_next_predefined_method(const union + acpi_predefined_info + *this_name); + +const union acpi_predefined_info *acpi_ut_match_predefined_method(char *name); + +const union acpi_predefined_info *acpi_ut_match_resource_name(char *name); + +void +acpi_ut_display_predefined_method(char *buffer, + const union acpi_predefined_info *this_name, + u8 multi_line); + +void acpi_ut_get_expected_return_types(char *buffer, u32 expected_btypes); + +u32 acpi_ut_get_resource_bit_width(char *buffer, u16 types); + +/* * utstate - Generic state creation/cache routines */ void @@ -483,7 +504,8 @@ acpi_ut_short_divide(u64 in_dividend, /* * utmisc */ -const char *acpi_ut_validate_exception(acpi_status status); +const struct acpi_exception_info *acpi_ut_validate_exception(acpi_status + status); u8 acpi_ut_is_pci_root_bridge(char *id); diff --git a/drivers/acpi/acpica/dsutils.c b/drivers/acpi/acpica/dsutils.c index 4d8c992a51d..99778997c35 100644 --- a/drivers/acpi/acpica/dsutils.c +++ b/drivers/acpi/acpica/dsutils.c @@ -178,7 +178,7 @@ acpi_ds_is_result_used(union acpi_parse_object * op, if (!op) { ACPI_ERROR((AE_INFO, "Null Op")); - return_VALUE(TRUE); + return_UINT8(TRUE); } /* @@ -210,7 +210,7 @@ acpi_ds_is_result_used(union acpi_parse_object * op, "At Method level, result of [%s] not used\n", acpi_ps_get_opcode_name(op->common. aml_opcode))); - return_VALUE(FALSE); + return_UINT8(FALSE); } /* Get info on the parent. The root_op is AML_SCOPE */ @@ -219,7 +219,7 @@ acpi_ds_is_result_used(union acpi_parse_object * op, acpi_ps_get_opcode_info(op->common.parent->common.aml_opcode); if (parent_info->class == AML_CLASS_UNKNOWN) { ACPI_ERROR((AE_INFO, "Unknown parent opcode Op=%p", op)); - return_VALUE(FALSE); + return_UINT8(FALSE); } /* @@ -307,7 +307,7 @@ acpi_ds_is_result_used(union acpi_parse_object * op, acpi_ps_get_opcode_name(op->common.parent->common. aml_opcode), op)); - return_VALUE(TRUE); + return_UINT8(TRUE); result_not_used: ACPI_DEBUG_PRINT((ACPI_DB_DISPATCH, @@ -316,7 +316,7 @@ acpi_ds_is_result_used(union acpi_parse_object * op, acpi_ps_get_opcode_name(op->common.parent->common. aml_opcode), op)); - return_VALUE(FALSE); + return_UINT8(FALSE); } /******************************************************************************* diff --git a/drivers/acpi/acpica/dswexec.c b/drivers/acpi/acpica/dswexec.c index 44f8325c2ba..e2199a94747 100644 --- a/drivers/acpi/acpica/dswexec.c +++ b/drivers/acpi/acpica/dswexec.c @@ -693,7 +693,7 @@ acpi_status acpi_ds_exec_end_op(struct acpi_walk_state *walk_state) default: ACPI_ERROR((AE_INFO, - "Unimplemented opcode, class=0x%X type=0x%X Opcode=-0x%X Op=%p", + "Unimplemented opcode, class=0x%X type=0x%X Opcode=0x%X Op=%p", op_class, op_type, op->common.aml_opcode, op)); diff --git a/drivers/acpi/acpica/evevent.c b/drivers/acpi/acpica/evevent.c index b8ea0b26cde..83cd45f4a87 100644 --- a/drivers/acpi/acpica/evevent.c +++ b/drivers/acpi/acpica/evevent.c @@ -257,6 +257,8 @@ u32 acpi_ev_fixed_event_detect(void) * * DESCRIPTION: Clears the status bit for the requested event, calls the * handler that previously registered for the event. + * NOTE: If there is no handler for the event, the event is + * disabled to prevent further interrupts. * ******************************************************************************/ @@ -271,17 +273,17 @@ static u32 acpi_ev_fixed_event_dispatch(u32 event) status_register_id, ACPI_CLEAR_STATUS); /* - * Make sure we've got a handler. If not, report an error. The event is - * disabled to prevent further interrupts. + * Make sure that a handler exists. If not, report an error + * and disable the event to prevent further interrupts. */ - if (NULL == acpi_gbl_fixed_event_handlers[event].handler) { + if (!acpi_gbl_fixed_event_handlers[event].handler) { (void)acpi_write_bit_register(acpi_gbl_fixed_event_info[event]. enable_register_id, ACPI_DISABLE_EVENT); ACPI_ERROR((AE_INFO, - "No installed handler for fixed event [0x%08X]", - event)); + "No installed handler for fixed event - %s (%u), disabling", + acpi_ut_get_event_name(event), event)); return (ACPI_INTERRUPT_NOT_HANDLED); } diff --git a/drivers/acpi/acpica/evgpe.c b/drivers/acpi/acpica/evgpe.c index b9adb9a7ed8..a493b528f8f 100644 --- a/drivers/acpi/acpica/evgpe.c +++ b/drivers/acpi/acpica/evgpe.c @@ -707,7 +707,7 @@ acpi_ev_gpe_dispatch(struct acpi_namespace_node *gpe_device, if (ACPI_FAILURE(status)) { ACPI_EXCEPTION((AE_INFO, status, "Unable to clear GPE%02X", gpe_number)); - return_VALUE(ACPI_INTERRUPT_NOT_HANDLED); + return_UINT32(ACPI_INTERRUPT_NOT_HANDLED); } } @@ -724,7 +724,7 @@ acpi_ev_gpe_dispatch(struct acpi_namespace_node *gpe_device, if (ACPI_FAILURE(status)) { ACPI_EXCEPTION((AE_INFO, status, "Unable to disable GPE%02X", gpe_number)); - return_VALUE(ACPI_INTERRUPT_NOT_HANDLED); + return_UINT32(ACPI_INTERRUPT_NOT_HANDLED); } /* @@ -784,7 +784,7 @@ acpi_ev_gpe_dispatch(struct acpi_namespace_node *gpe_device, break; } - return_VALUE(ACPI_INTERRUPT_HANDLED); + return_UINT32(ACPI_INTERRUPT_HANDLED); } #endif /* !ACPI_REDUCED_HARDWARE */ diff --git a/drivers/acpi/acpica/evsci.c b/drivers/acpi/acpica/evsci.c index f4b43bede01..b905acf7aac 100644 --- a/drivers/acpi/acpica/evsci.c +++ b/drivers/acpi/acpica/evsci.c @@ -89,7 +89,7 @@ static u32 ACPI_SYSTEM_XFACE acpi_ev_sci_xrupt_handler(void *context) */ interrupt_handled |= acpi_ev_gpe_detect(gpe_xrupt_list); - return_VALUE(interrupt_handled); + return_UINT32(interrupt_handled); } /******************************************************************************* @@ -120,7 +120,7 @@ u32 ACPI_SYSTEM_XFACE acpi_ev_gpe_xrupt_handler(void *context) interrupt_handled |= acpi_ev_gpe_detect(gpe_xrupt_list); - return_VALUE(interrupt_handled); + return_UINT32(interrupt_handled); } /****************************************************************************** diff --git a/drivers/acpi/acpica/evxface.c b/drivers/acpi/acpica/evxface.c index ddffd684791..ca5fba99c33 100644 --- a/drivers/acpi/acpica/evxface.c +++ b/drivers/acpi/acpica/evxface.c @@ -467,9 +467,9 @@ acpi_install_fixed_event_handler(u32 event, return_ACPI_STATUS(status); } - /* Don't allow two handlers. */ + /* Do not allow multiple handlers */ - if (NULL != acpi_gbl_fixed_event_handlers[event].handler) { + if (acpi_gbl_fixed_event_handlers[event].handler) { status = AE_ALREADY_EXISTS; goto cleanup; } @@ -483,8 +483,9 @@ acpi_install_fixed_event_handler(u32 event, if (ACPI_SUCCESS(status)) status = acpi_enable_event(event, 0); if (ACPI_FAILURE(status)) { - ACPI_WARNING((AE_INFO, "Could not enable fixed event 0x%X", - event)); + ACPI_WARNING((AE_INFO, + "Could not enable fixed event - %s (%u)", + acpi_ut_get_event_name(event), event)); /* Remove the handler */ @@ -492,7 +493,8 @@ acpi_install_fixed_event_handler(u32 event, acpi_gbl_fixed_event_handlers[event].context = NULL; } else { ACPI_DEBUG_PRINT((ACPI_DB_INFO, - "Enabled fixed event %X, Handler=%p\n", event, + "Enabled fixed event %s (%X), Handler=%p\n", + acpi_ut_get_event_name(event), event, handler)); } @@ -544,11 +546,12 @@ acpi_remove_fixed_event_handler(u32 event, acpi_event_handler handler) if (ACPI_FAILURE(status)) { ACPI_WARNING((AE_INFO, - "Could not write to fixed event enable register 0x%X", - event)); + "Could not disable fixed event - %s (%u)", + acpi_ut_get_event_name(event), event)); } else { - ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Disabled fixed event %X\n", - event)); + ACPI_DEBUG_PRINT((ACPI_DB_INFO, + "Disabled fixed event - %s (%X)\n", + acpi_ut_get_event_name(event), event)); } (void)acpi_ut_release_mutex(ACPI_MTX_EVENTS); diff --git a/drivers/acpi/acpica/evxfevnt.c b/drivers/acpi/acpica/evxfevnt.c index d6e4e42316d..7039606a0ba 100644 --- a/drivers/acpi/acpica/evxfevnt.c +++ b/drivers/acpi/acpica/evxfevnt.c @@ -74,6 +74,12 @@ acpi_status acpi_enable(void) return_ACPI_STATUS(AE_NO_ACPI_TABLES); } + /* If the Hardware Reduced flag is set, machine is always in acpi mode */ + + if (acpi_gbl_reduced_hardware) { + return_ACPI_STATUS(AE_OK); + } + /* Check current mode */ if (acpi_hw_get_mode() == ACPI_SYS_MODE_ACPI) { @@ -126,6 +132,12 @@ acpi_status acpi_disable(void) ACPI_FUNCTION_TRACE(acpi_disable); + /* If the Hardware Reduced flag is set, machine is always in acpi mode */ + + if (acpi_gbl_reduced_hardware) { + return_ACPI_STATUS(AE_OK); + } + if (acpi_hw_get_mode() == ACPI_SYS_MODE_LEGACY) { ACPI_DEBUG_PRINT((ACPI_DB_INIT, "System is already in legacy (non-ACPI) mode\n")); diff --git a/drivers/acpi/acpica/exoparg2.c b/drivers/acpi/acpica/exoparg2.c index e491e46f17d..b0838a4ea53 100644 --- a/drivers/acpi/acpica/exoparg2.c +++ b/drivers/acpi/acpica/exoparg2.c @@ -257,7 +257,7 @@ acpi_status acpi_ex_opcode_2A_1T_1R(struct acpi_walk_state *walk_state) union acpi_operand_object *return_desc = NULL; u64 index; acpi_status status = AE_OK; - acpi_size length; + acpi_size length = 0; ACPI_FUNCTION_TRACE_STR(ex_opcode_2A_1T_1R, acpi_ps_get_opcode_name(walk_state->opcode)); @@ -320,7 +320,6 @@ acpi_status acpi_ex_opcode_2A_1T_1R(struct acpi_walk_state *walk_state) * NOTE: A length of zero is ok, and will create a zero-length, null * terminated string. */ - length = 0; while ((length < operand[0]->buffer.length) && (length < operand[1]->integer.value) && (operand[0]->buffer.pointer[length])) { @@ -376,6 +375,7 @@ acpi_status acpi_ex_opcode_2A_1T_1R(struct acpi_walk_state *walk_state) case ACPI_TYPE_STRING: if (index >= operand[0]->string.length) { + length = operand[0]->string.length; status = AE_AML_STRING_LIMIT; } @@ -386,6 +386,7 @@ acpi_status acpi_ex_opcode_2A_1T_1R(struct acpi_walk_state *walk_state) case ACPI_TYPE_BUFFER: if (index >= operand[0]->buffer.length) { + length = operand[0]->buffer.length; status = AE_AML_BUFFER_LIMIT; } @@ -396,6 +397,7 @@ acpi_status acpi_ex_opcode_2A_1T_1R(struct acpi_walk_state *walk_state) case ACPI_TYPE_PACKAGE: if (index >= operand[0]->package.count) { + length = operand[0]->package.count; status = AE_AML_PACKAGE_LIMIT; } @@ -414,8 +416,9 @@ acpi_status acpi_ex_opcode_2A_1T_1R(struct acpi_walk_state *walk_state) if (ACPI_FAILURE(status)) { ACPI_EXCEPTION((AE_INFO, status, - "Index (0x%8.8X%8.8X) is beyond end of object", - ACPI_FORMAT_UINT64(index))); + "Index (0x%X%8.8X) is beyond end of object (length 0x%X)", + ACPI_FORMAT_UINT64(index), + (u32)length)); goto cleanup; } diff --git a/drivers/acpi/acpica/exprep.c b/drivers/acpi/acpica/exprep.c index d6eab81f54f..6b728aef2dc 100644 --- a/drivers/acpi/acpica/exprep.c +++ b/drivers/acpi/acpica/exprep.c @@ -276,7 +276,7 @@ acpi_ex_decode_field_access(union acpi_operand_object *obj_desc, /* Invalid field access type */ ACPI_ERROR((AE_INFO, "Unknown field access type 0x%X", access)); - return_VALUE(0); + return_UINT32(0); } if (obj_desc->common.type == ACPI_TYPE_BUFFER_FIELD) { @@ -289,7 +289,7 @@ acpi_ex_decode_field_access(union acpi_operand_object *obj_desc, } *return_byte_alignment = byte_alignment; - return_VALUE(bit_length); + return_UINT32(bit_length); } /******************************************************************************* diff --git a/drivers/acpi/acpica/exutils.c b/drivers/acpi/acpica/exutils.c index b205cbb4b50..99dc7b287d5 100644 --- a/drivers/acpi/acpica/exutils.c +++ b/drivers/acpi/acpica/exutils.c @@ -340,7 +340,7 @@ static u32 acpi_ex_digits_needed(u64 value, u32 base) /* u64 is unsigned, so we don't worry about a '-' prefix */ if (value == 0) { - return_VALUE(1); + return_UINT32(1); } current_value = value; @@ -354,7 +354,7 @@ static u32 acpi_ex_digits_needed(u64 value, u32 base) num_digits++; } - return_VALUE(num_digits); + return_UINT32(num_digits); } /******************************************************************************* diff --git a/drivers/acpi/acpica/hwacpi.c b/drivers/acpi/acpica/hwacpi.c index deb3f61e2bd..579c3a53ac8 100644 --- a/drivers/acpi/acpica/hwacpi.c +++ b/drivers/acpi/acpica/hwacpi.c @@ -66,6 +66,12 @@ acpi_status acpi_hw_set_mode(u32 mode) ACPI_FUNCTION_TRACE(hw_set_mode); + /* If the Hardware Reduced flag is set, machine is always in acpi mode */ + + if (acpi_gbl_reduced_hardware) { + return_ACPI_STATUS(AE_OK); + } + /* * ACPI 2.0 clarified that if SMI_CMD in FADT is zero, * system does not support mode transition. @@ -146,23 +152,29 @@ u32 acpi_hw_get_mode(void) ACPI_FUNCTION_TRACE(hw_get_mode); + /* If the Hardware Reduced flag is set, machine is always in acpi mode */ + + if (acpi_gbl_reduced_hardware) { + return_UINT32(ACPI_SYS_MODE_ACPI); + } + /* * ACPI 2.0 clarified that if SMI_CMD in FADT is zero, * system does not support mode transition. */ if (!acpi_gbl_FADT.smi_command) { - return_VALUE(ACPI_SYS_MODE_ACPI); + return_UINT32(ACPI_SYS_MODE_ACPI); } status = acpi_read_bit_register(ACPI_BITREG_SCI_ENABLE, &value); if (ACPI_FAILURE(status)) { - return_VALUE(ACPI_SYS_MODE_LEGACY); + return_UINT32(ACPI_SYS_MODE_LEGACY); } if (value) { - return_VALUE(ACPI_SYS_MODE_ACPI); + return_UINT32(ACPI_SYS_MODE_ACPI); } else { - return_VALUE(ACPI_SYS_MODE_LEGACY); + return_UINT32(ACPI_SYS_MODE_LEGACY); } } diff --git a/drivers/acpi/acpica/nsconvert.c b/drivers/acpi/acpica/nsconvert.c new file mode 100644 index 00000000000..8f79a9d2d50 --- /dev/null +++ b/drivers/acpi/acpica/nsconvert.c @@ -0,0 +1,443 @@ +/****************************************************************************** + * + * Module Name: nsconvert - Object conversions for objects returned by + * predefined methods + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2013, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#include <acpi/acpi.h> +#include "accommon.h" +#include "acnamesp.h" +#include "acinterp.h" +#include "acpredef.h" +#include "amlresrc.h" + +#define _COMPONENT ACPI_NAMESPACE +ACPI_MODULE_NAME("nsconvert") + +/******************************************************************************* + * + * FUNCTION: acpi_ns_convert_to_integer + * + * PARAMETERS: original_object - Object to be converted + * return_object - Where the new converted object is returned + * + * RETURN: Status. AE_OK if conversion was successful. + * + * DESCRIPTION: Attempt to convert a String/Buffer object to an Integer. + * + ******************************************************************************/ +acpi_status +acpi_ns_convert_to_integer(union acpi_operand_object *original_object, + union acpi_operand_object **return_object) +{ + union acpi_operand_object *new_object; + acpi_status status; + u64 value = 0; + u32 i; + + switch (original_object->common.type) { + case ACPI_TYPE_STRING: + + /* String-to-Integer conversion */ + + status = acpi_ut_strtoul64(original_object->string.pointer, + ACPI_ANY_BASE, &value); + if (ACPI_FAILURE(status)) { + return (status); + } + break; + + case ACPI_TYPE_BUFFER: + + /* Buffer-to-Integer conversion. Max buffer size is 64 bits. */ + + if (original_object->buffer.length > 8) { + return (AE_AML_OPERAND_TYPE); + } + + /* Extract each buffer byte to create the integer */ + + for (i = 0; i < original_object->buffer.length; i++) { + value |= + ((u64)original_object->buffer. + pointer[i] << (i * 8)); + } + break; + + default: + return (AE_AML_OPERAND_TYPE); + } + + new_object = acpi_ut_create_integer_object(value); + if (!new_object) { + return (AE_NO_MEMORY); + } + + *return_object = new_object; + return (AE_OK); +} + +/******************************************************************************* + * + * FUNCTION: acpi_ns_convert_to_string + * + * PARAMETERS: original_object - Object to be converted + * return_object - Where the new converted object is returned + * + * RETURN: Status. AE_OK if conversion was successful. + * + * DESCRIPTION: Attempt to convert a Integer/Buffer object to a String. + * + ******************************************************************************/ + +acpi_status +acpi_ns_convert_to_string(union acpi_operand_object *original_object, + union acpi_operand_object **return_object) +{ + union acpi_operand_object *new_object; + acpi_size length; + acpi_status status; + + switch (original_object->common.type) { + case ACPI_TYPE_INTEGER: + /* + * Integer-to-String conversion. Commonly, convert + * an integer of value 0 to a NULL string. The last element of + * _BIF and _BIX packages occasionally need this fix. + */ + if (original_object->integer.value == 0) { + + /* Allocate a new NULL string object */ + + new_object = acpi_ut_create_string_object(0); + if (!new_object) { + return (AE_NO_MEMORY); + } + } else { + status = + acpi_ex_convert_to_string(original_object, + &new_object, + ACPI_IMPLICIT_CONVERT_HEX); + if (ACPI_FAILURE(status)) { + return (status); + } + } + break; + + case ACPI_TYPE_BUFFER: + /* + * Buffer-to-String conversion. Use a to_string + * conversion, no transform performed on the buffer data. The best + * example of this is the _BIF method, where the string data from + * the battery is often (incorrectly) returned as buffer object(s). + */ + length = 0; + while ((length < original_object->buffer.length) && + (original_object->buffer.pointer[length])) { + length++; + } + + /* Allocate a new string object */ + + new_object = acpi_ut_create_string_object(length); + if (!new_object) { + return (AE_NO_MEMORY); + } + + /* + * Copy the raw buffer data with no transform. String is already NULL + * terminated at Length+1. + */ + ACPI_MEMCPY(new_object->string.pointer, + original_object->buffer.pointer, length); + break; + + default: + return (AE_AML_OPERAND_TYPE); + } + + *return_object = new_object; + return (AE_OK); +} + +/******************************************************************************* + * + * FUNCTION: acpi_ns_convert_to_buffer + * + * PARAMETERS: original_object - Object to be converted + * return_object - Where the new converted object is returned + * + * RETURN: Status. AE_OK if conversion was successful. + * + * DESCRIPTION: Attempt to convert a Integer/String/Package object to a Buffer. + * + ******************************************************************************/ + +acpi_status +acpi_ns_convert_to_buffer(union acpi_operand_object *original_object, + union acpi_operand_object **return_object) +{ + union acpi_operand_object *new_object; + acpi_status status; + union acpi_operand_object **elements; + u32 *dword_buffer; + u32 count; + u32 i; + + switch (original_object->common.type) { + case ACPI_TYPE_INTEGER: + /* + * Integer-to-Buffer conversion. + * Convert the Integer to a packed-byte buffer. _MAT and other + * objects need this sometimes, if a read has been performed on a + * Field object that is less than or equal to the global integer + * size (32 or 64 bits). + */ + status = + acpi_ex_convert_to_buffer(original_object, &new_object); + if (ACPI_FAILURE(status)) { + return (status); + } + break; + + case ACPI_TYPE_STRING: + + /* String-to-Buffer conversion. Simple data copy */ + + new_object = + acpi_ut_create_buffer_object(original_object->string. + length); + if (!new_object) { + return (AE_NO_MEMORY); + } + + ACPI_MEMCPY(new_object->buffer.pointer, + original_object->string.pointer, + original_object->string.length); + break; + + case ACPI_TYPE_PACKAGE: + /* + * This case is often seen for predefined names that must return a + * Buffer object with multiple DWORD integers within. For example, + * _FDE and _GTM. The Package can be converted to a Buffer. + */ + + /* All elements of the Package must be integers */ + + elements = original_object->package.elements; + count = original_object->package.count; + + for (i = 0; i < count; i++) { + if ((!*elements) || + ((*elements)->common.type != ACPI_TYPE_INTEGER)) { + return (AE_AML_OPERAND_TYPE); + } + elements++; + } + + /* Create the new buffer object to replace the Package */ + + new_object = acpi_ut_create_buffer_object(ACPI_MUL_4(count)); + if (!new_object) { + return (AE_NO_MEMORY); + } + + /* Copy the package elements (integers) to the buffer as DWORDs */ + + elements = original_object->package.elements; + dword_buffer = ACPI_CAST_PTR(u32, new_object->buffer.pointer); + + for (i = 0; i < count; i++) { + *dword_buffer = (u32)(*elements)->integer.value; + dword_buffer++; + elements++; + } + break; + + default: + return (AE_AML_OPERAND_TYPE); + } + + *return_object = new_object; + return (AE_OK); +} + +/******************************************************************************* + * + * FUNCTION: acpi_ns_convert_to_unicode + * + * PARAMETERS: original_object - ASCII String Object to be converted + * return_object - Where the new converted object is returned + * + * RETURN: Status. AE_OK if conversion was successful. + * + * DESCRIPTION: Attempt to convert a String object to a Unicode string Buffer. + * + ******************************************************************************/ + +acpi_status +acpi_ns_convert_to_unicode(union acpi_operand_object *original_object, + union acpi_operand_object **return_object) +{ + union acpi_operand_object *new_object; + char *ascii_string; + u16 *unicode_buffer; + u32 unicode_length; + u32 i; + + if (!original_object) { + return (AE_OK); + } + + /* If a Buffer was returned, it must be at least two bytes long */ + + if (original_object->common.type == ACPI_TYPE_BUFFER) { + if (original_object->buffer.length < 2) { + return (AE_AML_OPERAND_VALUE); + } + + *return_object = NULL; + return (AE_OK); + } + + /* + * The original object is an ASCII string. Convert this string to + * a unicode buffer. + */ + ascii_string = original_object->string.pointer; + unicode_length = (original_object->string.length * 2) + 2; + + /* Create a new buffer object for the Unicode data */ + + new_object = acpi_ut_create_buffer_object(unicode_length); + if (!new_object) { + return (AE_NO_MEMORY); + } + + unicode_buffer = ACPI_CAST_PTR(u16, new_object->buffer.pointer); + + /* Convert ASCII to Unicode */ + + for (i = 0; i < original_object->string.length; i++) { + unicode_buffer[i] = (u16)ascii_string[i]; + } + + *return_object = new_object; + return (AE_OK); +} + +/******************************************************************************* + * + * FUNCTION: acpi_ns_convert_to_resource + * + * PARAMETERS: original_object - Object to be converted + * return_object - Where the new converted object is returned + * + * RETURN: Status. AE_OK if conversion was successful + * + * DESCRIPTION: Attempt to convert a Integer object to a resource_template + * Buffer. + * + ******************************************************************************/ + +acpi_status +acpi_ns_convert_to_resource(union acpi_operand_object *original_object, + union acpi_operand_object **return_object) +{ + union acpi_operand_object *new_object; + u8 *buffer; + + /* + * We can fix the following cases for an expected resource template: + * 1. No return value (interpreter slack mode is disabled) + * 2. A "Return (Zero)" statement + * 3. A "Return empty buffer" statement + * + * We will return a buffer containing a single end_tag + * resource descriptor. + */ + if (original_object) { + switch (original_object->common.type) { + case ACPI_TYPE_INTEGER: + + /* We can only repair an Integer==0 */ + + if (original_object->integer.value) { + return (AE_AML_OPERAND_TYPE); + } + break; + + case ACPI_TYPE_BUFFER: + + if (original_object->buffer.length) { + + /* Additional checks can be added in the future */ + + *return_object = NULL; + return (AE_OK); + } + break; + + case ACPI_TYPE_STRING: + default: + + return (AE_AML_OPERAND_TYPE); + } + } + + /* Create the new buffer object for the resource descriptor */ + + new_object = acpi_ut_create_buffer_object(2); + if (!new_object) { + return (AE_NO_MEMORY); + } + + buffer = ACPI_CAST_PTR(u8, new_object->buffer.pointer); + + /* Initialize the Buffer with a single end_tag descriptor */ + + buffer[0] = (ACPI_RESOURCE_NAME_END_TAG | ASL_RDESC_END_TAG_SIZE); + buffer[1] = 0x00; + + *return_object = new_object; + return (AE_OK); +} diff --git a/drivers/acpi/acpica/nseval.c b/drivers/acpi/acpica/nseval.c index 1538f3eb2a8..b61db69d567 100644 --- a/drivers/acpi/acpica/nseval.c +++ b/drivers/acpi/acpica/nseval.c @@ -98,17 +98,21 @@ acpi_status acpi_ns_evaluate(struct acpi_evaluate_info * info) info->return_object = NULL; info->param_count = 0; - /* - * Get the actual namespace node for the target object. Handles these cases: - * - * 1) Null node, Pathname (absolute path) - * 2) Node, Pathname (path relative to Node) - * 3) Node, Null Pathname - */ - status = acpi_ns_get_node(info->prefix_node, info->pathname, - ACPI_NS_NO_UPSEARCH, &info->resolved_node); - if (ACPI_FAILURE(status)) { - return_ACPI_STATUS(status); + if (!info->resolved_node) { + /* + * Get the actual namespace node for the target object if we need to. + * Handles these cases: + * + * 1) Null node, Pathname (absolute path) + * 2) Node, Pathname (path relative to Node) + * 3) Node, Null Pathname + */ + status = acpi_ns_get_node(info->prefix_node, info->pathname, + ACPI_NS_NO_UPSEARCH, + &info->resolved_node); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } } /* diff --git a/drivers/acpi/acpica/nspredef.c b/drivers/acpi/acpica/nspredef.c index 224c3005340..8a52916148c 100644 --- a/drivers/acpi/acpica/nspredef.c +++ b/drivers/acpi/acpica/nspredef.c @@ -76,19 +76,7 @@ static acpi_status acpi_ns_check_reference(struct acpi_predefined_data *data, union acpi_operand_object *return_object); -static void acpi_ns_get_expected_types(char *buffer, u32 expected_btypes); - -/* - * Names for the types that can be returned by the predefined objects. - * Used for warning messages. Must be in the same order as the ACPI_RTYPEs - */ -static const char *acpi_rtype_names[] = { - "/Integer", - "/String", - "/Buffer", - "/Package", - "/Reference", -}; +static u32 acpi_ns_get_bitmapped_type(union acpi_operand_object *return_object); /******************************************************************************* * @@ -112,7 +100,6 @@ acpi_ns_check_predefined_names(struct acpi_namespace_node *node, acpi_status return_status, union acpi_operand_object **return_object_ptr) { - union acpi_operand_object *return_object = *return_object_ptr; acpi_status status = AE_OK; const union acpi_predefined_info *predefined; char *pathname; @@ -120,7 +107,7 @@ acpi_ns_check_predefined_names(struct acpi_namespace_node *node, /* Match the name for this method/object against the predefined list */ - predefined = acpi_ns_check_for_predefined_name(node); + predefined = acpi_ut_match_predefined_method(node->name.ascii); /* Get the full pathname to the object, for use in warning messages */ @@ -152,25 +139,6 @@ acpi_ns_check_predefined_names(struct acpi_namespace_node *node, } /* - * If there is no return value, check if we require a return value for - * this predefined name. Either one return value is expected, or none, - * for both methods and other objects. - * - * Exit now if there is no return object. Warning if one was expected. - */ - if (!return_object) { - if ((predefined->info.expected_btypes) && - (!(predefined->info.expected_btypes & ACPI_RTYPE_NONE))) { - ACPI_WARN_PREDEFINED((AE_INFO, pathname, - ACPI_WARN_ALWAYS, - "Missing expected return value")); - - status = AE_AML_NO_RETURN_VALUE; - } - goto cleanup; - } - - /* * Return value validation and possible repair. * * 1) Don't perform return value validation/repair if this feature @@ -310,8 +278,10 @@ acpi_ns_check_parameter_count(char *pathname, * Validate the user-supplied parameter count. * Allow two different legal argument counts (_SCP, etc.) */ - required_params_current = predefined->info.param_count & 0x0F; - required_params_old = predefined->info.param_count >> 4; + required_params_current = + predefined->info.argument_list & METHOD_ARG_MASK; + required_params_old = + predefined->info.argument_list >> METHOD_ARG_BIT_WIDTH; if (user_param_count != ACPI_UINT32_MAX) { if ((user_param_count != required_params_current) && @@ -340,52 +310,6 @@ acpi_ns_check_parameter_count(char *pathname, /******************************************************************************* * - * FUNCTION: acpi_ns_check_for_predefined_name - * - * PARAMETERS: node - Namespace node for the method/object - * - * RETURN: Pointer to entry in predefined table. NULL indicates not found. - * - * DESCRIPTION: Check an object name against the predefined object list. - * - ******************************************************************************/ - -const union acpi_predefined_info *acpi_ns_check_for_predefined_name(struct - acpi_namespace_node - *node) -{ - const union acpi_predefined_info *this_name; - - /* Quick check for a predefined name, first character must be underscore */ - - if (node->name.ascii[0] != '_') { - return (NULL); - } - - /* Search info table for a predefined method/object name */ - - this_name = predefined_names; - while (this_name->info.name[0]) { - if (ACPI_COMPARE_NAME(node->name.ascii, this_name->info.name)) { - return (this_name); - } - - /* - * Skip next entry in the table if this name returns a Package - * (next entry contains the package info) - */ - if (this_name->info.expected_btypes & ACPI_RTYPE_PACKAGE) { - this_name++; - } - - this_name++; - } - - return (NULL); /* Not found */ -} - -/******************************************************************************* - * * FUNCTION: acpi_ns_check_object_type * * PARAMETERS: data - Pointer to validation data structure @@ -410,28 +334,12 @@ acpi_ns_check_object_type(struct acpi_predefined_data *data, { union acpi_operand_object *return_object = *return_object_ptr; acpi_status status = AE_OK; - u32 return_btype; char type_buffer[48]; /* Room for 5 types */ - /* - * If we get a NULL return_object here, it is a NULL package element. - * Since all extraneous NULL package elements were removed earlier by a - * call to acpi_ns_remove_null_elements, this is an unexpected NULL element. - * We will attempt to repair it. - */ - if (!return_object) { - status = acpi_ns_repair_null_element(data, expected_btypes, - package_index, - return_object_ptr); - if (ACPI_SUCCESS(status)) { - return (AE_OK); /* Repair was successful */ - } - goto type_error_exit; - } - /* A Namespace node should not get here, but make sure */ - if (ACPI_GET_DESCRIPTOR_TYPE(return_object) == ACPI_DESC_TYPE_NAMED) { + if (return_object && + ACPI_GET_DESCRIPTOR_TYPE(return_object) == ACPI_DESC_TYPE_NAMED) { ACPI_WARN_PREDEFINED((AE_INFO, data->pathname, data->node_flags, "Invalid return type - Found a Namespace node [%4.4s] type %s", return_object->node.name.ascii, @@ -448,59 +356,31 @@ acpi_ns_check_object_type(struct acpi_predefined_data *data, * from all of the predefined names (including elements of returned * packages) */ - switch (return_object->common.type) { - case ACPI_TYPE_INTEGER: - return_btype = ACPI_RTYPE_INTEGER; - break; - - case ACPI_TYPE_BUFFER: - return_btype = ACPI_RTYPE_BUFFER; - break; - - case ACPI_TYPE_STRING: - return_btype = ACPI_RTYPE_STRING; - break; + data->return_btype = acpi_ns_get_bitmapped_type(return_object); + if (data->return_btype == ACPI_RTYPE_ANY) { - case ACPI_TYPE_PACKAGE: - return_btype = ACPI_RTYPE_PACKAGE; - break; - - case ACPI_TYPE_LOCAL_REFERENCE: - return_btype = ACPI_RTYPE_REFERENCE; - break; - - default: /* Not one of the supported objects, must be incorrect */ - goto type_error_exit; } - /* Is the object one of the expected types? */ - - if (return_btype & expected_btypes) { - - /* For reference objects, check that the reference type is correct */ - - if (return_object->common.type == ACPI_TYPE_LOCAL_REFERENCE) { - status = acpi_ns_check_reference(data, return_object); - } + /* For reference objects, check that the reference type is correct */ + if ((data->return_btype & expected_btypes) == ACPI_RTYPE_REFERENCE) { + status = acpi_ns_check_reference(data, return_object); return (status); } - /* Type mismatch -- attempt repair of the returned object */ + /* Attempt simple repair of the returned object if necessary */ - status = acpi_ns_repair_object(data, expected_btypes, + status = acpi_ns_simple_repair(data, expected_btypes, package_index, return_object_ptr); - if (ACPI_SUCCESS(status)) { - return (AE_OK); /* Repair was successful */ - } + return (status); type_error_exit: /* Create a string with all expected types for this predefined object */ - acpi_ns_get_expected_types(type_buffer, expected_btypes); + acpi_ut_get_expected_return_types(type_buffer, expected_btypes); if (package_index == ACPI_NOT_PACKAGE_ELEMENT) { ACPI_WARN_PREDEFINED((AE_INFO, data->pathname, data->node_flags, @@ -558,36 +438,55 @@ acpi_ns_check_reference(struct acpi_predefined_data *data, /******************************************************************************* * - * FUNCTION: acpi_ns_get_expected_types + * FUNCTION: acpi_ns_get_bitmapped_type * - * PARAMETERS: buffer - Pointer to where the string is returned - * expected_btypes - Bitmap of expected return type(s) + * PARAMETERS: return_object - Object returned from method/obj evaluation * - * RETURN: Buffer is populated with type names. + * RETURN: Object return type. ACPI_RTYPE_ANY indicates that the object + * type is not supported. ACPI_RTYPE_NONE indicates that no + * object was returned (return_object is NULL). * - * DESCRIPTION: Translate the expected types bitmap into a string of ascii - * names of expected types, for use in warning messages. + * DESCRIPTION: Convert object type into a bitmapped object return type. * ******************************************************************************/ -static void acpi_ns_get_expected_types(char *buffer, u32 expected_btypes) +static u32 acpi_ns_get_bitmapped_type(union acpi_operand_object *return_object) { - u32 this_rtype; - u32 i; - u32 j; + u32 return_btype; - j = 1; - buffer[0] = 0; - this_rtype = ACPI_RTYPE_INTEGER; + if (!return_object) { + return (ACPI_RTYPE_NONE); + } - for (i = 0; i < ACPI_NUM_RTYPES; i++) { + /* Map acpi_object_type to internal bitmapped type */ - /* If one of the expected types, concatenate the name of this type */ + switch (return_object->common.type) { + case ACPI_TYPE_INTEGER: + return_btype = ACPI_RTYPE_INTEGER; + break; - if (expected_btypes & this_rtype) { - ACPI_STRCAT(buffer, &acpi_rtype_names[i][j]); - j = 0; /* Use name separator from now on */ - } - this_rtype <<= 1; /* Next Rtype */ + case ACPI_TYPE_BUFFER: + return_btype = ACPI_RTYPE_BUFFER; + break; + + case ACPI_TYPE_STRING: + return_btype = ACPI_RTYPE_STRING; + break; + + case ACPI_TYPE_PACKAGE: + return_btype = ACPI_RTYPE_PACKAGE; + break; + + case ACPI_TYPE_LOCAL_REFERENCE: + return_btype = ACPI_RTYPE_REFERENCE; + break; + + default: + /* Not one of the supported objects, must be incorrect */ + + return_btype = ACPI_RTYPE_ANY; + break; } + + return (return_btype); } diff --git a/drivers/acpi/acpica/nsprepkg.c b/drivers/acpi/acpica/nsprepkg.c index a40155467d2..77cdd539de1 100644 --- a/drivers/acpi/acpica/nsprepkg.c +++ b/drivers/acpi/acpica/nsprepkg.c @@ -112,9 +112,15 @@ acpi_ns_check_package(struct acpi_predefined_data *data, elements = return_object->package.elements; count = return_object->package.count; - /* The package must have at least one element, else invalid */ - + /* + * Most packages must have at least one element. The only exception + * is the variable-length package (ACPI_PTYPE1_VAR). + */ if (!count) { + if (package->ret_info.type == ACPI_PTYPE1_VAR) { + return (AE_OK); + } + ACPI_WARN_PREDEFINED((AE_INFO, data->pathname, data->node_flags, "Return Package has no elements (empty)")); diff --git a/drivers/acpi/acpica/nsrepair.c b/drivers/acpi/acpica/nsrepair.c index 9e833353c06..18f02e4ece0 100644 --- a/drivers/acpi/acpica/nsrepair.c +++ b/drivers/acpi/acpica/nsrepair.c @@ -46,6 +46,7 @@ #include "acnamesp.h" #include "acinterp.h" #include "acpredef.h" +#include "amlresrc.h" #define _COMPONENT ACPI_NAMESPACE ACPI_MODULE_NAME("nsrepair") @@ -71,6 +72,11 @@ ACPI_MODULE_NAME("nsrepair") * Buffer -> String * Buffer -> Package of Integers * Package -> Package of one Package + * + * Additional conversions that are available: + * Convert a null return or zero return value to an end_tag descriptor + * Convert an ASCII string to a Unicode buffer + * * An incorrect standalone object is wrapped with required outer package * * Additional possible repairs: @@ -78,21 +84,51 @@ ACPI_MODULE_NAME("nsrepair") * ******************************************************************************/ /* Local prototypes */ -static acpi_status -acpi_ns_convert_to_integer(union acpi_operand_object *original_object, - union acpi_operand_object **return_object); - -static acpi_status -acpi_ns_convert_to_string(union acpi_operand_object *original_object, - union acpi_operand_object **return_object); +static const struct acpi_simple_repair_info *acpi_ns_match_simple_repair(struct + acpi_namespace_node + *node, + u32 + return_btype, + u32 + package_index); -static acpi_status -acpi_ns_convert_to_buffer(union acpi_operand_object *original_object, - union acpi_operand_object **return_object); +/* + * Special but simple repairs for some names. + * + * 2nd argument: Unexpected types that can be repaired + */ +static const struct acpi_simple_repair_info acpi_object_repair_info[] = { + /* Resource descriptor conversions */ + + {"_CRS", + ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | ACPI_RTYPE_BUFFER | + ACPI_RTYPE_NONE, + ACPI_NOT_PACKAGE_ELEMENT, + acpi_ns_convert_to_resource}, + {"_DMA", + ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | ACPI_RTYPE_BUFFER | + ACPI_RTYPE_NONE, + ACPI_NOT_PACKAGE_ELEMENT, + acpi_ns_convert_to_resource}, + {"_PRS", + ACPI_RTYPE_INTEGER | ACPI_RTYPE_STRING | ACPI_RTYPE_BUFFER | + ACPI_RTYPE_NONE, + ACPI_NOT_PACKAGE_ELEMENT, + acpi_ns_convert_to_resource}, + + /* Unicode conversions */ + + {"_MLS", ACPI_RTYPE_STRING, 1, + acpi_ns_convert_to_unicode}, + {"_STR", ACPI_RTYPE_STRING | ACPI_RTYPE_BUFFER, + ACPI_NOT_PACKAGE_ELEMENT, + acpi_ns_convert_to_unicode}, + {{0, 0, 0, 0}, 0, 0, NULL} /* Table terminator */ +}; /******************************************************************************* * - * FUNCTION: acpi_ns_repair_object + * FUNCTION: acpi_ns_simple_repair * * PARAMETERS: data - Pointer to validation data structure * expected_btypes - Object types expected @@ -110,16 +146,54 @@ acpi_ns_convert_to_buffer(union acpi_operand_object *original_object, ******************************************************************************/ acpi_status -acpi_ns_repair_object(struct acpi_predefined_data *data, +acpi_ns_simple_repair(struct acpi_predefined_data *data, u32 expected_btypes, u32 package_index, union acpi_operand_object **return_object_ptr) { union acpi_operand_object *return_object = *return_object_ptr; - union acpi_operand_object *new_object; + union acpi_operand_object *new_object = NULL; acpi_status status; + const struct acpi_simple_repair_info *predefined; + + ACPI_FUNCTION_NAME(ns_simple_repair); + + /* + * Special repairs for certain names that are in the repair table. + * Check if this name is in the list of repairable names. + */ + predefined = acpi_ns_match_simple_repair(data->node, + data->return_btype, + package_index); + if (predefined) { + if (!return_object) { + ACPI_WARN_PREDEFINED((AE_INFO, data->pathname, + ACPI_WARN_ALWAYS, + "Missing expected return value")); + } + + status = + predefined->object_converter(return_object, &new_object); + if (ACPI_FAILURE(status)) { + + /* A fatal error occurred during a conversion */ + + ACPI_EXCEPTION((AE_INFO, status, + "During return object analysis")); + return (status); + } + if (new_object) { + goto object_repaired; + } + } - ACPI_FUNCTION_NAME(ns_repair_object); + /* + * Do not perform simple object repair unless the return type is not + * expected. + */ + if (data->return_btype & expected_btypes) { + return (AE_OK); + } /* * At this point, we know that the type of the returned object was not @@ -127,6 +201,24 @@ acpi_ns_repair_object(struct acpi_predefined_data *data, * repair the object by converting it to one of the expected object * types for this predefined name. */ + + /* + * If there is no return value, check if we require a return value for + * this predefined name. Either one return value is expected, or none, + * for both methods and other objects. + * + * Exit now if there is no return object. Warning if one was expected. + */ + if (!return_object) { + if (expected_btypes && (!(expected_btypes & ACPI_RTYPE_NONE))) { + ACPI_WARN_PREDEFINED((AE_INFO, data->pathname, + ACPI_WARN_ALWAYS, + "Missing expected return value")); + + return (AE_AML_NO_RETURN_VALUE); + } + } + if (expected_btypes & ACPI_RTYPE_INTEGER) { status = acpi_ns_convert_to_integer(return_object, &new_object); if (ACPI_SUCCESS(status)) { @@ -216,254 +308,51 @@ acpi_ns_repair_object(struct acpi_predefined_data *data, return (AE_OK); } -/******************************************************************************* - * - * FUNCTION: acpi_ns_convert_to_integer - * - * PARAMETERS: original_object - Object to be converted - * return_object - Where the new converted object is returned - * - * RETURN: Status. AE_OK if conversion was successful. - * - * DESCRIPTION: Attempt to convert a String/Buffer object to an Integer. - * - ******************************************************************************/ - -static acpi_status -acpi_ns_convert_to_integer(union acpi_operand_object *original_object, - union acpi_operand_object **return_object) -{ - union acpi_operand_object *new_object; - acpi_status status; - u64 value = 0; - u32 i; - - switch (original_object->common.type) { - case ACPI_TYPE_STRING: - - /* String-to-Integer conversion */ - - status = acpi_ut_strtoul64(original_object->string.pointer, - ACPI_ANY_BASE, &value); - if (ACPI_FAILURE(status)) { - return (status); - } - break; - - case ACPI_TYPE_BUFFER: - - /* Buffer-to-Integer conversion. Max buffer size is 64 bits. */ - - if (original_object->buffer.length > 8) { - return (AE_AML_OPERAND_TYPE); - } - - /* Extract each buffer byte to create the integer */ - - for (i = 0; i < original_object->buffer.length; i++) { - value |= - ((u64) original_object->buffer. - pointer[i] << (i * 8)); - } - break; - - default: - return (AE_AML_OPERAND_TYPE); - } - - new_object = acpi_ut_create_integer_object(value); - if (!new_object) { - return (AE_NO_MEMORY); - } - - *return_object = new_object; - return (AE_OK); -} - -/******************************************************************************* - * - * FUNCTION: acpi_ns_convert_to_string - * - * PARAMETERS: original_object - Object to be converted - * return_object - Where the new converted object is returned - * - * RETURN: Status. AE_OK if conversion was successful. - * - * DESCRIPTION: Attempt to convert a Integer/Buffer object to a String. - * - ******************************************************************************/ - -static acpi_status -acpi_ns_convert_to_string(union acpi_operand_object *original_object, - union acpi_operand_object **return_object) -{ - union acpi_operand_object *new_object; - acpi_size length; - acpi_status status; - - switch (original_object->common.type) { - case ACPI_TYPE_INTEGER: - /* - * Integer-to-String conversion. Commonly, convert - * an integer of value 0 to a NULL string. The last element of - * _BIF and _BIX packages occasionally need this fix. - */ - if (original_object->integer.value == 0) { - - /* Allocate a new NULL string object */ - - new_object = acpi_ut_create_string_object(0); - if (!new_object) { - return (AE_NO_MEMORY); - } - } else { - status = - acpi_ex_convert_to_string(original_object, - &new_object, - ACPI_IMPLICIT_CONVERT_HEX); - if (ACPI_FAILURE(status)) { - return (status); - } - } - break; - - case ACPI_TYPE_BUFFER: - /* - * Buffer-to-String conversion. Use a to_string - * conversion, no transform performed on the buffer data. The best - * example of this is the _BIF method, where the string data from - * the battery is often (incorrectly) returned as buffer object(s). - */ - length = 0; - while ((length < original_object->buffer.length) && - (original_object->buffer.pointer[length])) { - length++; - } - - /* Allocate a new string object */ - - new_object = acpi_ut_create_string_object(length); - if (!new_object) { - return (AE_NO_MEMORY); - } - - /* - * Copy the raw buffer data with no transform. String is already NULL - * terminated at Length+1. - */ - ACPI_MEMCPY(new_object->string.pointer, - original_object->buffer.pointer, length); - break; - - default: - return (AE_AML_OPERAND_TYPE); - } - - *return_object = new_object; - return (AE_OK); -} - -/******************************************************************************* +/****************************************************************************** * - * FUNCTION: acpi_ns_convert_to_buffer + * FUNCTION: acpi_ns_match_simple_repair * - * PARAMETERS: original_object - Object to be converted - * return_object - Where the new converted object is returned + * PARAMETERS: node - Namespace node for the method/object + * return_btype - Object type that was returned + * package_index - Index of object within parent package (if + * applicable - ACPI_NOT_PACKAGE_ELEMENT + * otherwise) * - * RETURN: Status. AE_OK if conversion was successful. + * RETURN: Pointer to entry in repair table. NULL indicates not found. * - * DESCRIPTION: Attempt to convert a Integer/String/Package object to a Buffer. + * DESCRIPTION: Check an object name against the repairable object list. * - ******************************************************************************/ + *****************************************************************************/ -static acpi_status -acpi_ns_convert_to_buffer(union acpi_operand_object *original_object, - union acpi_operand_object **return_object) +static const struct acpi_simple_repair_info *acpi_ns_match_simple_repair(struct + acpi_namespace_node + *node, + u32 + return_btype, + u32 + package_index) { - union acpi_operand_object *new_object; - acpi_status status; - union acpi_operand_object **elements; - u32 *dword_buffer; - u32 count; - u32 i; + const struct acpi_simple_repair_info *this_name; - switch (original_object->common.type) { - case ACPI_TYPE_INTEGER: - /* - * Integer-to-Buffer conversion. - * Convert the Integer to a packed-byte buffer. _MAT and other - * objects need this sometimes, if a read has been performed on a - * Field object that is less than or equal to the global integer - * size (32 or 64 bits). - */ - status = - acpi_ex_convert_to_buffer(original_object, &new_object); - if (ACPI_FAILURE(status)) { - return (status); - } - break; + /* Search info table for a repairable predefined method/object name */ - case ACPI_TYPE_STRING: + this_name = acpi_object_repair_info; + while (this_name->object_converter) { + if (ACPI_COMPARE_NAME(node->name.ascii, this_name->name)) { - /* String-to-Buffer conversion. Simple data copy */ - - new_object = - acpi_ut_create_buffer_object(original_object->string. - length); - if (!new_object) { - return (AE_NO_MEMORY); - } + /* Check if we can actually repair this name/type combination */ - ACPI_MEMCPY(new_object->buffer.pointer, - original_object->string.pointer, - original_object->string.length); - break; - - case ACPI_TYPE_PACKAGE: - /* - * This case is often seen for predefined names that must return a - * Buffer object with multiple DWORD integers within. For example, - * _FDE and _GTM. The Package can be converted to a Buffer. - */ - - /* All elements of the Package must be integers */ - - elements = original_object->package.elements; - count = original_object->package.count; - - for (i = 0; i < count; i++) { - if ((!*elements) || - ((*elements)->common.type != ACPI_TYPE_INTEGER)) { - return (AE_AML_OPERAND_TYPE); + if ((return_btype & this_name->unexpected_btypes) && + (package_index == this_name->package_index)) { + return (this_name); } - elements++; - } - - /* Create the new buffer object to replace the Package */ - new_object = acpi_ut_create_buffer_object(ACPI_MUL_4(count)); - if (!new_object) { - return (AE_NO_MEMORY); + return (NULL); } - - /* Copy the package elements (integers) to the buffer as DWORDs */ - - elements = original_object->package.elements; - dword_buffer = ACPI_CAST_PTR(u32, new_object->buffer.pointer); - - for (i = 0; i < count; i++) { - *dword_buffer = (u32) (*elements)->integer.value; - dword_buffer++; - elements++; - } - break; - - default: - return (AE_AML_OPERAND_TYPE); + this_name++; } - *return_object = new_object; - return (AE_OK); + return (NULL); /* Name was not found in the repair table */ } /******************************************************************************* diff --git a/drivers/acpi/acpica/nsrepair2.c b/drivers/acpi/acpica/nsrepair2.c index ba4d98287c6..149e9b9c2c1 100644 --- a/drivers/acpi/acpica/nsrepair2.c +++ b/drivers/acpi/acpica/nsrepair2.c @@ -66,9 +66,9 @@ typedef struct acpi_repair_info { /* Local prototypes */ -static const struct acpi_repair_info *acpi_ns_match_repairable_name(struct - acpi_namespace_node - *node); +static const struct acpi_repair_info *acpi_ns_match_complex_repair(struct + acpi_namespace_node + *node); static acpi_status acpi_ns_repair_ALR(struct acpi_predefined_data *data, @@ -175,7 +175,7 @@ acpi_ns_complex_repairs(struct acpi_predefined_data *data, /* Check if this name is in the list of repairable names */ - predefined = acpi_ns_match_repairable_name(node); + predefined = acpi_ns_match_complex_repair(node); if (!predefined) { return (validate_status); } @@ -186,7 +186,7 @@ acpi_ns_complex_repairs(struct acpi_predefined_data *data, /****************************************************************************** * - * FUNCTION: acpi_ns_match_repairable_name + * FUNCTION: acpi_ns_match_complex_repair * * PARAMETERS: node - Namespace node for the method/object * @@ -196,9 +196,9 @@ acpi_ns_complex_repairs(struct acpi_predefined_data *data, * *****************************************************************************/ -static const struct acpi_repair_info *acpi_ns_match_repairable_name(struct - acpi_namespace_node - *node) +static const struct acpi_repair_info *acpi_ns_match_complex_repair(struct + acpi_namespace_node + *node) { const struct acpi_repair_info *this_name; diff --git a/drivers/acpi/acpica/nsutils.c b/drivers/acpi/acpica/nsutils.c index 686420df684..2808586fad3 100644 --- a/drivers/acpi/acpica/nsutils.c +++ b/drivers/acpi/acpica/nsutils.c @@ -112,10 +112,10 @@ acpi_object_type acpi_ns_get_type(struct acpi_namespace_node * node) if (!node) { ACPI_WARNING((AE_INFO, "Null Node parameter")); - return_VALUE(ACPI_TYPE_ANY); + return_UINT8(ACPI_TYPE_ANY); } - return_VALUE(node->type); + return_UINT8(node->type); } /******************************************************************************* @@ -140,10 +140,10 @@ u32 acpi_ns_local(acpi_object_type type) /* Type code out of range */ ACPI_WARNING((AE_INFO, "Invalid Object Type 0x%X", type)); - return_VALUE(ACPI_NS_NORMAL); + return_UINT32(ACPI_NS_NORMAL); } - return_VALUE(acpi_gbl_ns_properties[type] & ACPI_NS_LOCAL); + return_UINT32(acpi_gbl_ns_properties[type] & ACPI_NS_LOCAL); } /******************************************************************************* diff --git a/drivers/acpi/acpica/psargs.c b/drivers/acpi/acpica/psargs.c index f51308cdbc6..9f25a3d4e99 100644 --- a/drivers/acpi/acpica/psargs.c +++ b/drivers/acpi/acpica/psargs.c @@ -108,7 +108,7 @@ acpi_ps_get_next_package_length(struct acpi_parse_state *parser_state) /* Byte 0 is a special case, either bits [0:3] or [0:5] are used */ package_length |= (aml[0] & byte_zero_mask); - return_VALUE(package_length); + return_UINT32(package_length); } /******************************************************************************* diff --git a/drivers/acpi/acpica/rscalc.c b/drivers/acpi/acpica/rscalc.c index 7816d4eef04..72077fa1eea 100644 --- a/drivers/acpi/acpica/rscalc.c +++ b/drivers/acpi/acpica/rscalc.c @@ -202,6 +202,12 @@ acpi_rs_get_aml_length(struct acpi_resource * resource, acpi_size * size_needed) return_ACPI_STATUS(AE_AML_INVALID_RESOURCE_TYPE); } + /* Sanity check the length. It must not be zero, or we loop forever */ + + if (!resource->length) { + return_ACPI_STATUS(AE_AML_BAD_RESOURCE_LENGTH); + } + /* Get the base size of the (external stream) resource descriptor */ total_size = acpi_gbl_aml_resource_sizes[resource->type]; diff --git a/drivers/acpi/acpica/rsdump.c b/drivers/acpi/acpica/rsdump.c index cab51445189..b5fc0db2e87 100644 --- a/drivers/acpi/acpica/rsdump.c +++ b/drivers/acpi/acpica/rsdump.c @@ -385,6 +385,14 @@ void acpi_rs_dump_resource_list(struct acpi_resource *resource_list) return; } + /* Sanity check the length. It must not be zero, or we loop forever */ + + if (!resource_list->length) { + acpi_os_printf + ("Invalid zero length descriptor in resource list\n"); + return; + } + /* Dump the resource descriptor */ if (type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { diff --git a/drivers/acpi/acpica/rslist.c b/drivers/acpi/acpica/rslist.c index ee2e206fc6c..6053aa18209 100644 --- a/drivers/acpi/acpica/rslist.c +++ b/drivers/acpi/acpica/rslist.c @@ -178,6 +178,14 @@ acpi_rs_convert_resources_to_aml(struct acpi_resource *resource, return_ACPI_STATUS(AE_BAD_DATA); } + /* Sanity check the length. It must not be zero, or we loop forever */ + + if (!resource->length) { + ACPI_ERROR((AE_INFO, + "Invalid zero length descriptor in resource list\n")); + return_ACPI_STATUS(AE_AML_BAD_RESOURCE_LENGTH); + } + /* Perform the conversion */ if (resource->type == ACPI_RESOURCE_TYPE_SERIAL_BUS) { diff --git a/drivers/acpi/acpica/rsxface.c b/drivers/acpi/acpica/rsxface.c index 15d6eaef0e2..c0e5d2d3ce6 100644 --- a/drivers/acpi/acpica/rsxface.c +++ b/drivers/acpi/acpica/rsxface.c @@ -563,13 +563,19 @@ acpi_walk_resource_buffer(struct acpi_buffer * buffer, while (resource < resource_end) { - /* Sanity check the resource */ + /* Sanity check the resource type */ if (resource->type > ACPI_RESOURCE_TYPE_MAX) { status = AE_AML_INVALID_RESOURCE_TYPE; break; } + /* Sanity check the length. It must not be zero, or we loop forever */ + + if (!resource->length) { + return_ACPI_STATUS(AE_AML_BAD_RESOURCE_LENGTH); + } + /* Invoke the user function, abort on any error returned */ status = user_function(resource, context); diff --git a/drivers/acpi/acpica/tbfadt.c b/drivers/acpi/acpica/tbfadt.c index 74181bf181e..33b00d22300 100644 --- a/drivers/acpi/acpica/tbfadt.c +++ b/drivers/acpi/acpica/tbfadt.c @@ -559,8 +559,12 @@ static void acpi_tb_validate_fadt(void) /* * For each extended field, check for length mismatch between the * legacy length field and the corresponding 64-bit X length field. + * Note: If the legacy length field is > 0xFF bits, ignore this + * check. (GPE registers can be larger than the 64-bit GAS structure + * can accomodate, 0xFF bits). */ if (address64->address && + (ACPI_MUL_8(length) <= ACPI_UINT8_MAX) && (address64->bit_width != ACPI_MUL_8(length))) { ACPI_BIOS_WARNING((AE_INFO, "32/64X length mismatch in FADT/%s: %u/%u", diff --git a/drivers/acpi/acpica/tbxface.c b/drivers/acpi/acpica/tbxface.c index b35a5e6d653..ad11162482f 100644 --- a/drivers/acpi/acpica/tbxface.c +++ b/drivers/acpi/acpica/tbxface.c @@ -1,6 +1,6 @@ /****************************************************************************** * - * Module Name: tbxface - ACPI table oriented external interfaces + * Module Name: tbxface - ACPI table-oriented external interfaces * *****************************************************************************/ @@ -80,7 +80,7 @@ acpi_status acpi_allocate_root_table(u32 initial_table_count) * array is dynamically allocated. * initial_table_count - Size of initial_table_array, in number of * struct acpi_table_desc structures - * allow_realloc - Flag to tell Table Manager if resize of + * allow_resize - Flag to tell Table Manager if resize of * pre-allocated array is allowed. Ignored * if initial_table_array is NULL. * @@ -107,8 +107,8 @@ acpi_initialize_tables(struct acpi_table_desc * initial_table_array, ACPI_FUNCTION_TRACE(acpi_initialize_tables); /* - * Set up the Root Table Array - * Allocate the table array if requested + * Setup the Root Table Array and allocate the table array + * if requested */ if (!initial_table_array) { status = acpi_allocate_root_table(initial_table_count); @@ -305,9 +305,10 @@ ACPI_EXPORT_SYMBOL(acpi_unload_table_id) * instance - Which instance (for SSDTs) * out_table - Where the pointer to the table is returned * - * RETURN: Status and pointer to table + * RETURN: Status and pointer to the requested table * - * DESCRIPTION: Finds and verifies an ACPI table. + * DESCRIPTION: Finds and verifies an ACPI table. Table must be in the + * RSDT/XSDT. * ******************************************************************************/ acpi_status @@ -375,9 +376,10 @@ ACPI_EXPORT_SYMBOL(acpi_get_table) * PARAMETERS: table_index - Table index * table - Where the pointer to the table is returned * - * RETURN: Status and pointer to the table + * RETURN: Status and pointer to the requested table * - * DESCRIPTION: Obtain a table by an index into the global table list. + * DESCRIPTION: Obtain a table by an index into the global table list. Used + * internally also. * ******************************************************************************/ acpi_status @@ -432,7 +434,7 @@ ACPI_EXPORT_SYMBOL(acpi_get_table_by_index) * * RETURN: Status * - * DESCRIPTION: Install table event handler + * DESCRIPTION: Install a global table event handler. * ******************************************************************************/ acpi_status @@ -479,7 +481,7 @@ ACPI_EXPORT_SYMBOL(acpi_install_table_handler) * * RETURN: Status * - * DESCRIPTION: Remove table event handler + * DESCRIPTION: Remove a table event handler * ******************************************************************************/ acpi_status acpi_remove_table_handler(acpi_table_handler handler) diff --git a/drivers/acpi/acpica/utaddress.c b/drivers/acpi/acpica/utaddress.c index 698b9d38551..e0a2e2779c2 100644 --- a/drivers/acpi/acpica/utaddress.c +++ b/drivers/acpi/acpica/utaddress.c @@ -214,7 +214,7 @@ acpi_ut_check_address_range(acpi_adr_space_type space_id, if ((space_id != ACPI_ADR_SPACE_SYSTEM_MEMORY) && (space_id != ACPI_ADR_SPACE_SYSTEM_IO)) { - return_VALUE(0); + return_UINT32(0); } range_info = acpi_gbl_address_range_list[space_id]; @@ -256,7 +256,7 @@ acpi_ut_check_address_range(acpi_adr_space_type space_id, range_info = range_info->next; } - return_VALUE(overlap_count); + return_UINT32(overlap_count); } /******************************************************************************* diff --git a/drivers/acpi/acpica/utcache.c b/drivers/acpi/acpica/utcache.c index e0e8579deaa..a877a9647fd 100644 --- a/drivers/acpi/acpica/utcache.c +++ b/drivers/acpi/acpica/utcache.c @@ -85,7 +85,6 @@ acpi_os_create_cache(char *cache_name, /* Populate the cache object and return it */ ACPI_MEMSET(cache, 0, sizeof(struct acpi_memory_list)); - cache->link_offset = 8; cache->list_name = cache_name; cache->object_size = object_size; cache->max_depth = max_depth; @@ -108,7 +107,7 @@ acpi_os_create_cache(char *cache_name, acpi_status acpi_os_purge_cache(struct acpi_memory_list * cache) { - char *next; + void *next; acpi_status status; ACPI_FUNCTION_ENTRY(); @@ -128,10 +127,7 @@ acpi_status acpi_os_purge_cache(struct acpi_memory_list * cache) /* Delete and unlink one cached state object */ - next = *(ACPI_CAST_INDIRECT_PTR(char, - &(((char *)cache-> - list_head)[cache-> - link_offset]))); + next = ACPI_GET_DESCRIPTOR_PTR(cache->list_head); ACPI_FREE(cache->list_head); cache->list_head = next; @@ -221,10 +217,7 @@ acpi_os_release_object(struct acpi_memory_list * cache, void *object) /* Put the object at the head of the cache list */ - *(ACPI_CAST_INDIRECT_PTR(char, - &(((char *)object)[cache-> - link_offset]))) = - cache->list_head; + ACPI_SET_DESCRIPTOR_PTR(object, cache->list_head); cache->list_head = object; cache->current_depth++; @@ -272,10 +265,7 @@ void *acpi_os_acquire_object(struct acpi_memory_list *cache) /* There is an object available, use it */ object = cache->list_head; - cache->list_head = *(ACPI_CAST_INDIRECT_PTR(char, - &(((char *) - object)[cache-> - link_offset]))); + cache->list_head = ACPI_GET_DESCRIPTOR_PTR(object); cache->current_depth--; diff --git a/drivers/acpi/acpica/utdelete.c b/drivers/acpi/acpica/utdelete.c index 2541de42024..29b930250b6 100644 --- a/drivers/acpi/acpica/utdelete.c +++ b/drivers/acpi/acpica/utdelete.c @@ -359,19 +359,20 @@ void acpi_ut_delete_internal_object_list(union acpi_operand_object **obj_list) * FUNCTION: acpi_ut_update_ref_count * * PARAMETERS: object - Object whose ref count is to be updated - * action - What to do + * action - What to do (REF_INCREMENT or REF_DECREMENT) * - * RETURN: New ref count + * RETURN: None. Sets new reference count within the object * - * DESCRIPTION: Modify the ref count and return it. + * DESCRIPTION: Modify the reference count for an internal acpi object * ******************************************************************************/ static void acpi_ut_update_ref_count(union acpi_operand_object *object, u32 action) { - u16 count; - u16 new_count; + u16 original_count; + u16 new_count = 0; + acpi_cpu_flags lock_flags; ACPI_FUNCTION_NAME(ut_update_ref_count); @@ -379,76 +380,79 @@ acpi_ut_update_ref_count(union acpi_operand_object *object, u32 action) return; } - count = object->common.reference_count; - new_count = count; - /* - * Perform the reference count action (increment, decrement, force delete) + * Always get the reference count lock. Note: Interpreter and/or + * Namespace is not always locked when this function is called. */ + lock_flags = acpi_os_acquire_lock(acpi_gbl_reference_count_lock); + original_count = object->common.reference_count; + + /* Perform the reference count action (increment, decrement) */ + switch (action) { case REF_INCREMENT: - new_count++; + new_count = original_count + 1; object->common.reference_count = new_count; + acpi_os_release_lock(acpi_gbl_reference_count_lock, lock_flags); + + /* The current reference count should never be zero here */ + + if (!original_count) { + ACPI_WARNING((AE_INFO, + "Obj %p, Reference Count was zero before increment\n", + object)); + } ACPI_DEBUG_PRINT((ACPI_DB_ALLOCATIONS, - "Obj %p Refs=%X, [Incremented]\n", - object, new_count)); + "Obj %p Type %.2X Refs %.2X [Incremented]\n", + object, object->common.type, new_count)); break; case REF_DECREMENT: - if (count < 1) { - ACPI_DEBUG_PRINT((ACPI_DB_ALLOCATIONS, - "Obj %p Refs=%X, can't decrement! (Set to 0)\n", - object, new_count)); - - new_count = 0; - } else { - new_count--; + /* The current reference count must be non-zero */ - ACPI_DEBUG_PRINT((ACPI_DB_ALLOCATIONS, - "Obj %p Refs=%X, [Decremented]\n", - object, new_count)); + if (original_count) { + new_count = original_count - 1; + object->common.reference_count = new_count; } - if (object->common.type == ACPI_TYPE_METHOD) { - ACPI_DEBUG_PRINT((ACPI_DB_ALLOCATIONS, - "Method Obj %p Refs=%X, [Decremented]\n", - object, new_count)); - } + acpi_os_release_lock(acpi_gbl_reference_count_lock, lock_flags); - object->common.reference_count = new_count; - if (new_count == 0) { - acpi_ut_delete_internal_obj(object); + if (!original_count) { + ACPI_WARNING((AE_INFO, + "Obj %p, Reference Count is already zero, cannot decrement\n", + object)); } - break; - - case REF_FORCE_DELETE: ACPI_DEBUG_PRINT((ACPI_DB_ALLOCATIONS, - "Obj %p Refs=%X, Force delete! (Set to 0)\n", - object, count)); + "Obj %p Type %.2X Refs %.2X [Decremented]\n", + object, object->common.type, new_count)); - new_count = 0; - object->common.reference_count = new_count; - acpi_ut_delete_internal_obj(object); + /* Actually delete the object on a reference count of zero */ + + if (new_count == 0) { + acpi_ut_delete_internal_obj(object); + } break; default: - ACPI_ERROR((AE_INFO, "Unknown action (0x%X)", action)); - break; + acpi_os_release_lock(acpi_gbl_reference_count_lock, lock_flags); + ACPI_ERROR((AE_INFO, "Unknown Reference Count action (0x%X)", + action)); + return; } /* * Sanity check the reference count, for debug purposes only. * (A deleted object will have a huge reference count) */ - if (count > ACPI_MAX_REFERENCE_COUNT) { + if (new_count > ACPI_MAX_REFERENCE_COUNT) { ACPI_WARNING((AE_INFO, - "Large Reference Count (0x%X) in object %p", - count, object)); + "Large Reference Count (0x%X) in object %p, Type=0x%.2X", + new_count, object, object->common.type)); } } @@ -458,8 +462,7 @@ acpi_ut_update_ref_count(union acpi_operand_object *object, u32 action) * * PARAMETERS: object - Increment ref count for this object * and all sub-objects - * action - Either REF_INCREMENT or REF_DECREMENT or - * REF_FORCE_DELETE + * action - Either REF_INCREMENT or REF_DECREMENT * * RETURN: Status * @@ -714,7 +717,6 @@ void acpi_ut_remove_reference(union acpi_operand_object *object) /* * Allow a NULL pointer to be passed in, just ignore it. This saves * each caller from having to check. Also, ignore NS nodes. - * */ if (!object || (ACPI_GET_DESCRIPTOR_TYPE(object) == ACPI_DESC_TYPE_NAMED)) { diff --git a/drivers/acpi/acpica/utexcep.c b/drivers/acpi/acpica/utexcep.c index a0ab7c02e87..b543a144941 100644 --- a/drivers/acpi/acpica/utexcep.c +++ b/drivers/acpi/acpica/utexcep.c @@ -64,7 +64,7 @@ ACPI_MODULE_NAME("utexcep") ******************************************************************************/ const char *acpi_format_exception(acpi_status status) { - const char *exception = NULL; + const struct acpi_exception_info *exception; ACPI_FUNCTION_ENTRY(); @@ -76,10 +76,10 @@ const char *acpi_format_exception(acpi_status status) ACPI_ERROR((AE_INFO, "Unknown exception code: 0x%8.8X", status)); - exception = "UNKNOWN_STATUS_CODE"; + return ("UNKNOWN_STATUS_CODE"); } - return (ACPI_CAST_PTR(const char, exception)); + return (exception->name); } ACPI_EXPORT_SYMBOL(acpi_format_exception) @@ -97,10 +97,10 @@ ACPI_EXPORT_SYMBOL(acpi_format_exception) * an ASCII string. * ******************************************************************************/ -const char *acpi_ut_validate_exception(acpi_status status) +const struct acpi_exception_info *acpi_ut_validate_exception(acpi_status status) { u32 sub_status; - const char *exception = NULL; + const struct acpi_exception_info *exception = NULL; ACPI_FUNCTION_ENTRY(); @@ -113,35 +113,35 @@ const char *acpi_ut_validate_exception(acpi_status status) case AE_CODE_ENVIRONMENTAL: if (sub_status <= AE_CODE_ENV_MAX) { - exception = acpi_gbl_exception_names_env[sub_status]; + exception = &acpi_gbl_exception_names_env[sub_status]; } break; case AE_CODE_PROGRAMMER: if (sub_status <= AE_CODE_PGM_MAX) { - exception = acpi_gbl_exception_names_pgm[sub_status]; + exception = &acpi_gbl_exception_names_pgm[sub_status]; } break; case AE_CODE_ACPI_TABLES: if (sub_status <= AE_CODE_TBL_MAX) { - exception = acpi_gbl_exception_names_tbl[sub_status]; + exception = &acpi_gbl_exception_names_tbl[sub_status]; } break; case AE_CODE_AML: if (sub_status <= AE_CODE_AML_MAX) { - exception = acpi_gbl_exception_names_aml[sub_status]; + exception = &acpi_gbl_exception_names_aml[sub_status]; } break; case AE_CODE_CONTROL: if (sub_status <= AE_CODE_CTRL_MAX) { - exception = acpi_gbl_exception_names_ctrl[sub_status]; + exception = &acpi_gbl_exception_names_ctrl[sub_status]; } break; @@ -149,5 +149,9 @@ const char *acpi_ut_validate_exception(acpi_status status) break; } - return (ACPI_CAST_PTR(const char, exception)); + if (!exception || !exception->name) { + return (NULL); + } + + return (exception); } diff --git a/drivers/acpi/acpica/utglobal.c b/drivers/acpi/acpica/utglobal.c index ffecf4b4f0d..f736448a860 100644 --- a/drivers/acpi/acpica/utglobal.c +++ b/drivers/acpi/acpica/utglobal.c @@ -359,6 +359,8 @@ acpi_status acpi_ut_init_globals(void) #ifdef ACPI_DISASSEMBLER acpi_gbl_external_list = NULL; + acpi_gbl_num_external_methods = 0; + acpi_gbl_resolved_external_methods = 0; #endif #ifdef ACPI_DEBUG_OUTPUT diff --git a/drivers/acpi/acpica/utmutex.c b/drivers/acpi/acpica/utmutex.c index 22feb99b8e3..08c32324558 100644 --- a/drivers/acpi/acpica/utmutex.c +++ b/drivers/acpi/acpica/utmutex.c @@ -81,7 +81,7 @@ acpi_status acpi_ut_mutex_initialize(void) } } - /* Create the spinlocks for use at interrupt level */ + /* Create the spinlocks for use at interrupt level or for speed */ status = acpi_os_create_lock (&acpi_gbl_gpe_lock); if (ACPI_FAILURE (status)) { @@ -93,7 +93,13 @@ acpi_status acpi_ut_mutex_initialize(void) return_ACPI_STATUS (status); } + status = acpi_os_create_lock(&acpi_gbl_reference_count_lock); + if (ACPI_FAILURE(status)) { + return_ACPI_STATUS(status); + } + /* Mutex for _OSI support */ + status = acpi_os_create_mutex(&acpi_gbl_osi_mutex); if (ACPI_FAILURE(status)) { return_ACPI_STATUS(status); @@ -136,6 +142,7 @@ void acpi_ut_mutex_terminate(void) acpi_os_delete_lock(acpi_gbl_gpe_lock); acpi_os_delete_lock(acpi_gbl_hardware_lock); + acpi_os_delete_lock(acpi_gbl_reference_count_lock); /* Delete the reader/writer lock */ diff --git a/drivers/acpi/acpica/utosi.c b/drivers/acpi/acpica/utosi.c index 36a7d361d7c..b15acebb96a 100644 --- a/drivers/acpi/acpica/utosi.c +++ b/drivers/acpi/acpica/utosi.c @@ -108,9 +108,14 @@ static struct acpi_interface_info acpi_default_supported_interfaces[] = { acpi_status acpi_ut_initialize_interfaces(void) { + acpi_status status; u32 i; - (void)acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + status = acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + if (ACPI_FAILURE(status)) { + return (status); + } + acpi_gbl_supported_interfaces = acpi_default_supported_interfaces; /* Link the static list of supported interfaces */ @@ -132,20 +137,24 @@ acpi_status acpi_ut_initialize_interfaces(void) * * PARAMETERS: None * - * RETURN: None + * RETURN: Status * * DESCRIPTION: Delete all interfaces in the global list. Sets * acpi_gbl_supported_interfaces to NULL. * ******************************************************************************/ -void acpi_ut_interface_terminate(void) +acpi_status acpi_ut_interface_terminate(void) { + acpi_status status; struct acpi_interface_info *next_interface; - (void)acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); - next_interface = acpi_gbl_supported_interfaces; + status = acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + if (ACPI_FAILURE(status)) { + return (status); + } + next_interface = acpi_gbl_supported_interfaces; while (next_interface) { acpi_gbl_supported_interfaces = next_interface->next; @@ -160,6 +169,7 @@ void acpi_ut_interface_terminate(void) } acpi_os_release_mutex(acpi_gbl_osi_mutex); + return (AE_OK); } /******************************************************************************* @@ -315,6 +325,7 @@ acpi_status acpi_ut_osi_implementation(struct acpi_walk_state * walk_state) union acpi_operand_object *return_desc; struct acpi_interface_info *interface_info; acpi_interface_handler interface_handler; + acpi_status status; u32 return_value; ACPI_FUNCTION_TRACE(ut_osi_implementation); @@ -336,7 +347,10 @@ acpi_status acpi_ut_osi_implementation(struct acpi_walk_state * walk_state) /* Default return value is 0, NOT SUPPORTED */ return_value = 0; - (void)acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + status = acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + if (ACPI_FAILURE(status)) { + return (status); + } /* Lookup the interface in the global _OSI list */ diff --git a/drivers/acpi/acpica/utpredef.c b/drivers/acpi/acpica/utpredef.c new file mode 100644 index 00000000000..29459479148 --- /dev/null +++ b/drivers/acpi/acpica/utpredef.c @@ -0,0 +1,399 @@ +/****************************************************************************** + * + * Module Name: utpredef - support functions for predefined names + * + *****************************************************************************/ + +/* + * Copyright (C) 2000 - 2013, Intel Corp. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions, and the following disclaimer, + * without modification. + * 2. Redistributions in binary form must reproduce at minimum a disclaimer + * substantially similar to the "NO WARRANTY" disclaimer below + * ("Disclaimer") and any redistribution must be conditioned upon + * including a substantially similar Disclaimer requirement for further + * binary redistribution. + * 3. Neither the names of the above-listed copyright holders nor the names + * of any contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * Alternatively, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2 as published by the Free + * Software Foundation. + * + * NO WARRANTY + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * HOLDERS OR CONTRIBUTORS BE LIABLE FOR SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS + * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) + * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, + * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING + * IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE + * POSSIBILITY OF SUCH DAMAGES. + */ + +#include <acpi/acpi.h> +#include "accommon.h" +#include "acpredef.h" + +#define _COMPONENT ACPI_UTILITIES +ACPI_MODULE_NAME("utpredef") + +/* + * Names for the types that can be returned by the predefined objects. + * Used for warning messages. Must be in the same order as the ACPI_RTYPEs + */ +static const char *ut_rtype_names[] = { + "/Integer", + "/String", + "/Buffer", + "/Package", + "/Reference", +}; + +/******************************************************************************* + * + * FUNCTION: acpi_ut_get_next_predefined_method + * + * PARAMETERS: this_name - Entry in the predefined method/name table + * + * RETURN: Pointer to next entry in predefined table. + * + * DESCRIPTION: Get the next entry in the predefine method table. Handles the + * cases where a package info entry follows a method name that + * returns a package. + * + ******************************************************************************/ + +const union acpi_predefined_info *acpi_ut_get_next_predefined_method(const union + acpi_predefined_info + *this_name) +{ + + /* + * Skip next entry in the table if this name returns a Package + * (next entry contains the package info) + */ + if ((this_name->info.expected_btypes & ACPI_RTYPE_PACKAGE) && + (this_name->info.expected_btypes != ACPI_RTYPE_ALL)) { + this_name++; + } + + this_name++; + return (this_name); +} + +/******************************************************************************* + * + * FUNCTION: acpi_ut_match_predefined_method + * + * PARAMETERS: name - Name to find + * + * RETURN: Pointer to entry in predefined table. NULL indicates not found. + * + * DESCRIPTION: Check an object name against the predefined object list. + * + ******************************************************************************/ + +const union acpi_predefined_info *acpi_ut_match_predefined_method(char *name) +{ + const union acpi_predefined_info *this_name; + + /* Quick check for a predefined name, first character must be underscore */ + + if (name[0] != '_') { + return (NULL); + } + + /* Search info table for a predefined method/object name */ + + this_name = acpi_gbl_predefined_methods; + while (this_name->info.name[0]) { + if (ACPI_COMPARE_NAME(name, this_name->info.name)) { + return (this_name); + } + + this_name = acpi_ut_get_next_predefined_method(this_name); + } + + return (NULL); /* Not found */ +} + +/******************************************************************************* + * + * FUNCTION: acpi_ut_get_expected_return_types + * + * PARAMETERS: buffer - Where the formatted string is returned + * expected_Btypes - Bitfield of expected data types + * + * RETURN: Formatted string in Buffer. + * + * DESCRIPTION: Format the expected object types into a printable string. + * + ******************************************************************************/ + +void acpi_ut_get_expected_return_types(char *buffer, u32 expected_btypes) +{ + u32 this_rtype; + u32 i; + u32 j; + + j = 1; + buffer[0] = 0; + this_rtype = ACPI_RTYPE_INTEGER; + + for (i = 0; i < ACPI_NUM_RTYPES; i++) { + + /* If one of the expected types, concatenate the name of this type */ + + if (expected_btypes & this_rtype) { + ACPI_STRCAT(buffer, &ut_rtype_names[i][j]); + j = 0; /* Use name separator from now on */ + } + + this_rtype <<= 1; /* Next Rtype */ + } +} + +/******************************************************************************* + * + * The remaining functions are used by iASL and acpi_help only + * + ******************************************************************************/ + +#if (defined ACPI_ASL_COMPILER || defined ACPI_HELP_APP) +#include <stdio.h> +#include <string.h> + +/* Local prototypes */ + +static u32 acpi_ut_get_argument_types(char *buffer, u16 argument_types); + +/* Types that can be returned externally by a predefined name */ + +static const char *ut_external_type_names[] = /* Indexed by ACPI_TYPE_* */ +{ + ", UNSUPPORTED-TYPE", + ", Integer", + ", String", + ", Buffer", + ", Package" +}; + +/* Bit widths for resource descriptor predefined names */ + +static const char *ut_resource_type_names[] = { + "/1", + "/2", + "/3", + "/8", + "/16", + "/32", + "/64", + "/variable", +}; + +/******************************************************************************* + * + * FUNCTION: acpi_ut_match_resource_name + * + * PARAMETERS: name - Name to find + * + * RETURN: Pointer to entry in the resource table. NULL indicates not + * found. + * + * DESCRIPTION: Check an object name against the predefined resource + * descriptor object list. + * + ******************************************************************************/ + +const union acpi_predefined_info *acpi_ut_match_resource_name(char *name) +{ + const union acpi_predefined_info *this_name; + + /* Quick check for a predefined name, first character must be underscore */ + + if (name[0] != '_') { + return (NULL); + } + + /* Search info table for a predefined method/object name */ + + this_name = acpi_gbl_resource_names; + while (this_name->info.name[0]) { + if (ACPI_COMPARE_NAME(name, this_name->info.name)) { + return (this_name); + } + + this_name++; + } + + return (NULL); /* Not found */ +} + +/******************************************************************************* + * + * FUNCTION: acpi_ut_display_predefined_method + * + * PARAMETERS: buffer - Scratch buffer for this function + * this_name - Entry in the predefined method/name table + * multi_line - TRUE if output should be on >1 line + * + * RETURN: None + * + * DESCRIPTION: Display information about a predefined method. Number and + * type of the input arguments, and expected type(s) for the + * return value, if any. + * + ******************************************************************************/ + +void +acpi_ut_display_predefined_method(char *buffer, + const union acpi_predefined_info *this_name, + u8 multi_line) +{ + u32 arg_count; + + /* + * Get the argument count and the string buffer + * containing all argument types + */ + arg_count = acpi_ut_get_argument_types(buffer, + this_name->info.argument_list); + + if (multi_line) { + printf(" "); + } + + printf("%4.4s Requires %s%u argument%s", + this_name->info.name, + (this_name->info.argument_list & ARG_COUNT_IS_MINIMUM) ? + "(at least) " : "", arg_count, arg_count != 1 ? "s" : ""); + + /* Display the types for any arguments */ + + if (arg_count > 0) { + printf(" (%s)", buffer); + } + + if (multi_line) { + printf("\n "); + } + + /* Get the return value type(s) allowed */ + + if (this_name->info.expected_btypes) { + acpi_ut_get_expected_return_types(buffer, + this_name->info. + expected_btypes); + printf(" Return value types: %s\n", buffer); + } else { + printf(" No return value\n"); + } +} + +/******************************************************************************* + * + * FUNCTION: acpi_ut_get_argument_types + * + * PARAMETERS: buffer - Where to return the formatted types + * argument_types - Types field for this method + * + * RETURN: count - the number of arguments required for this method + * + * DESCRIPTION: Format the required data types for this method (Integer, + * String, Buffer, or Package) and return the required argument + * count. + * + ******************************************************************************/ + +static u32 acpi_ut_get_argument_types(char *buffer, u16 argument_types) +{ + u16 this_argument_type; + u16 sub_index; + u16 arg_count; + u32 i; + + *buffer = 0; + sub_index = 2; + + /* First field in the types list is the count of args to follow */ + + arg_count = (argument_types & METHOD_ARG_MASK); + argument_types >>= METHOD_ARG_BIT_WIDTH; + + if (arg_count > METHOD_PREDEF_ARGS_MAX) { + printf("**** Invalid argument count (%u) " + "in predefined info structure\n", arg_count); + return (arg_count); + } + + /* Get each argument from the list, convert to ascii, store to buffer */ + + for (i = 0; i < arg_count; i++) { + this_argument_type = (argument_types & METHOD_ARG_MASK); + if (!this_argument_type + || (this_argument_type > METHOD_MAX_ARG_TYPE)) { + printf("**** Invalid argument type (%u) " + "in predefined info structure\n", + this_argument_type); + return (arg_count); + } + + strcat(buffer, + ut_external_type_names[this_argument_type] + sub_index); + + /* Shift to next argument type field */ + + argument_types >>= METHOD_ARG_BIT_WIDTH; + sub_index = 0; + } + + return (arg_count); +} + +/******************************************************************************* + * + * FUNCTION: acpi_ut_get_resource_bit_width + * + * PARAMETERS: buffer - Where the formatted string is returned + * types - Bitfield of expected data types + * + * RETURN: Count of return types. Formatted string in Buffer. + * + * DESCRIPTION: Format the resource bit widths into a printable string. + * + ******************************************************************************/ + +u32 acpi_ut_get_resource_bit_width(char *buffer, u16 types) +{ + u32 i; + u16 sub_index; + u32 found; + + *buffer = 0; + sub_index = 1; + found = 0; + + for (i = 0; i < NUM_RESOURCE_WIDTHS; i++) { + if (types & 1) { + strcat(buffer, &(ut_resource_type_names[i][sub_index])); + sub_index = 0; + found++; + } + + types >>= 1; + } + + return (found); +} +#endif diff --git a/drivers/acpi/acpica/utxface.c b/drivers/acpi/acpica/utxface.c index 48efb446258..6505774f223 100644 --- a/drivers/acpi/acpica/utxface.c +++ b/drivers/acpi/acpica/utxface.c @@ -287,7 +287,10 @@ acpi_status acpi_install_interface(acpi_string interface_name) return (AE_BAD_PARAMETER); } - (void)acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + status = acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + if (ACPI_FAILURE(status)) { + return (status); + } /* Check if the interface name is already in the global list */ @@ -336,7 +339,10 @@ acpi_status acpi_remove_interface(acpi_string interface_name) return (AE_BAD_PARAMETER); } - (void)acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + status = acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + if (ACPI_FAILURE(status)) { + return (status); + } status = acpi_ut_remove_interface(interface_name); @@ -362,9 +368,12 @@ ACPI_EXPORT_SYMBOL(acpi_remove_interface) ****************************************************************************/ acpi_status acpi_install_interface_handler(acpi_interface_handler handler) { - acpi_status status = AE_OK; + acpi_status status; - (void)acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + status = acpi_os_acquire_mutex(acpi_gbl_osi_mutex, ACPI_WAIT_FOREVER); + if (ACPI_FAILURE(status)) { + return (status); + } if (handler && acpi_gbl_interface_handler) { status = AE_ALREADY_EXISTS; diff --git a/drivers/acpi/battery.c b/drivers/acpi/battery.c index c5cd5b5513e..0cc384b7294 100644 --- a/drivers/acpi/battery.c +++ b/drivers/acpi/battery.c @@ -146,7 +146,7 @@ struct acpi_battery { #define to_acpi_battery(x) container_of(x, struct acpi_battery, bat) -inline int acpi_battery_present(struct acpi_battery *battery) +static inline int acpi_battery_present(struct acpi_battery *battery) { return battery->device->status.battery_present; } diff --git a/drivers/acpi/bus.c b/drivers/acpi/bus.c index 01708a16536..292de3cab9c 100644 --- a/drivers/acpi/bus.c +++ b/drivers/acpi/bus.c @@ -288,13 +288,12 @@ acpi_status acpi_run_osc(acpi_handle handle, struct acpi_osc_context *context) } out_success: context->ret.length = out_obj->buffer.length; - context->ret.pointer = kmalloc(context->ret.length, GFP_KERNEL); + context->ret.pointer = kmemdup(out_obj->buffer.pointer, + context->ret.length, GFP_KERNEL); if (!context->ret.pointer) { status = AE_NO_MEMORY; goto out_kfree; } - memcpy(context->ret.pointer, out_obj->buffer.pointer, - context->ret.length); status = AE_OK; out_kfree: diff --git a/drivers/acpi/button.c b/drivers/acpi/button.c index 86c7d5445c3..92a659aa639 100644 --- a/drivers/acpi/button.c +++ b/drivers/acpi/button.c @@ -33,6 +33,7 @@ #include <linux/slab.h> #include <acpi/acpi_bus.h> #include <acpi/acpi_drivers.h> +#include <acpi/button.h> #define PREFIX "ACPI: " diff --git a/drivers/acpi/container.c b/drivers/acpi/container.c index 5523ba7d764..e2315166765 100644 --- a/drivers/acpi/container.c +++ b/drivers/acpi/container.c @@ -1,12 +1,12 @@ /* - * acpi_container.c - ACPI Generic Container Driver - * ($Revision: ) + * container.c - ACPI Generic Container Driver * * Copyright (C) 2004 Anil S Keshavamurthy (anil.s.keshavamurthy@intel.com) * Copyright (C) 2004 Keiichiro Tokunaga (tokunaga.keiich@jp.fujitsu.com) * Copyright (C) 2004 Motoyuki Ito (motoyuki@soft.fujitsu.com) - * Copyright (C) 2004 Intel Corp. * Copyright (C) 2004 FUJITSU LIMITED + * Copyright (C) 2004, 2013 Intel Corp. + * Author: Rafael J. Wysocki <rafael.j.wysocki@intel.com> * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ * @@ -26,14 +26,11 @@ * * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */ -#include <linux/kernel.h> -#include <linux/module.h> -#include <linux/init.h> -#include <linux/slab.h> -#include <linux/types.h> #include <linux/acpi.h> -#include <acpi/acpi_bus.h> -#include <acpi/acpi_drivers.h> + +#include "internal.h" + +#include "internal.h" #define PREFIX "ACPI: " @@ -50,141 +47,20 @@ static const struct acpi_device_id container_device_ids[] = { static int container_device_attach(struct acpi_device *device, const struct acpi_device_id *not_used) { - /* - * FIXME: This is necessary, so that acpi_eject_store() doesn't return - * -ENODEV for containers. - */ + /* This is necessary for container hotplug to work. */ return 1; } -static struct acpi_scan_handler container_device_handler = { +static struct acpi_scan_handler container_handler = { .ids = container_device_ids, .attach = container_device_attach, + .hotplug = { + .enabled = true, + .mode = AHM_CONTAINER, + }, }; -static int is_device_present(acpi_handle handle) -{ - acpi_handle temp; - acpi_status status; - unsigned long long sta; - - - status = acpi_get_handle(handle, "_STA", &temp); - if (ACPI_FAILURE(status)) - return 1; /* _STA not found, assume device present */ - - status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); - if (ACPI_FAILURE(status)) - return 0; /* Firmware error */ - - return ((sta & ACPI_STA_DEVICE_PRESENT) == ACPI_STA_DEVICE_PRESENT); -} - -static void container_notify_cb(acpi_handle handle, u32 type, void *context) -{ - struct acpi_device *device = NULL; - int result; - int present; - acpi_status status; - u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */ - - acpi_scan_lock_acquire(); - - switch (type) { - case ACPI_NOTIFY_BUS_CHECK: - /* Fall through */ - case ACPI_NOTIFY_DEVICE_CHECK: - pr_debug("Container driver received %s event\n", - (type == ACPI_NOTIFY_BUS_CHECK) ? - "ACPI_NOTIFY_BUS_CHECK" : "ACPI_NOTIFY_DEVICE_CHECK"); - - present = is_device_present(handle); - status = acpi_bus_get_device(handle, &device); - if (!present) { - if (ACPI_SUCCESS(status)) { - /* device exist and this is a remove request */ - device->flags.eject_pending = 1; - kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE); - goto out; - } - break; - } - - if (!ACPI_FAILURE(status) || device) - break; - - result = acpi_bus_scan(handle); - if (result) { - acpi_handle_warn(handle, "Failed to add container\n"); - break; - } - result = acpi_bus_get_device(handle, &device); - if (result) { - acpi_handle_warn(handle, "Missing device object\n"); - break; - } - - kobject_uevent(&device->dev.kobj, KOBJ_ONLINE); - ost_code = ACPI_OST_SC_SUCCESS; - break; - - case ACPI_NOTIFY_EJECT_REQUEST: - if (!acpi_bus_get_device(handle, &device) && device) { - device->flags.eject_pending = 1; - kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE); - goto out; - } - break; - - default: - /* non-hotplug event; possibly handled by other handler */ - goto out; - } - - /* Inform firmware that the hotplug operation has completed */ - (void) acpi_evaluate_hotplug_ost(handle, type, ost_code, NULL); - - out: - acpi_scan_lock_release(); -} - -static bool is_container(acpi_handle handle) -{ - struct acpi_device_info *info; - bool ret = false; - - if (ACPI_FAILURE(acpi_get_object_info(handle, &info))) - return false; - - if (info->valid & ACPI_VALID_HID) { - const struct acpi_device_id *id; - - for (id = container_device_ids; id->id[0]; id++) { - ret = !strcmp((char *)id->id, info->hardware_id.string); - if (ret) - break; - } - } - kfree(info); - return ret; -} - -static acpi_status acpi_container_register_notify_handler(acpi_handle handle, - u32 lvl, void *ctxt, - void **retv) -{ - if (is_container(handle)) - acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, - container_notify_cb, NULL); - - return AE_OK; -} - void __init acpi_container_init(void) { - acpi_walk_namespace(ACPI_TYPE_DEVICE, ACPI_ROOT_OBJECT, ACPI_UINT32_MAX, - acpi_container_register_notify_handler, NULL, - NULL, NULL); - - acpi_scan_add_handler(&container_device_handler); + acpi_scan_add_handler_with_hotplug(&container_handler, "container"); } diff --git a/drivers/acpi/device_pm.c b/drivers/acpi/device_pm.c index dd314ef9bff..96de787e610 100644 --- a/drivers/acpi/device_pm.c +++ b/drivers/acpi/device_pm.c @@ -145,27 +145,36 @@ int acpi_device_get_power(struct acpi_device *device, int *state) } /* - * Get the device's power state either directly (via _PSC) or - * indirectly (via power resources). + * Get the device's power state from power resources settings and _PSC, + * if available. */ + if (device->power.flags.power_resources) { + int error = acpi_power_get_inferred_state(device, &result); + if (error) + return error; + } if (device->power.flags.explicit_get) { + acpi_handle handle = device->handle; unsigned long long psc; - acpi_status status = acpi_evaluate_integer(device->handle, - "_PSC", NULL, &psc); + acpi_status status; + + status = acpi_evaluate_integer(handle, "_PSC", NULL, &psc); if (ACPI_FAILURE(status)) return -ENODEV; - result = psc; - } - /* The test below covers ACPI_STATE_UNKNOWN too. */ - if (result <= ACPI_STATE_D2) { - ; /* Do nothing. */ - } else if (device->power.flags.power_resources) { - int error = acpi_power_get_inferred_state(device, &result); - if (error) - return error; - } else if (result == ACPI_STATE_D3_HOT) { - result = ACPI_STATE_D3; + /* + * The power resources settings may indicate a power state + * shallower than the actual power state of the device. + * + * Moreover, on systems predating ACPI 4.0, if the device + * doesn't depend on any power resources and _PSC returns 3, + * that means "power off". We need to maintain compatibility + * with those systems. + */ + if (psc > result && psc < ACPI_STATE_D3_COLD) + result = psc; + else if (result == ACPI_STATE_UNKNOWN) + result = psc > ACPI_STATE_D2 ? ACPI_STATE_D3_COLD : psc; } /* diff --git a/drivers/acpi/fan.c b/drivers/acpi/fan.c index f815da82c76..8d1c0105e11 100644 --- a/drivers/acpi/fan.c +++ b/drivers/acpi/fan.c @@ -174,9 +174,13 @@ static int acpi_fan_add(struct acpi_device *device) static int acpi_fan_remove(struct acpi_device *device) { - struct thermal_cooling_device *cdev = acpi_driver_data(device); + struct thermal_cooling_device *cdev; + + if (!device) + return -EINVAL; - if (!device || !cdev) + cdev = acpi_driver_data(device); + if (!cdev) return -EINVAL; sysfs_remove_link(&device->dev.kobj, "thermal_cooling"); diff --git a/drivers/acpi/internal.h b/drivers/acpi/internal.h index 3c94a732b4b..6f1afd9118c 100644 --- a/drivers/acpi/internal.h +++ b/drivers/acpi/internal.h @@ -41,6 +41,17 @@ void acpi_container_init(void); #else static inline void acpi_container_init(void) {} #endif +#ifdef CONFIG_ACPI_HOTPLUG_MEMORY +void acpi_memory_hotplug_init(void); +#else +static inline void acpi_memory_hotplug_init(void) {} +#endif + +void acpi_sysfs_add_hotplug_profile(struct acpi_hotplug_profile *hotplug, + const char *name); +int acpi_scan_add_handler_with_hotplug(struct acpi_scan_handler *handler, + const char *hotplug_profile_name); +void acpi_scan_hotplug_enabled(struct acpi_hotplug_profile *hotplug, bool val); #ifdef CONFIG_DEBUG_FS extern struct dentry *acpi_debugfs_dir; @@ -48,6 +59,11 @@ int acpi_debugfs_init(void); #else static inline void acpi_debugfs_init(void) { return; } #endif +#ifdef CONFIG_X86_INTEL_LPSS +void acpi_lpss_init(void); +#else +static inline void acpi_lpss_init(void) {} +#endif /* -------------------------------------------------------------------------- Device Node Initialization / Removal @@ -60,7 +76,7 @@ int acpi_device_add(struct acpi_device *device, void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, int type, unsigned long long sta); void acpi_device_add_finalize(struct acpi_device *device); -void acpi_free_ids(struct acpi_device *device); +void acpi_free_pnp_ids(struct acpi_device_pnp *pnp); /* -------------------------------------------------------------------------- Power Resource @@ -131,4 +147,7 @@ static inline void suspend_nvs_restore(void) {} -------------------------------------------------------------------------- */ struct platform_device; +int acpi_create_platform_device(struct acpi_device *adev, + const struct acpi_device_id *id); + #endif /* _ACPI_INTERNAL_H_ */ diff --git a/drivers/acpi/osl.c b/drivers/acpi/osl.c index 586e7e993d3..e72186340fe 100644 --- a/drivers/acpi/osl.c +++ b/drivers/acpi/osl.c @@ -641,7 +641,7 @@ void __init acpi_initrd_override(void *data, size_t size) * Both memblock_reserve and e820_add_region (via arch_reserve_mem_area) * works fine. */ - memblock_reserve(acpi_tables_addr, acpi_tables_addr + all_tables_size); + memblock_reserve(acpi_tables_addr, all_tables_size); arch_reserve_mem_area(acpi_tables_addr, all_tables_size); p = early_ioremap(acpi_tables_addr, all_tables_size); @@ -1555,7 +1555,7 @@ int acpi_check_resource_conflict(const struct resource *res) else space_id = ACPI_ADR_SPACE_SYSTEM_MEMORY; - length = res->end - res->start + 1; + length = resource_size(res); if (acpi_enforce_resources != ENFORCE_RESOURCES_NO) warn = 1; clash = acpi_check_address_range(space_id, res->start, length, warn); diff --git a/drivers/acpi/pci_link.c b/drivers/acpi/pci_link.c index ab764ed34a5..2652a614dee 100644 --- a/drivers/acpi/pci_link.c +++ b/drivers/acpi/pci_link.c @@ -354,6 +354,7 @@ static int acpi_pci_link_set(struct acpi_pci_link *link, int irq) } resource->end.type = ACPI_RESOURCE_TYPE_END_TAG; + resource->end.length = sizeof(struct acpi_resource); /* Attempt to set the resource */ status = acpi_set_current_resources(link->device->handle, &buffer); diff --git a/drivers/acpi/pci_root.c b/drivers/acpi/pci_root.c index ac8688b8970..1dd6f6c8587 100644 --- a/drivers/acpi/pci_root.c +++ b/drivers/acpi/pci_root.c @@ -169,8 +169,8 @@ static acpi_status acpi_pci_query_osc(struct acpi_pci_root *root, *control &= OSC_PCI_CONTROL_MASKS; capbuf[OSC_CONTROL_TYPE] = *control | root->osc_control_set; } else { - /* Run _OSC query for all possible controls. */ - capbuf[OSC_CONTROL_TYPE] = OSC_PCI_CONTROL_MASKS; + /* Run _OSC query only with existing controls. */ + capbuf[OSC_CONTROL_TYPE] = root->osc_control_set; } status = acpi_pci_run_osc(root->device->handle, capbuf, &result); diff --git a/drivers/acpi/power.c b/drivers/acpi/power.c index 34f5ef11d42..f962047c6c8 100644 --- a/drivers/acpi/power.c +++ b/drivers/acpi/power.c @@ -459,57 +459,79 @@ static struct attribute_group attr_groups[] = { }, }; -static void acpi_power_hide_list(struct acpi_device *adev, int state) +static struct attribute_group wakeup_attr_group = { + .name = "power_resources_wakeup", + .attrs = attrs, +}; + +static void acpi_power_hide_list(struct acpi_device *adev, + struct list_head *resources, + struct attribute_group *attr_group) { - struct acpi_device_power_state *ps = &adev->power.states[state]; struct acpi_power_resource_entry *entry; - if (list_empty(&ps->resources)) + if (list_empty(resources)) return; - list_for_each_entry_reverse(entry, &ps->resources, node) { + list_for_each_entry_reverse(entry, resources, node) { struct acpi_device *res_dev = &entry->resource->device; sysfs_remove_link_from_group(&adev->dev.kobj, - attr_groups[state].name, + attr_group->name, dev_name(&res_dev->dev)); } - sysfs_remove_group(&adev->dev.kobj, &attr_groups[state]); + sysfs_remove_group(&adev->dev.kobj, attr_group); } -static void acpi_power_expose_list(struct acpi_device *adev, int state) +static void acpi_power_expose_list(struct acpi_device *adev, + struct list_head *resources, + struct attribute_group *attr_group) { - struct acpi_device_power_state *ps = &adev->power.states[state]; struct acpi_power_resource_entry *entry; int ret; - if (list_empty(&ps->resources)) + if (list_empty(resources)) return; - ret = sysfs_create_group(&adev->dev.kobj, &attr_groups[state]); + ret = sysfs_create_group(&adev->dev.kobj, attr_group); if (ret) return; - list_for_each_entry(entry, &ps->resources, node) { + list_for_each_entry(entry, resources, node) { struct acpi_device *res_dev = &entry->resource->device; ret = sysfs_add_link_to_group(&adev->dev.kobj, - attr_groups[state].name, + attr_group->name, &res_dev->dev.kobj, dev_name(&res_dev->dev)); if (ret) { - acpi_power_hide_list(adev, state); + acpi_power_hide_list(adev, resources, attr_group); break; } } } +static void acpi_power_expose_hide(struct acpi_device *adev, + struct list_head *resources, + struct attribute_group *attr_group, + bool expose) +{ + if (expose) + acpi_power_expose_list(adev, resources, attr_group); + else + acpi_power_hide_list(adev, resources, attr_group); +} + void acpi_power_add_remove_device(struct acpi_device *adev, bool add) { struct acpi_device_power_state *ps; struct acpi_power_resource_entry *entry; int state; + if (adev->wakeup.flags.valid) + acpi_power_expose_hide(adev, &adev->wakeup.resources, + &wakeup_attr_group, add); + if (!adev->power.flags.power_resources) return; @@ -523,12 +545,10 @@ void acpi_power_add_remove_device(struct acpi_device *adev, bool add) acpi_power_remove_dependent(resource, adev); } - for (state = ACPI_STATE_D0; state <= ACPI_STATE_D3_HOT; state++) { - if (add) - acpi_power_expose_list(adev, state); - else - acpi_power_hide_list(adev, state); - } + for (state = ACPI_STATE_D0; state <= ACPI_STATE_D3_HOT; state++) + acpi_power_expose_hide(adev, + &adev->power.states[state].resources, + &attr_groups[state], add); } int acpi_power_wakeup_list_init(struct list_head *list, int *system_level_p) @@ -824,7 +844,7 @@ static void acpi_release_power_resource(struct device *dev) list_del(&resource->list_node); mutex_unlock(&power_resource_list_lock); - acpi_free_ids(device); + acpi_free_pnp_ids(&device->pnp); kfree(resource); } diff --git a/drivers/acpi/processor_idle.c b/drivers/acpi/processor_idle.c index ee255c60bda..f0df2c9434d 100644 --- a/drivers/acpi/processor_idle.c +++ b/drivers/acpi/processor_idle.c @@ -918,7 +918,6 @@ static int acpi_idle_enter_bm(struct cpuidle_device *dev, struct cpuidle_driver acpi_idle_driver = { .name = "acpi_idle", .owner = THIS_MODULE, - .en_core_tk_irqen = 1, }; /** diff --git a/drivers/acpi/processor_thermal.c b/drivers/acpi/processor_thermal.c index 641b5450a0d..e8e652710e6 100644 --- a/drivers/acpi/processor_thermal.c +++ b/drivers/acpi/processor_thermal.c @@ -218,9 +218,13 @@ processor_get_max_state(struct thermal_cooling_device *cdev, unsigned long *state) { struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr = acpi_driver_data(device); + struct acpi_processor *pr; - if (!device || !pr) + if (!device) + return -EINVAL; + + pr = acpi_driver_data(device); + if (!pr) return -EINVAL; *state = acpi_processor_max_state(pr); @@ -232,9 +236,13 @@ processor_get_cur_state(struct thermal_cooling_device *cdev, unsigned long *cur_state) { struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr = acpi_driver_data(device); + struct acpi_processor *pr; - if (!device || !pr) + if (!device) + return -EINVAL; + + pr = acpi_driver_data(device); + if (!pr) return -EINVAL; *cur_state = cpufreq_get_cur_state(pr->id); @@ -248,11 +256,15 @@ processor_set_cur_state(struct thermal_cooling_device *cdev, unsigned long state) { struct acpi_device *device = cdev->devdata; - struct acpi_processor *pr = acpi_driver_data(device); + struct acpi_processor *pr; int result = 0; int max_pstate; - if (!device || !pr) + if (!device) + return -EINVAL; + + pr = acpi_driver_data(device); + if (!pr) return -EINVAL; max_pstate = cpufreq_get_max_state(pr->id); diff --git a/drivers/acpi/processor_throttling.c b/drivers/acpi/processor_throttling.c index 1d02b7b5ade..e7dd2c1fee7 100644 --- a/drivers/acpi/processor_throttling.c +++ b/drivers/acpi/processor_throttling.c @@ -211,9 +211,10 @@ err_ret: */ void acpi_processor_throttling_init(void) { - if (acpi_processor_update_tsd_coord()) + if (acpi_processor_update_tsd_coord()) { ACPI_DEBUG_PRINT((ACPI_DB_INFO, "Assume no T-state coordination\n")); + } return; } diff --git a/drivers/acpi/scan.c b/drivers/acpi/scan.c index f54d1985e59..fe158fd4f1d 100644 --- a/drivers/acpi/scan.c +++ b/drivers/acpi/scan.c @@ -63,6 +63,19 @@ int acpi_scan_add_handler(struct acpi_scan_handler *handler) return 0; } +int acpi_scan_add_handler_with_hotplug(struct acpi_scan_handler *handler, + const char *hotplug_profile_name) +{ + int error; + + error = acpi_scan_add_handler(handler); + if (error) + return error; + + acpi_sysfs_add_hotplug_profile(&handler->hotplug, hotplug_profile_name); + return 0; +} + /* * Creates hid/cid(s) string needed for modalias and uevent * e.g. on a device with hid:IBM0001 and cid:ACPI0001 you get: @@ -107,32 +120,20 @@ acpi_device_modalias_show(struct device *dev, struct device_attribute *attr, cha } static DEVICE_ATTR(modalias, 0444, acpi_device_modalias_show, NULL); -/** - * acpi_bus_hot_remove_device: hot-remove a device and its children - * @context: struct acpi_eject_event pointer (freed in this func) - * - * Hot-remove a device and its children. This function frees up the - * memory space passed by arg context, so that the caller may call - * this function asynchronously through acpi_os_hotplug_execute(). - */ -void acpi_bus_hot_remove_device(void *context) +static int acpi_scan_hot_remove(struct acpi_device *device) { - struct acpi_eject_event *ej_event = context; - struct acpi_device *device = ej_event->device; acpi_handle handle = device->handle; - acpi_handle temp; + acpi_handle not_used; struct acpi_object_list arg_list; union acpi_object arg; - acpi_status status = AE_OK; - u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; /* default */ - - mutex_lock(&acpi_scan_lock); + acpi_status status; + unsigned long long sta; /* If there is no handle, the device node has been unregistered. */ - if (!device->handle) { + if (!handle) { dev_dbg(&device->dev, "ACPI handle missing\n"); put_device(&device->dev); - goto out; + return -EINVAL; } ACPI_DEBUG_PRINT((ACPI_DB_INFO, @@ -143,7 +144,7 @@ void acpi_bus_hot_remove_device(void *context) put_device(&device->dev); device = NULL; - if (ACPI_SUCCESS(acpi_get_handle(handle, "_LCK", &temp))) { + if (ACPI_SUCCESS(acpi_get_handle(handle, "_LCK", ¬_used))) { arg_list.count = 1; arg_list.pointer = &arg; arg.type = ACPI_TYPE_INTEGER; @@ -161,18 +162,205 @@ void acpi_bus_hot_remove_device(void *context) */ status = acpi_evaluate_object(handle, "_EJ0", &arg_list, NULL); if (ACPI_FAILURE(status)) { - if (status != AE_NOT_FOUND) - acpi_handle_warn(handle, "Eject failed\n"); + if (status == AE_NOT_FOUND) { + return -ENODEV; + } else { + acpi_handle_warn(handle, "Eject failed (0x%x)\n", + status); + return -EIO; + } + } - /* Tell the firmware the hot-remove operation has failed. */ - acpi_evaluate_hotplug_ost(handle, ej_event->event, - ost_code, NULL); + /* + * Verify if eject was indeed successful. If not, log an error + * message. No need to call _OST since _EJ0 call was made OK. + */ + status = acpi_evaluate_integer(handle, "_STA", NULL, &sta); + if (ACPI_FAILURE(status)) { + acpi_handle_warn(handle, + "Status check after eject failed (0x%x)\n", status); + } else if (sta & ACPI_STA_DEVICE_ENABLED) { + acpi_handle_warn(handle, + "Eject incomplete - status 0x%llx\n", sta); + } + + return 0; +} + +static void acpi_bus_device_eject(void *context) +{ + acpi_handle handle = context; + struct acpi_device *device = NULL; + struct acpi_scan_handler *handler; + u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; + + mutex_lock(&acpi_scan_lock); + + acpi_bus_get_device(handle, &device); + if (!device) + goto err_out; + + handler = device->handler; + if (!handler || !handler->hotplug.enabled) { + ost_code = ACPI_OST_SC_EJECT_NOT_SUPPORTED; + goto err_out; + } + acpi_evaluate_hotplug_ost(handle, ACPI_NOTIFY_EJECT_REQUEST, + ACPI_OST_SC_EJECT_IN_PROGRESS, NULL); + if (handler->hotplug.mode == AHM_CONTAINER) { + device->flags.eject_pending = true; + kobject_uevent(&device->dev.kobj, KOBJ_OFFLINE); + } else { + int error; + + get_device(&device->dev); + error = acpi_scan_hot_remove(device); + if (error) + goto err_out; } out: mutex_unlock(&acpi_scan_lock); - kfree(context); return; + + err_out: + acpi_evaluate_hotplug_ost(handle, ACPI_NOTIFY_EJECT_REQUEST, ost_code, + NULL); + goto out; +} + +static void acpi_scan_bus_device_check(acpi_handle handle, u32 ost_source) +{ + struct acpi_device *device = NULL; + u32 ost_code = ACPI_OST_SC_NON_SPECIFIC_FAILURE; + int error; + + mutex_lock(&acpi_scan_lock); + + acpi_bus_get_device(handle, &device); + if (device) { + dev_warn(&device->dev, "Attempt to re-insert\n"); + goto out; + } + acpi_evaluate_hotplug_ost(handle, ost_source, + ACPI_OST_SC_INSERT_IN_PROGRESS, NULL); + error = acpi_bus_scan(handle); + if (error) { + acpi_handle_warn(handle, "Namespace scan failure\n"); + goto out; + } + error = acpi_bus_get_device(handle, &device); + if (error) { + acpi_handle_warn(handle, "Missing device node object\n"); + goto out; + } + ost_code = ACPI_OST_SC_SUCCESS; + if (device->handler && device->handler->hotplug.mode == AHM_CONTAINER) + kobject_uevent(&device->dev.kobj, KOBJ_ONLINE); + + out: + acpi_evaluate_hotplug_ost(handle, ost_source, ost_code, NULL); + mutex_unlock(&acpi_scan_lock); +} + +static void acpi_scan_bus_check(void *context) +{ + acpi_scan_bus_device_check((acpi_handle)context, + ACPI_NOTIFY_BUS_CHECK); +} + +static void acpi_scan_device_check(void *context) +{ + acpi_scan_bus_device_check((acpi_handle)context, + ACPI_NOTIFY_DEVICE_CHECK); +} + +static void acpi_hotplug_unsupported(acpi_handle handle, u32 type) +{ + u32 ost_status; + + switch (type) { + case ACPI_NOTIFY_BUS_CHECK: + acpi_handle_debug(handle, + "ACPI_NOTIFY_BUS_CHECK event: unsupported\n"); + ost_status = ACPI_OST_SC_INSERT_NOT_SUPPORTED; + break; + case ACPI_NOTIFY_DEVICE_CHECK: + acpi_handle_debug(handle, + "ACPI_NOTIFY_DEVICE_CHECK event: unsupported\n"); + ost_status = ACPI_OST_SC_INSERT_NOT_SUPPORTED; + break; + case ACPI_NOTIFY_EJECT_REQUEST: + acpi_handle_debug(handle, + "ACPI_NOTIFY_EJECT_REQUEST event: unsupported\n"); + ost_status = ACPI_OST_SC_EJECT_NOT_SUPPORTED; + break; + default: + /* non-hotplug event; possibly handled by other handler */ + return; + } + + acpi_evaluate_hotplug_ost(handle, type, ost_status, NULL); +} + +static void acpi_hotplug_notify_cb(acpi_handle handle, u32 type, void *data) +{ + acpi_osd_exec_callback callback; + struct acpi_scan_handler *handler = data; + acpi_status status; + + if (!handler->hotplug.enabled) + return acpi_hotplug_unsupported(handle, type); + + switch (type) { + case ACPI_NOTIFY_BUS_CHECK: + acpi_handle_debug(handle, "ACPI_NOTIFY_BUS_CHECK event\n"); + callback = acpi_scan_bus_check; + break; + case ACPI_NOTIFY_DEVICE_CHECK: + acpi_handle_debug(handle, "ACPI_NOTIFY_DEVICE_CHECK event\n"); + callback = acpi_scan_device_check; + break; + case ACPI_NOTIFY_EJECT_REQUEST: + acpi_handle_debug(handle, "ACPI_NOTIFY_EJECT_REQUEST event\n"); + callback = acpi_bus_device_eject; + break; + default: + /* non-hotplug event; possibly handled by other handler */ + return; + } + status = acpi_os_hotplug_execute(callback, handle); + if (ACPI_FAILURE(status)) + acpi_evaluate_hotplug_ost(handle, type, + ACPI_OST_SC_NON_SPECIFIC_FAILURE, + NULL); +} + +/** + * acpi_bus_hot_remove_device: hot-remove a device and its children + * @context: struct acpi_eject_event pointer (freed in this func) + * + * Hot-remove a device and its children. This function frees up the + * memory space passed by arg context, so that the caller may call + * this function asynchronously through acpi_os_hotplug_execute(). + */ +void acpi_bus_hot_remove_device(void *context) +{ + struct acpi_eject_event *ej_event = context; + struct acpi_device *device = ej_event->device; + acpi_handle handle = device->handle; + int error; + + mutex_lock(&acpi_scan_lock); + + error = acpi_scan_hot_remove(device); + if (error && handle) + acpi_evaluate_hotplug_ost(handle, ej_event->event, + ACPI_OST_SC_NON_SPECIFIC_FAILURE, + NULL); + + mutex_unlock(&acpi_scan_lock); + kfree(context); } EXPORT_SYMBOL(acpi_bus_hot_remove_device); @@ -206,51 +394,61 @@ static ssize_t acpi_eject_store(struct device *d, struct device_attribute *attr, const char *buf, size_t count) { - int ret = count; - acpi_status status; - acpi_object_type type = 0; struct acpi_device *acpi_device = to_acpi_device(d); struct acpi_eject_event *ej_event; + acpi_object_type not_used; + acpi_status status; + u32 ost_source; + int ret; - if ((!count) || (buf[0] != '1')) { + if (!count || buf[0] != '1') return -EINVAL; - } - if (!acpi_device->driver && !acpi_device->handler) { - ret = -ENODEV; - goto err; - } - status = acpi_get_type(acpi_device->handle, &type); - if (ACPI_FAILURE(status) || (!acpi_device->flags.ejectable)) { - ret = -ENODEV; - goto err; - } - ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL); - if (!ej_event) { - ret = -ENOMEM; - goto err; - } + if ((!acpi_device->handler || !acpi_device->handler->hotplug.enabled) + && !acpi_device->driver) + return -ENODEV; + + status = acpi_get_type(acpi_device->handle, ¬_used); + if (ACPI_FAILURE(status) || !acpi_device->flags.ejectable) + return -ENODEV; + + mutex_lock(&acpi_scan_lock); - get_device(&acpi_device->dev); - ej_event->device = acpi_device; if (acpi_device->flags.eject_pending) { - /* event originated from ACPI eject notification */ - ej_event->event = ACPI_NOTIFY_EJECT_REQUEST; + /* ACPI eject notification event. */ + ost_source = ACPI_NOTIFY_EJECT_REQUEST; acpi_device->flags.eject_pending = 0; } else { - /* event originated from user */ - ej_event->event = ACPI_OST_EC_OSPM_EJECT; - (void) acpi_evaluate_hotplug_ost(acpi_device->handle, - ej_event->event, ACPI_OST_SC_EJECT_IN_PROGRESS, NULL); + /* Eject initiated by user space. */ + ost_source = ACPI_OST_EC_OSPM_EJECT; } - + ej_event = kmalloc(sizeof(*ej_event), GFP_KERNEL); + if (!ej_event) { + ret = -ENOMEM; + goto err_out; + } + acpi_evaluate_hotplug_ost(acpi_device->handle, ost_source, + ACPI_OST_SC_EJECT_IN_PROGRESS, NULL); + ej_event->device = acpi_device; + ej_event->event = ost_source; + get_device(&acpi_device->dev); status = acpi_os_hotplug_execute(acpi_bus_hot_remove_device, ej_event); if (ACPI_FAILURE(status)) { put_device(&acpi_device->dev); kfree(ej_event); + ret = status == AE_NO_MEMORY ? -ENOMEM : -EAGAIN; + goto err_out; } -err: + ret = count; + + out: + mutex_unlock(&acpi_scan_lock); return ret; + + err_out: + acpi_evaluate_hotplug_ost(acpi_device->handle, ost_source, + ACPI_OST_SC_NON_SPECIFIC_FAILURE, NULL); + goto out; } static DEVICE_ATTR(eject, 0200, NULL, acpi_eject_store); @@ -376,7 +574,7 @@ static int acpi_device_setup_files(struct acpi_device *dev) goto end; } - if (dev->flags.bus_address) + if (dev->pnp.type.bus_address) result = device_create_file(&dev->dev, &dev_attr_adr); if (dev->pnp.unique_id) result = device_create_file(&dev->dev, &dev_attr_uid); @@ -449,7 +647,7 @@ static void acpi_device_remove_files(struct acpi_device *dev) if (dev->pnp.unique_id) device_remove_file(&dev->dev, &dev_attr_uid); - if (dev->flags.bus_address) + if (dev->pnp.type.bus_address) device_remove_file(&dev->dev, &dev_attr_adr); device_remove_file(&dev->dev, &dev_attr_modalias); device_remove_file(&dev->dev, &dev_attr_hid); @@ -512,17 +710,6 @@ int acpi_match_device_ids(struct acpi_device *device, } EXPORT_SYMBOL(acpi_match_device_ids); -void acpi_free_ids(struct acpi_device *device) -{ - struct acpi_hardware_id *id, *tmp; - - list_for_each_entry_safe(id, tmp, &device->pnp.ids, list) { - kfree(id->id); - kfree(id); - } - kfree(device->pnp.unique_id); -} - static void acpi_free_power_resources_lists(struct acpi_device *device) { int i; @@ -543,7 +730,7 @@ static void acpi_device_release(struct device *dev) { struct acpi_device *acpi_dev = to_acpi_device(dev); - acpi_free_ids(acpi_dev); + acpi_free_pnp_ids(&acpi_dev->pnp); acpi_free_power_resources_lists(acpi_dev); kfree(acpi_dev); } @@ -1256,19 +1443,17 @@ static void acpi_device_get_busid(struct acpi_device *device) } /* - * acpi_bay_match - see if a device is an ejectable driver bay + * acpi_bay_match - see if an acpi object is an ejectable driver bay * * If an acpi object is ejectable and has one of the ACPI ATA methods defined, * then we can safely call it an ejectable drive bay */ -static int acpi_bay_match(struct acpi_device *device){ +static int acpi_bay_match(acpi_handle handle) +{ acpi_status status; - acpi_handle handle; acpi_handle tmp; acpi_handle phandle; - handle = device->handle; - status = acpi_get_handle(handle, "_EJ0", &tmp); if (ACPI_FAILURE(status)) return -ENODEV; @@ -1292,12 +1477,12 @@ static int acpi_bay_match(struct acpi_device *device){ } /* - * acpi_dock_match - see if a device has a _DCK method + * acpi_dock_match - see if an acpi object has a _DCK method */ -static int acpi_dock_match(struct acpi_device *device) +static int acpi_dock_match(acpi_handle handle) { acpi_handle tmp; - return acpi_get_handle(device->handle, "_DCK", &tmp); + return acpi_get_handle(handle, "_DCK", &tmp); } const char *acpi_device_hid(struct acpi_device *device) @@ -1312,7 +1497,7 @@ const char *acpi_device_hid(struct acpi_device *device) } EXPORT_SYMBOL(acpi_device_hid); -static void acpi_add_id(struct acpi_device *device, const char *dev_id) +static void acpi_add_id(struct acpi_device_pnp *pnp, const char *dev_id) { struct acpi_hardware_id *id; @@ -1326,7 +1511,8 @@ static void acpi_add_id(struct acpi_device *device, const char *dev_id) return; } - list_add_tail(&id->list, &device->pnp.ids); + list_add_tail(&id->list, &pnp->ids); + pnp->type.hardware_id = 1; } /* @@ -1334,7 +1520,7 @@ static void acpi_add_id(struct acpi_device *device, const char *dev_id) * lacks the SMBUS01 HID and the methods do not have the necessary "_" * prefix. Work around this. */ -static int acpi_ibm_smbus_match(struct acpi_device *device) +static int acpi_ibm_smbus_match(acpi_handle handle) { acpi_handle h_dummy; struct acpi_buffer path = {ACPI_ALLOCATE_BUFFER, NULL}; @@ -1344,7 +1530,7 @@ static int acpi_ibm_smbus_match(struct acpi_device *device) return -ENODEV; /* Look for SMBS object */ - result = acpi_get_name(device->handle, ACPI_SINGLE_NAME, &path); + result = acpi_get_name(handle, ACPI_SINGLE_NAME, &path); if (result) return result; @@ -1355,48 +1541,50 @@ static int acpi_ibm_smbus_match(struct acpi_device *device) /* Does it have the necessary (but misnamed) methods? */ result = -ENODEV; - if (ACPI_SUCCESS(acpi_get_handle(device->handle, "SBI", &h_dummy)) && - ACPI_SUCCESS(acpi_get_handle(device->handle, "SBR", &h_dummy)) && - ACPI_SUCCESS(acpi_get_handle(device->handle, "SBW", &h_dummy))) + if (ACPI_SUCCESS(acpi_get_handle(handle, "SBI", &h_dummy)) && + ACPI_SUCCESS(acpi_get_handle(handle, "SBR", &h_dummy)) && + ACPI_SUCCESS(acpi_get_handle(handle, "SBW", &h_dummy))) result = 0; out: kfree(path.pointer); return result; } -static void acpi_device_set_id(struct acpi_device *device) +static void acpi_set_pnp_ids(acpi_handle handle, struct acpi_device_pnp *pnp, + int device_type) { acpi_status status; struct acpi_device_info *info; struct acpi_pnp_device_id_list *cid_list; int i; - switch (device->device_type) { + switch (device_type) { case ACPI_BUS_TYPE_DEVICE: - if (ACPI_IS_ROOT_DEVICE(device)) { - acpi_add_id(device, ACPI_SYSTEM_HID); + if (handle == ACPI_ROOT_OBJECT) { + acpi_add_id(pnp, ACPI_SYSTEM_HID); break; } - status = acpi_get_object_info(device->handle, &info); + status = acpi_get_object_info(handle, &info); if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX "%s: Error reading device info\n", __func__); + pr_err(PREFIX "%s: Error reading device info\n", + __func__); return; } if (info->valid & ACPI_VALID_HID) - acpi_add_id(device, info->hardware_id.string); + acpi_add_id(pnp, info->hardware_id.string); if (info->valid & ACPI_VALID_CID) { cid_list = &info->compatible_id_list; for (i = 0; i < cid_list->count; i++) - acpi_add_id(device, cid_list->ids[i].string); + acpi_add_id(pnp, cid_list->ids[i].string); } if (info->valid & ACPI_VALID_ADR) { - device->pnp.bus_address = info->address; - device->flags.bus_address = 1; + pnp->bus_address = info->address; + pnp->type.bus_address = 1; } if (info->valid & ACPI_VALID_UID) - device->pnp.unique_id = kstrdup(info->unique_id.string, + pnp->unique_id = kstrdup(info->unique_id.string, GFP_KERNEL); kfree(info); @@ -1405,40 +1593,50 @@ static void acpi_device_set_id(struct acpi_device *device) * Some devices don't reliably have _HIDs & _CIDs, so add * synthetic HIDs to make sure drivers can find them. */ - if (acpi_is_video_device(device)) - acpi_add_id(device, ACPI_VIDEO_HID); - else if (ACPI_SUCCESS(acpi_bay_match(device))) - acpi_add_id(device, ACPI_BAY_HID); - else if (ACPI_SUCCESS(acpi_dock_match(device))) - acpi_add_id(device, ACPI_DOCK_HID); - else if (!acpi_ibm_smbus_match(device)) - acpi_add_id(device, ACPI_SMBUS_IBM_HID); - else if (list_empty(&device->pnp.ids) && - ACPI_IS_ROOT_DEVICE(device->parent)) { - acpi_add_id(device, ACPI_BUS_HID); /* \_SB, LNXSYBUS */ - strcpy(device->pnp.device_name, ACPI_BUS_DEVICE_NAME); - strcpy(device->pnp.device_class, ACPI_BUS_CLASS); + if (acpi_is_video_device(handle)) + acpi_add_id(pnp, ACPI_VIDEO_HID); + else if (ACPI_SUCCESS(acpi_bay_match(handle))) + acpi_add_id(pnp, ACPI_BAY_HID); + else if (ACPI_SUCCESS(acpi_dock_match(handle))) + acpi_add_id(pnp, ACPI_DOCK_HID); + else if (!acpi_ibm_smbus_match(handle)) + acpi_add_id(pnp, ACPI_SMBUS_IBM_HID); + else if (list_empty(&pnp->ids) && handle == ACPI_ROOT_OBJECT) { + acpi_add_id(pnp, ACPI_BUS_HID); /* \_SB, LNXSYBUS */ + strcpy(pnp->device_name, ACPI_BUS_DEVICE_NAME); + strcpy(pnp->device_class, ACPI_BUS_CLASS); } break; case ACPI_BUS_TYPE_POWER: - acpi_add_id(device, ACPI_POWER_HID); + acpi_add_id(pnp, ACPI_POWER_HID); break; case ACPI_BUS_TYPE_PROCESSOR: - acpi_add_id(device, ACPI_PROCESSOR_OBJECT_HID); + acpi_add_id(pnp, ACPI_PROCESSOR_OBJECT_HID); break; case ACPI_BUS_TYPE_THERMAL: - acpi_add_id(device, ACPI_THERMAL_HID); + acpi_add_id(pnp, ACPI_THERMAL_HID); break; case ACPI_BUS_TYPE_POWER_BUTTON: - acpi_add_id(device, ACPI_BUTTON_HID_POWERF); + acpi_add_id(pnp, ACPI_BUTTON_HID_POWERF); break; case ACPI_BUS_TYPE_SLEEP_BUTTON: - acpi_add_id(device, ACPI_BUTTON_HID_SLEEPF); + acpi_add_id(pnp, ACPI_BUTTON_HID_SLEEPF); break; } } +void acpi_free_pnp_ids(struct acpi_device_pnp *pnp) +{ + struct acpi_hardware_id *id, *tmp; + + list_for_each_entry_safe(id, tmp, &pnp->ids, list) { + kfree(id->id); + kfree(id); + } + kfree(pnp->unique_id); +} + void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, int type, unsigned long long sta) { @@ -1448,7 +1646,7 @@ void acpi_init_device_object(struct acpi_device *device, acpi_handle handle, device->parent = acpi_bus_get_parent(handle); STRUCT_TO_INT(device->status) = sta; acpi_device_get_busid(device); - acpi_device_set_id(device); + acpi_set_pnp_ids(handle, &device->pnp, type); acpi_bus_get_flags(device); device->flags.match_driver = false; device_initialize(&device->dev); @@ -1536,6 +1734,75 @@ static int acpi_bus_type_and_status(acpi_handle handle, int *type, return 0; } +static bool acpi_scan_handler_matching(struct acpi_scan_handler *handler, + char *idstr, + const struct acpi_device_id **matchid) +{ + const struct acpi_device_id *devid; + + for (devid = handler->ids; devid->id[0]; devid++) + if (!strcmp((char *)devid->id, idstr)) { + if (matchid) + *matchid = devid; + + return true; + } + + return false; +} + +static struct acpi_scan_handler *acpi_scan_match_handler(char *idstr, + const struct acpi_device_id **matchid) +{ + struct acpi_scan_handler *handler; + + list_for_each_entry(handler, &acpi_scan_handlers_list, list_node) + if (acpi_scan_handler_matching(handler, idstr, matchid)) + return handler; + + return NULL; +} + +void acpi_scan_hotplug_enabled(struct acpi_hotplug_profile *hotplug, bool val) +{ + if (!!hotplug->enabled == !!val) + return; + + mutex_lock(&acpi_scan_lock); + + hotplug->enabled = val; + + mutex_unlock(&acpi_scan_lock); +} + +static void acpi_scan_init_hotplug(acpi_handle handle, int type) +{ + struct acpi_device_pnp pnp = {}; + struct acpi_hardware_id *hwid; + struct acpi_scan_handler *handler; + + INIT_LIST_HEAD(&pnp.ids); + acpi_set_pnp_ids(handle, &pnp, type); + + if (!pnp.type.hardware_id) + return; + + /* + * This relies on the fact that acpi_install_notify_handler() will not + * install the same notify handler routine twice for the same handle. + */ + list_for_each_entry(hwid, &pnp.ids, list) { + handler = acpi_scan_match_handler(hwid->id, NULL); + if (handler) { + acpi_install_notify_handler(handle, ACPI_SYSTEM_NOTIFY, + acpi_hotplug_notify_cb, handler); + break; + } + } + + acpi_free_pnp_ids(&pnp); +} + static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used, void *not_used, void **return_value) { @@ -1558,6 +1825,8 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used, return AE_OK; } + acpi_scan_init_hotplug(handle, type); + if (!(sta & ACPI_STA_DEVICE_PRESENT) && !(sta & ACPI_STA_DEVICE_FUNCTIONING)) { struct acpi_device_wakeup wakeup; @@ -1583,42 +1852,26 @@ static acpi_status acpi_bus_check_add(acpi_handle handle, u32 lvl_not_used, return AE_OK; } -static int acpi_scan_do_attach_handler(struct acpi_device *device, char *id) +static int acpi_scan_attach_handler(struct acpi_device *device) { - struct acpi_scan_handler *handler; + struct acpi_hardware_id *hwid; + int ret = 0; - list_for_each_entry(handler, &acpi_scan_handlers_list, list_node) { + list_for_each_entry(hwid, &device->pnp.ids, list) { const struct acpi_device_id *devid; + struct acpi_scan_handler *handler; - for (devid = handler->ids; devid->id[0]; devid++) { - int ret; - - if (strcmp((char *)devid->id, id)) - continue; - + handler = acpi_scan_match_handler(hwid->id, &devid); + if (handler) { ret = handler->attach(device, devid); if (ret > 0) { device->handler = handler; - return ret; + break; } else if (ret < 0) { - return ret; + break; } } } - return 0; -} - -static int acpi_scan_attach_handler(struct acpi_device *device) -{ - struct acpi_hardware_id *hwid; - int ret = 0; - - list_for_each_entry(hwid, &device->pnp.ids, list) { - ret = acpi_scan_do_attach_handler(device, hwid->id); - if (ret) - break; - - } return ret; } @@ -1788,8 +2041,10 @@ int __init acpi_scan_init(void) acpi_pci_root_init(); acpi_pci_link_init(); acpi_platform_init(); + acpi_lpss_init(); acpi_csrt_init(); acpi_container_init(); + acpi_memory_hotplug_init(); mutex_lock(&acpi_scan_lock); /* diff --git a/drivers/acpi/sysfs.c b/drivers/acpi/sysfs.c index 41c0504470d..fcae5fa2e1b 100644 --- a/drivers/acpi/sysfs.c +++ b/drivers/acpi/sysfs.c @@ -7,6 +7,8 @@ #include <linux/moduleparam.h> #include <acpi/acpi_drivers.h> +#include "internal.h" + #define _COMPONENT ACPI_SYSTEM_COMPONENT ACPI_MODULE_NAME("sysfs"); @@ -249,6 +251,7 @@ module_param_call(acpica_version, NULL, param_get_acpica_version, NULL, 0444); static LIST_HEAD(acpi_table_attr_list); static struct kobject *tables_kobj; static struct kobject *dynamic_tables_kobj; +static struct kobject *hotplug_kobj; struct acpi_table_attr { struct bin_attribute attr; @@ -716,6 +719,67 @@ acpi_show_profile(struct device *dev, struct device_attribute *attr, static const struct device_attribute pm_profile_attr = __ATTR(pm_profile, S_IRUGO, acpi_show_profile, NULL); +static ssize_t hotplug_enabled_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct acpi_hotplug_profile *hotplug = to_acpi_hotplug_profile(kobj); + + return sprintf(buf, "%d\n", hotplug->enabled); +} + +static ssize_t hotplug_enabled_store(struct kobject *kobj, + struct kobj_attribute *attr, + const char *buf, size_t size) +{ + struct acpi_hotplug_profile *hotplug = to_acpi_hotplug_profile(kobj); + unsigned int val; + + if (kstrtouint(buf, 10, &val) || val > 1) + return -EINVAL; + + acpi_scan_hotplug_enabled(hotplug, val); + return size; +} + +static struct kobj_attribute hotplug_enabled_attr = + __ATTR(enabled, S_IRUGO | S_IWUSR, hotplug_enabled_show, + hotplug_enabled_store); + +static struct attribute *hotplug_profile_attrs[] = { + &hotplug_enabled_attr.attr, + NULL +}; + +static struct kobj_type acpi_hotplug_profile_ktype = { + .sysfs_ops = &kobj_sysfs_ops, + .default_attrs = hotplug_profile_attrs, +}; + +void acpi_sysfs_add_hotplug_profile(struct acpi_hotplug_profile *hotplug, + const char *name) +{ + int error; + + if (!hotplug_kobj) + goto err_out; + + kobject_init(&hotplug->kobj, &acpi_hotplug_profile_ktype); + error = kobject_set_name(&hotplug->kobj, "%s", name); + if (error) + goto err_out; + + hotplug->kobj.parent = hotplug_kobj; + error = kobject_add(&hotplug->kobj, hotplug_kobj, NULL); + if (error) + goto err_out; + + kobject_uevent(&hotplug->kobj, KOBJ_ADD); + return; + + err_out: + pr_err(PREFIX "Unable to add hotplug profile '%s'\n", name); +} + int __init acpi_sysfs_init(void) { int result; @@ -723,6 +787,8 @@ int __init acpi_sysfs_init(void) result = acpi_tables_sysfs_init(); if (result) return result; + + hotplug_kobj = kobject_create_and_add("hotplug", acpi_kobj); result = sysfs_create_file(acpi_kobj, &pm_profile_attr.attr); return result; } diff --git a/drivers/acpi/thermal.c b/drivers/acpi/thermal.c index 8470771e5ea..a33821ca389 100644 --- a/drivers/acpi/thermal.c +++ b/drivers/acpi/thermal.c @@ -723,9 +723,19 @@ static int thermal_get_trend(struct thermal_zone_device *thermal, return -EINVAL; if (type == THERMAL_TRIP_ACTIVE) { - /* aggressive active cooling */ - *trend = THERMAL_TREND_RAISING; - return 0; + unsigned long trip_temp; + unsigned long temp = KELVIN_TO_MILLICELSIUS(tz->temperature, + tz->kelvin_offset); + if (thermal_get_trip_temp(thermal, trip, &trip_temp)) + return -EINVAL; + + if (temp > trip_temp) { + *trend = THERMAL_TREND_RAISING; + return 0; + } else { + /* Fall back on default trend */ + return -EINVAL; + } } /* diff --git a/drivers/acpi/video.c b/drivers/acpi/video.c index 313f959413d..c3932d0876e 100644 --- a/drivers/acpi/video.c +++ b/drivers/acpi/video.c @@ -167,7 +167,8 @@ struct acpi_video_device_flags { u8 dvi:1; u8 bios:1; u8 unknown:1; - u8 reserved:2; + u8 notify:1; + u8 reserved:1; }; struct acpi_video_device_cap { @@ -222,7 +223,7 @@ static int acpi_video_device_lcd_set_level(struct acpi_video_device *device, int level); static int acpi_video_device_lcd_get_level_current( struct acpi_video_device *device, - unsigned long long *level, int init); + unsigned long long *level, bool raw); static int acpi_video_get_next_level(struct acpi_video_device *device, u32 level_current, u32 event); static int acpi_video_switch_brightness(struct acpi_video_device *device, @@ -236,7 +237,7 @@ static int acpi_video_get_brightness(struct backlight_device *bd) struct acpi_video_device *vd = (struct acpi_video_device *)bl_get_data(bd); - if (acpi_video_device_lcd_get_level_current(vd, &cur_level, 0)) + if (acpi_video_device_lcd_get_level_current(vd, &cur_level, false)) return -EINVAL; for (i = 2; i < vd->brightness->count; i++) { if (vd->brightness->levels[i] == cur_level) @@ -281,7 +282,7 @@ static int video_get_cur_state(struct thermal_cooling_device *cooling_dev, unsig unsigned long long level; int offset; - if (acpi_video_device_lcd_get_level_current(video, &level, 0)) + if (acpi_video_device_lcd_get_level_current(video, &level, false)) return -EINVAL; for (offset = 2; offset < video->brightness->count; offset++) if (level == video->brightness->levels[offset]) { @@ -447,12 +448,45 @@ static struct dmi_system_id video_dmi_table[] __initdata = { DMI_MATCH(DMI_PRODUCT_NAME, "HP Folio 13 - 2000 Notebook PC"), }, }, + { + .callback = video_ignore_initial_backlight, + .ident = "HP Pavilion dm4", + .matches = { + DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"), + DMI_MATCH(DMI_PRODUCT_NAME, "HP Pavilion dm4 Notebook PC"), + }, + }, {} }; +static unsigned long long +acpi_video_bqc_value_to_level(struct acpi_video_device *device, + unsigned long long bqc_value) +{ + unsigned long long level; + + if (device->brightness->flags._BQC_use_index) { + /* + * _BQC returns an index that doesn't account for + * the first 2 items with special meaning, so we need + * to compensate for that by offsetting ourselves + */ + if (device->brightness->flags._BCL_reversed) + bqc_value = device->brightness->count - 3 - bqc_value; + + level = device->brightness->levels[bqc_value + 2]; + } else { + level = bqc_value; + } + + level += bqc_offset_aml_bug_workaround; + + return level; +} + static int acpi_video_device_lcd_get_level_current(struct acpi_video_device *device, - unsigned long long *level, int init) + unsigned long long *level, bool raw) { acpi_status status = AE_OK; int i; @@ -463,29 +497,30 @@ acpi_video_device_lcd_get_level_current(struct acpi_video_device *device, status = acpi_evaluate_integer(device->dev->handle, buf, NULL, level); if (ACPI_SUCCESS(status)) { - if (device->brightness->flags._BQC_use_index) { - if (device->brightness->flags._BCL_reversed) - *level = device->brightness->count - - 3 - (*level); - *level = device->brightness->levels[*level + 2]; - + if (raw) { + /* + * Caller has indicated he wants the raw + * value returned by _BQC, so don't furtherly + * mess with the value. + */ + return 0; } - *level += bqc_offset_aml_bug_workaround; + + *level = acpi_video_bqc_value_to_level(device, *level); + for (i = 2; i < device->brightness->count; i++) if (device->brightness->levels[i] == *level) { device->brightness->curr = *level; return 0; } - if (!init) { - /* - * BQC returned an invalid level. - * Stop using it. - */ - ACPI_WARNING((AE_INFO, - "%s returned an invalid level", - buf)); - device->cap._BQC = device->cap._BCQ = 0; - } + /* + * BQC returned an invalid level. + * Stop using it. + */ + ACPI_WARNING((AE_INFO, + "%s returned an invalid level", + buf)); + device->cap._BQC = device->cap._BCQ = 0; } else { /* Fixme: * should we return an error or ignore this failure? @@ -598,6 +633,56 @@ acpi_video_cmp_level(const void *a, const void *b) } /* + * Decides if _BQC/_BCQ for this system is usable + * + * We do this by changing the level first and then read out the current + * brightness level, if the value does not match, find out if it is using + * index. If not, clear the _BQC/_BCQ capability. + */ +static int acpi_video_bqc_quirk(struct acpi_video_device *device, + int max_level, int current_level) +{ + struct acpi_video_device_brightness *br = device->brightness; + int result; + unsigned long long level; + int test_level; + + /* don't mess with existing known broken systems */ + if (bqc_offset_aml_bug_workaround) + return 0; + + /* + * Some systems always report current brightness level as maximum + * through _BQC, we need to test another value for them. + */ + test_level = current_level == max_level ? br->levels[2] : max_level; + + result = acpi_video_device_lcd_set_level(device, test_level); + if (result) + return result; + + result = acpi_video_device_lcd_get_level_current(device, &level, true); + if (result) + return result; + + if (level != test_level) { + /* buggy _BQC found, need to find out if it uses index */ + if (level < br->count) { + if (br->flags._BCL_reversed) + level = br->count - 3 - level; + if (br->levels[level + 2] == test_level) + br->flags._BQC_use_index = 1; + } + + if (!br->flags._BQC_use_index) + device->cap._BQC = device->cap._BCQ = 0; + } + + return 0; +} + + +/* * Arg: * device : video output device (LCD, CRT, ..) * @@ -703,42 +788,36 @@ acpi_video_init_brightness(struct acpi_video_device *device) if (!device->cap._BQC) goto set_level; - result = acpi_video_device_lcd_get_level_current(device, &level_old, 1); - if (result) - goto out_free_levels; - - /* - * Set the level to maximum and check if _BQC uses indexed value - */ - result = acpi_video_device_lcd_set_level(device, max_level); + result = acpi_video_device_lcd_get_level_current(device, + &level_old, true); if (result) goto out_free_levels; - result = acpi_video_device_lcd_get_level_current(device, &level, 0); + result = acpi_video_bqc_quirk(device, max_level, level_old); if (result) goto out_free_levels; + /* + * cap._BQC may get cleared due to _BQC is found to be broken + * in acpi_video_bqc_quirk, so check again here. + */ + if (!device->cap._BQC) + goto set_level; - br->flags._BQC_use_index = (level == max_level ? 0 : 1); - - if (!br->flags._BQC_use_index) { + if (use_bios_initial_backlight) { + level = acpi_video_bqc_value_to_level(device, level_old); /* - * Set the backlight to the initial state. - * On some buggy laptops, _BQC returns an uninitialized value - * when invoked for the first time, i.e. level_old is invalid. - * set the backlight to max_level in this case + * On some buggy laptops, _BQC returns an uninitialized + * value when invoked for the first time, i.e. + * level_old is invalid (no matter whether it's a level + * or an index). Set the backlight to max_level in this case. */ - if (use_bios_initial_backlight) { - for (i = 2; i < br->count; i++) - if (level_old == br->levels[i]) - level = level_old; - } - goto set_level; + for (i = 2; i < br->count; i++) + if (level_old == br->levels[i]) + break; + if (i == br->count) + level = max_level; } - if (br->flags._BCL_reversed) - level_old = (br->count - 1) - level_old; - level = br->levels[level_old]; - set_level: result = acpi_video_device_lcd_set_level(device, level); if (result) @@ -996,53 +1075,51 @@ acpi_video_bus_get_one_device(struct acpi_device *device, struct acpi_video_device *data; struct acpi_video_device_attrib* attribute; - if (!device || !video) - return -EINVAL; - status = acpi_evaluate_integer(device->handle, "_ADR", NULL, &device_id); - if (ACPI_SUCCESS(status)) { - - data = kzalloc(sizeof(struct acpi_video_device), GFP_KERNEL); - if (!data) - return -ENOMEM; - - strcpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME); - strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS); - device->driver_data = data; - - data->device_id = device_id; - data->video = video; - data->dev = device; + /* Some device omits _ADR, we skip them instead of fail */ + if (ACPI_FAILURE(status)) + return 0; - attribute = acpi_video_get_device_attr(video, device_id); + data = kzalloc(sizeof(struct acpi_video_device), GFP_KERNEL); + if (!data) + return -ENOMEM; - if((attribute != NULL) && attribute->device_id_scheme) { - switch (attribute->display_type) { - case ACPI_VIDEO_DISPLAY_CRT: - data->flags.crt = 1; - break; - case ACPI_VIDEO_DISPLAY_TV: - data->flags.tvout = 1; - break; - case ACPI_VIDEO_DISPLAY_DVI: - data->flags.dvi = 1; - break; - case ACPI_VIDEO_DISPLAY_LCD: - data->flags.lcd = 1; - break; - default: - data->flags.unknown = 1; - break; - } - if(attribute->bios_can_detect) - data->flags.bios = 1; - } else { - /* Check for legacy IDs */ - device_type = acpi_video_get_device_type(video, - device_id); - /* Ignore bits 16 and 18-20 */ - switch (device_type & 0xffe2ffff) { + strcpy(acpi_device_name(device), ACPI_VIDEO_DEVICE_NAME); + strcpy(acpi_device_class(device), ACPI_VIDEO_CLASS); + device->driver_data = data; + + data->device_id = device_id; + data->video = video; + data->dev = device; + + attribute = acpi_video_get_device_attr(video, device_id); + + if((attribute != NULL) && attribute->device_id_scheme) { + switch (attribute->display_type) { + case ACPI_VIDEO_DISPLAY_CRT: + data->flags.crt = 1; + break; + case ACPI_VIDEO_DISPLAY_TV: + data->flags.tvout = 1; + break; + case ACPI_VIDEO_DISPLAY_DVI: + data->flags.dvi = 1; + break; + case ACPI_VIDEO_DISPLAY_LCD: + data->flags.lcd = 1; + break; + default: + data->flags.unknown = 1; + break; + } + if(attribute->bios_can_detect) + data->flags.bios = 1; + } else { + /* Check for legacy IDs */ + device_type = acpi_video_get_device_type(video, device_id); + /* Ignore bits 16 and 18-20 */ + switch (device_type & 0xffe2ffff) { case ACPI_VIDEO_DISPLAY_LEGACY_MONITOR: data->flags.crt = 1; break; @@ -1054,34 +1131,24 @@ acpi_video_bus_get_one_device(struct acpi_device *device, break; default: data->flags.unknown = 1; - } } + } - acpi_video_device_bind(video, data); - acpi_video_device_find_cap(data); - - status = acpi_install_notify_handler(device->handle, - ACPI_DEVICE_NOTIFY, - acpi_video_device_notify, - data); - if (ACPI_FAILURE(status)) { - printk(KERN_ERR PREFIX - "Error installing notify handler\n"); - if(data->brightness) - kfree(data->brightness->levels); - kfree(data->brightness); - kfree(data); - return -ENODEV; - } + acpi_video_device_bind(video, data); + acpi_video_device_find_cap(data); - mutex_lock(&video->device_list_lock); - list_add_tail(&data->entry, &video->video_device_list); - mutex_unlock(&video->device_list_lock); + status = acpi_install_notify_handler(device->handle, ACPI_DEVICE_NOTIFY, + acpi_video_device_notify, data); + if (ACPI_FAILURE(status)) + dev_err(&device->dev, "Error installing notify handler\n"); + else + data->flags.notify = 1; - return 0; - } + mutex_lock(&video->device_list_lock); + list_add_tail(&data->entry, &video->video_device_list); + mutex_unlock(&video->device_list_lock); - return -ENOENT; + return status; } /* @@ -1268,7 +1335,8 @@ acpi_video_switch_brightness(struct acpi_video_device *device, int event) goto out; result = acpi_video_device_lcd_get_level_current(device, - &level_current, 0); + &level_current, + false); if (result) goto out; @@ -1373,9 +1441,8 @@ acpi_video_bus_get_devices(struct acpi_video_bus *video, status = acpi_video_bus_get_one_device(dev, video); if (status) { - printk(KERN_WARNING PREFIX - "Can't attach device\n"); - continue; + dev_err(&dev->dev, "Can't attach device\n"); + break; } } return status; @@ -1388,13 +1455,14 @@ static int acpi_video_bus_put_one_device(struct acpi_video_device *device) if (!device || !device->video) return -ENOENT; - status = acpi_remove_notify_handler(device->dev->handle, - ACPI_DEVICE_NOTIFY, - acpi_video_device_notify); - if (ACPI_FAILURE(status)) { - printk(KERN_WARNING PREFIX - "Can't remove video notify handler\n"); + if (device->flags.notify) { + status = acpi_remove_notify_handler(device->dev->handle, + ACPI_DEVICE_NOTIFY, acpi_video_device_notify); + if (ACPI_FAILURE(status)) + dev_err(&device->dev->dev, + "Can't remove video notify handler\n"); } + if (device->backlight) { backlight_device_unregister(device->backlight); device->backlight = NULL; @@ -1676,7 +1744,7 @@ static int acpi_video_bus_add(struct acpi_device *device) error = acpi_video_bus_get_devices(video, device); if (error) - goto err_free_video; + goto err_put_video; video->input = input = input_allocate_device(); if (!input) { diff --git a/drivers/acpi/video_detect.c b/drivers/acpi/video_detect.c index 4ac2593234e..66f67626f02 100644 --- a/drivers/acpi/video_detect.c +++ b/drivers/acpi/video_detect.c @@ -67,40 +67,37 @@ acpi_backlight_cap_match(acpi_handle handle, u32 level, void *context, return 0; } -/* Returns true if the device is a video device which can be handled by - * video.ko. +/* Returns true if the ACPI object is a video device which can be + * handled by video.ko. * The device will get a Linux specific CID added in scan.c to * identify the device as an ACPI graphics device * Be aware that the graphics device may not be physically present * Use acpi_video_get_capabilities() to detect general ACPI video * capabilities of present cards */ -long acpi_is_video_device(struct acpi_device *device) +long acpi_is_video_device(acpi_handle handle) { acpi_handle h_dummy; long video_caps = 0; - if (!device) - return 0; - /* Is this device able to support video switching ? */ - if (ACPI_SUCCESS(acpi_get_handle(device->handle, "_DOD", &h_dummy)) || - ACPI_SUCCESS(acpi_get_handle(device->handle, "_DOS", &h_dummy))) + if (ACPI_SUCCESS(acpi_get_handle(handle, "_DOD", &h_dummy)) || + ACPI_SUCCESS(acpi_get_handle(handle, "_DOS", &h_dummy))) video_caps |= ACPI_VIDEO_OUTPUT_SWITCHING; /* Is this device able to retrieve a video ROM ? */ - if (ACPI_SUCCESS(acpi_get_handle(device->handle, "_ROM", &h_dummy))) + if (ACPI_SUCCESS(acpi_get_handle(handle, "_ROM", &h_dummy))) video_caps |= ACPI_VIDEO_ROM_AVAILABLE; /* Is this device able to configure which video head to be POSTed ? */ - if (ACPI_SUCCESS(acpi_get_handle(device->handle, "_VPO", &h_dummy)) && - ACPI_SUCCESS(acpi_get_handle(device->handle, "_GPD", &h_dummy)) && - ACPI_SUCCESS(acpi_get_handle(device->handle, "_SPD", &h_dummy))) + if (ACPI_SUCCESS(acpi_get_handle(handle, "_VPO", &h_dummy)) && + ACPI_SUCCESS(acpi_get_handle(handle, "_GPD", &h_dummy)) && + ACPI_SUCCESS(acpi_get_handle(handle, "_SPD", &h_dummy))) video_caps |= ACPI_VIDEO_DEVICE_POSTING; /* Only check for backlight functionality if one of the above hit. */ if (video_caps) - acpi_walk_namespace(ACPI_TYPE_DEVICE, device->handle, + acpi_walk_namespace(ACPI_TYPE_DEVICE, handle, ACPI_UINT32_MAX, acpi_backlight_cap_match, NULL, &video_caps, NULL); @@ -127,7 +124,7 @@ find_video(acpi_handle handle, u32 lvl, void *context, void **rv) if (!dev) return AE_OK; pci_dev_put(dev); - *cap |= acpi_is_video_device(acpi_dev); + *cap |= acpi_is_video_device(handle); } return AE_OK; } diff --git a/drivers/ata/libata-acpi.c b/drivers/ata/libata-acpi.c index 8a52dab412e..87f2f395d79 100644 --- a/drivers/ata/libata-acpi.c +++ b/drivers/ata/libata-acpi.c @@ -17,7 +17,6 @@ #include <linux/pci.h> #include <linux/slab.h> #include <linux/pm_runtime.h> -#include <linux/pm_qos.h> #include <scsi/scsi_device.h> #include "libata.h" @@ -61,7 +60,8 @@ acpi_handle ata_ap_acpi_handle(struct ata_port *ap) if (ap->flags & ATA_FLAG_ACPI_SATA) return NULL; - return acpi_get_child(DEVICE_ACPI_HANDLE(ap->host->dev), ap->port_no); + return ap->scsi_host ? + DEVICE_ACPI_HANDLE(&ap->scsi_host->shost_gendev) : NULL; } EXPORT_SYMBOL(ata_ap_acpi_handle); @@ -77,7 +77,7 @@ acpi_handle ata_dev_acpi_handle(struct ata_device *dev) acpi_integer adr; struct ata_port *ap = dev->link->ap; - if (dev->flags & ATA_DFLAG_ACPI_DISABLED) + if (libata_noacpi || dev->flags & ATA_DFLAG_ACPI_DISABLED) return NULL; if (ap->flags & ATA_FLAG_ACPI_SATA) { @@ -240,28 +240,15 @@ void ata_acpi_dissociate(struct ata_host *host) } } -/** - * ata_acpi_gtm - execute _GTM - * @ap: target ATA port - * @gtm: out parameter for _GTM result - * - * Evaluate _GTM and store the result in @gtm. - * - * LOCKING: - * EH context. - * - * RETURNS: - * 0 on success, -ENOENT if _GTM doesn't exist, -errno on failure. - */ -int ata_acpi_gtm(struct ata_port *ap, struct ata_acpi_gtm *gtm) +static int __ata_acpi_gtm(struct ata_port *ap, acpi_handle handle, + struct ata_acpi_gtm *gtm) { struct acpi_buffer output = { .length = ACPI_ALLOCATE_BUFFER }; union acpi_object *out_obj; acpi_status status; int rc = 0; - status = acpi_evaluate_object(ata_ap_acpi_handle(ap), "_GTM", NULL, - &output); + status = acpi_evaluate_object(handle, "_GTM", NULL, &output); rc = -ENOENT; if (status == AE_NOT_FOUND) @@ -295,6 +282,27 @@ int ata_acpi_gtm(struct ata_port *ap, struct ata_acpi_gtm *gtm) return rc; } +/** + * ata_acpi_gtm - execute _GTM + * @ap: target ATA port + * @gtm: out parameter for _GTM result + * + * Evaluate _GTM and store the result in @gtm. + * + * LOCKING: + * EH context. + * + * RETURNS: + * 0 on success, -ENOENT if _GTM doesn't exist, -errno on failure. + */ +int ata_acpi_gtm(struct ata_port *ap, struct ata_acpi_gtm *gtm) +{ + if (ata_ap_acpi_handle(ap)) + return __ata_acpi_gtm(ap, ata_ap_acpi_handle(ap), gtm); + else + return -EINVAL; +} + EXPORT_SYMBOL_GPL(ata_acpi_gtm); /** @@ -1020,38 +1028,6 @@ void ata_acpi_on_disable(struct ata_device *dev) ata_acpi_clear_gtf(dev); } -static void ata_acpi_register_power_resource(struct ata_device *dev) -{ - struct scsi_device *sdev = dev->sdev; - acpi_handle handle; - - handle = ata_dev_acpi_handle(dev); - if (handle) - acpi_dev_pm_add_dependent(handle, &sdev->sdev_gendev); -} - -static void ata_acpi_unregister_power_resource(struct ata_device *dev) -{ - struct scsi_device *sdev = dev->sdev; - acpi_handle handle; - - handle = ata_dev_acpi_handle(dev); - if (handle) - acpi_dev_pm_remove_dependent(handle, &sdev->sdev_gendev); -} - -void ata_acpi_bind(struct ata_device *dev) -{ - ata_acpi_register_power_resource(dev); - if (zpodd_dev_enabled(dev)) - dev_pm_qos_expose_flags(&dev->sdev->sdev_gendev, 0); -} - -void ata_acpi_unbind(struct ata_device *dev) -{ - ata_acpi_unregister_power_resource(dev); -} - static int compat_pci_ata(struct ata_port *ap) { struct device *dev = ap->tdev.parent; @@ -1071,7 +1047,7 @@ static int compat_pci_ata(struct ata_port *ap) static int ata_acpi_bind_host(struct ata_port *ap, acpi_handle *handle) { - if (ap->flags & ATA_FLAG_ACPI_SATA) + if (libata_noacpi || ap->flags & ATA_FLAG_ACPI_SATA) return -ENODEV; *handle = acpi_get_child(DEVICE_ACPI_HANDLE(ap->tdev.parent), @@ -1080,7 +1056,7 @@ static int ata_acpi_bind_host(struct ata_port *ap, acpi_handle *handle) if (!*handle) return -ENODEV; - if (ata_acpi_gtm(ap, &ap->__acpi_init_gtm) == 0) + if (__ata_acpi_gtm(ap, *handle, &ap->__acpi_init_gtm) == 0) ap->pflags |= ATA_PFLAG_INIT_GTM_VALID; return 0; diff --git a/drivers/ata/libata-scsi.c b/drivers/ata/libata-scsi.c index ff44787e5a4..dd310b27b24 100644 --- a/drivers/ata/libata-scsi.c +++ b/drivers/ata/libata-scsi.c @@ -49,6 +49,7 @@ #include <linux/hdreg.h> #include <linux/uaccess.h> #include <linux/suspend.h> +#include <linux/pm_qos.h> #include <asm/unaligned.h> #include "libata.h" @@ -2126,7 +2127,6 @@ static unsigned int ata_scsiop_inq_89(struct ata_scsi_args *args, u8 *rbuf) memcpy(&rbuf[8], "linux ", 8); memcpy(&rbuf[16], "libata ", 16); memcpy(&rbuf[32], DRV_VERSION, 4); - ata_id_string(args->id, &rbuf[32], ATA_ID_FW_REV, 4); /* we don't store the ATA device signature, so we fake it */ @@ -3668,7 +3668,9 @@ void ata_scsi_scan_host(struct ata_port *ap, int sync) if (!IS_ERR(sdev)) { dev->sdev = sdev; scsi_device_put(sdev); - ata_acpi_bind(dev); + if (zpodd_dev_enabled(dev)) + dev_pm_qos_expose_flags( + &sdev->sdev_gendev, 0); } else { dev->sdev = NULL; } @@ -3767,7 +3769,6 @@ static void ata_scsi_remove_dev(struct ata_device *dev) if (zpodd_dev_enabled(dev)) zpodd_exit(dev); - ata_acpi_unbind(dev); /* clearing dev->sdev is protected by host lock */ sdev = dev->sdev; diff --git a/drivers/ata/pata_at32.c b/drivers/ata/pata_at32.c index 36f189c7ee8..8d493b4a096 100644 --- a/drivers/ata/pata_at32.c +++ b/drivers/ata/pata_at32.c @@ -393,18 +393,7 @@ static struct platform_driver pata_at32_driver = { }, }; -static int __init pata_at32_init(void) -{ - return platform_driver_probe(&pata_at32_driver, pata_at32_probe); -} - -static void __exit pata_at32_exit(void) -{ - platform_driver_unregister(&pata_at32_driver); -} - -module_init(pata_at32_init); -module_exit(pata_at32_exit); +module_platform_driver_probe(pata_at32_driver, pata_at32_probe); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("AVR32 SMC/CFC PATA Driver"); diff --git a/drivers/ata/pata_imx.c b/drivers/ata/pata_imx.c index 40849445a9d..aa3d166e02e 100644 --- a/drivers/ata/pata_imx.c +++ b/drivers/ata/pata_imx.c @@ -37,7 +37,7 @@ struct pata_imx_priv { struct clk *clk; /* timings/interrupt/control regs */ - u8 *host_regs; + void __iomem *host_regs; u32 ata_ctl; }; @@ -98,6 +98,7 @@ static int pata_imx_probe(struct platform_device *pdev) struct pata_imx_priv *priv; int irq = 0; struct resource *io_res; + int ret; io_res = platform_get_resource(pdev, IORESOURCE_MEM, 0); if (io_res == NULL) @@ -112,7 +113,7 @@ static int pata_imx_probe(struct platform_device *pdev) if (!priv) return -ENOMEM; - priv->clk = clk_get(&pdev->dev, NULL); + priv->clk = devm_clk_get(&pdev->dev, NULL); if (IS_ERR(priv->clk)) { dev_err(&pdev->dev, "Failed to get clock\n"); return PTR_ERR(priv->clk); @@ -121,8 +122,10 @@ static int pata_imx_probe(struct platform_device *pdev) clk_prepare_enable(priv->clk); host = ata_host_alloc(&pdev->dev, 1); - if (!host) - goto free_priv; + if (!host) { + ret = -ENOMEM; + goto err; + } host->private_data = priv; ap = host->ports[0]; @@ -135,7 +138,8 @@ static int pata_imx_probe(struct platform_device *pdev) resource_size(io_res)); if (!priv->host_regs) { dev_err(&pdev->dev, "failed to map IO/CTL base\n"); - goto free_priv; + ret = -EBUSY; + goto err; } ap->ioaddr.cmd_addr = priv->host_regs + PATA_IMX_DRIVE_DATA; @@ -158,13 +162,17 @@ static int pata_imx_probe(struct platform_device *pdev) priv->host_regs + PATA_IMX_ATA_INT_EN); /* activate */ - return ata_host_activate(host, irq, ata_sff_interrupt, 0, + ret = ata_host_activate(host, irq, ata_sff_interrupt, 0, &pata_imx_sht); -free_priv: + if (ret) + goto err; + + return 0; +err: clk_disable_unprepare(priv->clk); - clk_put(priv->clk); - return -ENOMEM; + + return ret; } static int pata_imx_remove(struct platform_device *pdev) @@ -177,7 +185,6 @@ static int pata_imx_remove(struct platform_device *pdev) __raw_writel(0, priv->host_regs + PATA_IMX_ATA_INT_EN); clk_disable_unprepare(priv->clk); - clk_put(priv->clk); return 0; } @@ -223,11 +230,20 @@ static const struct dev_pm_ops pata_imx_pm_ops = { }; #endif +static const struct of_device_id imx_pata_dt_ids[] = { + { + .compatible = "fsl,imx27-pata", + }, { + /* sentinel */ + } +}; + static struct platform_driver pata_imx_driver = { .probe = pata_imx_probe, .remove = pata_imx_remove, .driver = { .name = DRV_NAME, + .of_match_table = imx_pata_dt_ids, .owner = THIS_MODULE, #ifdef CONFIG_PM .pm = &pata_imx_pm_ops, diff --git a/drivers/ata/pata_legacy.c b/drivers/ata/pata_legacy.c index 4fe9d2138d4..be816428b43 100644 --- a/drivers/ata/pata_legacy.c +++ b/drivers/ata/pata_legacy.c @@ -542,7 +542,7 @@ static void opti82c46x_set_piomode(struct ata_port *ap, struct ata_device *adev) u8 sysclk; /* Get the clock */ - sysclk = opti_syscfg(0xAC) & 0xC0; /* BIOS set */ + sysclk = (opti_syscfg(0xAC) & 0xC0) >> 6; /* BIOS set */ /* Enter configuration mode */ ioread16(ap->ioaddr.error_addr); diff --git a/drivers/ata/pata_octeon_cf.c b/drivers/ata/pata_octeon_cf.c index ff2e57f3b59..e73bef3093d 100644 --- a/drivers/ata/pata_octeon_cf.c +++ b/drivers/ata/pata_octeon_cf.c @@ -926,7 +926,7 @@ static int octeon_cf_probe(struct platform_device *pdev) goto free_cf_port; } cs1 = devm_ioremap_nocache(&pdev->dev, res_cs1->start, - res_cs1->end - res_cs1->start + 1); + resource_size(res_cs1)); if (!cs1) goto free_cf_port; diff --git a/drivers/ata/sata_fsl.c b/drivers/ata/sata_fsl.c index dfc6fd0ac11..d40e403e82d 100644 --- a/drivers/ata/sata_fsl.c +++ b/drivers/ata/sata_fsl.c @@ -285,6 +285,7 @@ struct sata_fsl_host_priv { int irq; int data_snoop; struct device_attribute intr_coalescing; + struct device_attribute rx_watermark; }; static void fsl_sata_set_irq_coalescing(struct ata_host *host, @@ -343,6 +344,48 @@ static ssize_t fsl_sata_intr_coalescing_store(struct device *dev, return strlen(buf); } +static ssize_t fsl_sata_rx_watermark_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + unsigned int rx_watermark; + unsigned long flags; + struct ata_host *host = dev_get_drvdata(dev); + struct sata_fsl_host_priv *host_priv = host->private_data; + void __iomem *csr_base = host_priv->csr_base; + + spin_lock_irqsave(&host->lock, flags); + rx_watermark = ioread32(csr_base + TRANSCFG); + rx_watermark &= 0x1f; + + spin_unlock_irqrestore(&host->lock, flags); + return sprintf(buf, "%d\n", rx_watermark); +} + +static ssize_t fsl_sata_rx_watermark_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + unsigned int rx_watermark; + unsigned long flags; + struct ata_host *host = dev_get_drvdata(dev); + struct sata_fsl_host_priv *host_priv = host->private_data; + void __iomem *csr_base = host_priv->csr_base; + u32 temp; + + if (sscanf(buf, "%d", &rx_watermark) != 1) { + printk(KERN_ERR "fsl-sata: wrong parameter format.\n"); + return -EINVAL; + } + + spin_lock_irqsave(&host->lock, flags); + temp = ioread32(csr_base + TRANSCFG); + temp &= 0xffffffe0; + iowrite32(temp | rx_watermark, csr_base + TRANSCFG); + + spin_unlock_irqrestore(&host->lock, flags); + return strlen(buf); +} + static inline unsigned int sata_fsl_tag(unsigned int tag, void __iomem *hcr_base) { @@ -1500,6 +1543,17 @@ static int sata_fsl_probe(struct platform_device *ofdev) if (retval) goto error_exit_with_cleanup; + host_priv->rx_watermark.show = fsl_sata_rx_watermark_show; + host_priv->rx_watermark.store = fsl_sata_rx_watermark_store; + sysfs_attr_init(&host_priv->rx_watermark.attr); + host_priv->rx_watermark.attr.name = "rx_watermark"; + host_priv->rx_watermark.attr.mode = S_IRUGO | S_IWUSR; + retval = device_create_file(host->dev, &host_priv->rx_watermark); + if (retval) { + device_remove_file(&ofdev->dev, &host_priv->intr_coalescing); + goto error_exit_with_cleanup; + } + return 0; error_exit_with_cleanup: @@ -1522,6 +1576,7 @@ static int sata_fsl_remove(struct platform_device *ofdev) struct sata_fsl_host_priv *host_priv = host->private_data; device_remove_file(&ofdev->dev, &host_priv->intr_coalescing); + device_remove_file(&ofdev->dev, &host_priv->rx_watermark); ata_host_detach(host); diff --git a/drivers/ata/sata_highbank.c b/drivers/ata/sata_highbank.c index 5dba77ccaa0..b20aa96b958 100644 --- a/drivers/ata/sata_highbank.c +++ b/drivers/ata/sata_highbank.c @@ -251,7 +251,7 @@ static const struct ata_port_info ahci_highbank_port_info = { }; static struct scsi_host_template ahci_highbank_platform_sht = { - AHCI_SHT("highbank-ahci"), + AHCI_SHT("sata_highbank"), }; static const struct of_device_id ahci_of_match[] = { @@ -418,7 +418,7 @@ static int ahci_highbank_resume(struct device *dev) } #endif -SIMPLE_DEV_PM_OPS(ahci_highbank_pm_ops, +static SIMPLE_DEV_PM_OPS(ahci_highbank_pm_ops, ahci_highbank_suspend, ahci_highbank_resume); static struct platform_driver ahci_highbank_driver = { diff --git a/drivers/ata/sata_rcar.c b/drivers/ata/sata_rcar.c index caf33f620c3..4799868bd73 100644 --- a/drivers/ata/sata_rcar.c +++ b/drivers/ata/sata_rcar.c @@ -17,6 +17,7 @@ #include <linux/libata.h> #include <linux/platform_device.h> #include <linux/clk.h> +#include <linux/err.h> #define DRV_NAME "sata_rcar" @@ -799,9 +800,9 @@ static int sata_rcar_probe(struct platform_device *pdev) host->private_data = priv; - priv->base = devm_request_and_ioremap(&pdev->dev, mem); - if (!priv->base) { - ret = -EADDRNOTAVAIL; + priv->base = devm_ioremap_resource(&pdev->dev, mem); + if (IS_ERR(priv->base)) { + ret = PTR_ERR(priv->base); goto cleanup; } diff --git a/drivers/base/power/domain.c b/drivers/base/power/domain.c index 9a6b05a3560..7072404c8b6 100644 --- a/drivers/base/power/domain.c +++ b/drivers/base/power/domain.c @@ -920,7 +920,7 @@ static int pm_genpd_prepare(struct device *dev) pm_wakeup_event(dev, 0); if (pm_wakeup_pending()) { - pm_runtime_put_sync(dev); + pm_runtime_put(dev); return -EBUSY; } @@ -961,7 +961,7 @@ static int pm_genpd_prepare(struct device *dev) pm_runtime_enable(dev); } - pm_runtime_put_sync(dev); + pm_runtime_put(dev); return ret; } @@ -1327,7 +1327,7 @@ static void pm_genpd_complete(struct device *dev) pm_generic_complete(dev); pm_runtime_set_active(dev); pm_runtime_enable(dev); - pm_runtime_idle(dev); + pm_request_idle(dev); } } diff --git a/drivers/base/power/generic_ops.c b/drivers/base/power/generic_ops.c index d03d290f31c..bfd898b8988 100644 --- a/drivers/base/power/generic_ops.c +++ b/drivers/base/power/generic_ops.c @@ -324,6 +324,6 @@ void pm_generic_complete(struct device *dev) * Let runtime PM try to suspend devices that haven't been in use before * going into the system-wide sleep state we're resuming from. */ - pm_runtime_idle(dev); + pm_request_idle(dev); } #endif /* CONFIG_PM_SLEEP */ diff --git a/drivers/base/power/main.c b/drivers/base/power/main.c index 15beb500a4e..5a9b6569dd7 100644 --- a/drivers/base/power/main.c +++ b/drivers/base/power/main.c @@ -756,7 +756,7 @@ static void device_complete(struct device *dev, pm_message_t state) device_unlock(dev); - pm_runtime_put_sync(dev); + pm_runtime_put(dev); } /** diff --git a/drivers/base/power/opp.c b/drivers/base/power/opp.c index 32ee0fc7ea5..f0077cb8e24 100644 --- a/drivers/base/power/opp.c +++ b/drivers/base/power/opp.c @@ -55,6 +55,7 @@ * @rate: Frequency in hertz * @u_volt: Nominal voltage in microvolts corresponding to this OPP * @dev_opp: points back to the device_opp struct this opp belongs to + * @head: RCU callback head used for deferred freeing * * This structure stores the OPP information for a given device. */ diff --git a/drivers/base/power/runtime.c b/drivers/base/power/runtime.c index 1244930e3d7..ef13ad08afb 100644 --- a/drivers/base/power/runtime.c +++ b/drivers/base/power/runtime.c @@ -1400,5 +1400,5 @@ void pm_runtime_remove(struct device *dev) if (dev->power.runtime_status == RPM_ACTIVE) pm_runtime_set_suspended(dev); if (dev->power.irq_safe && dev->parent) - pm_runtime_put_sync(dev->parent); + pm_runtime_put(dev->parent); } diff --git a/drivers/char/tpm/tpm.c b/drivers/char/tpm/tpm.c index 0d2e82f9557..7c3b3dcbfbc 100644 --- a/drivers/char/tpm/tpm.c +++ b/drivers/char/tpm/tpm.c @@ -1337,7 +1337,7 @@ int tpm_pm_suspend(struct device *dev) { struct tpm_chip *chip = dev_get_drvdata(dev); struct tpm_cmd_t cmd; - int rc; + int rc, try; u8 dummy_hash[TPM_DIGEST_SIZE] = { 0 }; @@ -1355,9 +1355,32 @@ int tpm_pm_suspend(struct device *dev) } /* now do the actual savestate */ - cmd.header.in = savestate_header; - rc = transmit_cmd(chip, &cmd, SAVESTATE_RESULT_SIZE, - "sending savestate before suspend"); + for (try = 0; try < TPM_RETRY; try++) { + cmd.header.in = savestate_header; + rc = transmit_cmd(chip, &cmd, SAVESTATE_RESULT_SIZE, NULL); + + /* + * If the TPM indicates that it is too busy to respond to + * this command then retry before giving up. It can take + * several seconds for this TPM to be ready. + * + * This can happen if the TPM has already been sent the + * SaveState command before the driver has loaded. TCG 1.2 + * specification states that any communication after SaveState + * may cause the TPM to invalidate previously saved state. + */ + if (rc != TPM_WARN_RETRY) + break; + msleep(TPM_TIMEOUT_RETRY); + } + + if (rc) + dev_err(chip->dev, + "Error (%d) sending savestate before suspend\n", rc); + else if (try > 0) + dev_warn(chip->dev, "TPM savestate took %dms\n", + try * TPM_TIMEOUT_RETRY); + return rc; } EXPORT_SYMBOL_GPL(tpm_pm_suspend); diff --git a/drivers/char/tpm/tpm.h b/drivers/char/tpm/tpm.h index 81b52015f66..0770d1d7936 100644 --- a/drivers/char/tpm/tpm.h +++ b/drivers/char/tpm/tpm.h @@ -32,10 +32,12 @@ enum tpm_const { TPM_MINOR = 224, /* officially assigned */ TPM_BUFSIZE = 4096, TPM_NUM_DEVICES = 256, + TPM_RETRY = 50, /* 5 seconds */ }; enum tpm_timeout { TPM_TIMEOUT = 5, /* msecs */ + TPM_TIMEOUT_RETRY = 100 /* msecs */ }; /* TPM addresses */ @@ -44,6 +46,7 @@ enum tpm_addr { TPM_ADDR = 0x4E, }; +#define TPM_WARN_RETRY 0x800 #define TPM_WARN_DOING_SELFTEST 0x802 #define TPM_ERR_DEACTIVATED 0x6 #define TPM_ERR_DISABLED 0x7 diff --git a/drivers/char/tpm/tpm_i2c_infineon.c b/drivers/char/tpm/tpm_i2c_infineon.c index 8fe7ac3d095..37d5dcc10ea 100644 --- a/drivers/char/tpm/tpm_i2c_infineon.c +++ b/drivers/char/tpm/tpm_i2c_infineon.c @@ -1,5 +1,5 @@ /* - * Copyright (C) 2012 Infineon Technologies + * Copyright (C) 2012,2013 Infineon Technologies * * Authors: * Peter Huewe <peter.huewe@infineon.com> @@ -56,13 +56,21 @@ #define TPM_TIMEOUT_US_HI (TPM_TIMEOUT_US_LOW + 2000) /* expected value for DIDVID register */ -#define TPM_TIS_I2C_DID_VID 0x000b15d1L +#define TPM_TIS_I2C_DID_VID_9635 0xd1150b00L +#define TPM_TIS_I2C_DID_VID_9645 0x001a15d1L + +enum i2c_chip_type { + SLB9635, + SLB9645, + UNKNOWN, +}; /* Structure to store I2C TPM specific stuff */ struct tpm_inf_dev { struct i2c_client *client; u8 buf[TPM_BUFSIZE + sizeof(u8)]; /* max. buffer size + addr */ struct tpm_chip *chip; + enum i2c_chip_type chip_type; }; static struct tpm_inf_dev tpm_dev; @@ -90,10 +98,20 @@ static struct i2c_driver tpm_tis_i2c_driver; static int iic_tpm_read(u8 addr, u8 *buffer, size_t len) { - struct i2c_msg msg1 = { tpm_dev.client->addr, 0, 1, &addr }; - struct i2c_msg msg2 = { tpm_dev.client->addr, I2C_M_RD, len, buffer }; + struct i2c_msg msg1 = { + .addr = tpm_dev.client->addr, + .len = 1, + .buf = &addr + }; + struct i2c_msg msg2 = { + .addr = tpm_dev.client->addr, + .flags = I2C_M_RD, + .len = len, + .buf = buffer + }; + struct i2c_msg msgs[] = {msg1, msg2}; - int rc; + int rc = 0; int count; /* Lock the adapter for the duration of the whole sequence. */ @@ -101,30 +119,53 @@ static int iic_tpm_read(u8 addr, u8 *buffer, size_t len) return -EOPNOTSUPP; i2c_lock_adapter(tpm_dev.client->adapter); - for (count = 0; count < MAX_COUNT; count++) { - rc = __i2c_transfer(tpm_dev.client->adapter, &msg1, 1); - if (rc > 0) - break; /* break here to skip sleep */ - - usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); - } - - if (rc <= 0) - goto out; - - /* After the TPM has successfully received the register address it needs - * some time, thus we're sleeping here again, before retrieving the data - */ - for (count = 0; count < MAX_COUNT; count++) { - usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); - rc = __i2c_transfer(tpm_dev.client->adapter, &msg2, 1); - if (rc > 0) - break; + if (tpm_dev.chip_type == SLB9645) { + /* use a combined read for newer chips + * unfortunately the smbus functions are not suitable due to + * the 32 byte limit of the smbus. + * retries should usually not be needed, but are kept just to + * be on the safe side. + */ + for (count = 0; count < MAX_COUNT; count++) { + rc = __i2c_transfer(tpm_dev.client->adapter, msgs, 2); + if (rc > 0) + break; /* break here to skip sleep */ + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + } + } else { + /* slb9635 protocol should work in all cases */ + for (count = 0; count < MAX_COUNT; count++) { + rc = __i2c_transfer(tpm_dev.client->adapter, &msg1, 1); + if (rc > 0) + break; /* break here to skip sleep */ + + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + } + if (rc <= 0) + goto out; + + /* After the TPM has successfully received the register address + * it needs some time, thus we're sleeping here again, before + * retrieving the data + */ + for (count = 0; count < MAX_COUNT; count++) { + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + rc = __i2c_transfer(tpm_dev.client->adapter, &msg2, 1); + if (rc > 0) + break; + } } out: i2c_unlock_adapter(tpm_dev.client->adapter); + /* take care of 'guard time' */ + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + + /* __i2c_transfer returns the number of successfully transferred + * messages. + * So rc should be greater than 0 here otherwise we have an error. + */ if (rc <= 0) return -EIO; @@ -138,7 +179,11 @@ static int iic_tpm_write_generic(u8 addr, u8 *buffer, size_t len, int rc = -EIO; int count; - struct i2c_msg msg1 = { tpm_dev.client->addr, 0, len + 1, tpm_dev.buf }; + struct i2c_msg msg1 = { + .addr = tpm_dev.client->addr, + .len = len + 1, + .buf = tpm_dev.buf + }; if (len > TPM_BUFSIZE) return -EINVAL; @@ -154,16 +199,24 @@ static int iic_tpm_write_generic(u8 addr, u8 *buffer, size_t len, /* * NOTE: We have to use these special mechanisms here and unfortunately * cannot rely on the standard behavior of i2c_transfer. + * Even for newer chips the smbus functions are not + * suitable due to the 32 byte limit of the smbus. */ for (count = 0; count < max_count; count++) { rc = __i2c_transfer(tpm_dev.client->adapter, &msg1, 1); if (rc > 0) break; - usleep_range(sleep_low, sleep_hi); } i2c_unlock_adapter(tpm_dev.client->adapter); + /* take care of 'guard time' */ + usleep_range(SLEEP_DURATION_LOW, SLEEP_DURATION_HI); + + /* __i2c_transfer returns the number of successfully transferred + * messages. + * So rc should be greater than 0 here otherwise we have an error. + */ if (rc <= 0) return -EIO; @@ -283,11 +336,18 @@ static int request_locality(struct tpm_chip *chip, int loc) static u8 tpm_tis_i2c_status(struct tpm_chip *chip) { /* NOTE: since I2C read may fail, return 0 in this case --> time-out */ - u8 buf; - if (iic_tpm_read(TPM_STS(chip->vendor.locality), &buf, 1) < 0) - return 0; - else - return buf; + u8 buf = 0xFF; + u8 i = 0; + + do { + if (iic_tpm_read(TPM_STS(chip->vendor.locality), &buf, 1) < 0) + return 0; + + i++; + /* if locallity is set STS should not be 0xFF */ + } while ((buf == 0xFF) && i < 10); + + return buf; } static void tpm_tis_i2c_ready(struct tpm_chip *chip) @@ -328,7 +388,7 @@ static int wait_for_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout, /* check current status */ *status = tpm_tis_i2c_status(chip); - if ((*status & mask) == mask) + if ((*status != 0xFF) && (*status & mask) == mask) return 0; stop = jiffies + timeout; @@ -372,7 +432,6 @@ static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count) /* avoid endless loop in case of broken HW */ if (retries > MAX_COUNT_LONG) return -EIO; - } return size; } @@ -480,7 +539,6 @@ static int tpm_tis_i2c_send(struct tpm_chip *chip, u8 *buf, size_t len) rc = -EIO; goto out_err; } - } /* write last byte */ @@ -568,6 +626,7 @@ static int tpm_tis_i2c_init(struct device *dev) chip = tpm_register_hardware(dev, &tpm_tis_i2c); if (!chip) { + dev_err(dev, "could not register hardware\n"); rc = -ENODEV; goto out_err; } @@ -582,20 +641,24 @@ static int tpm_tis_i2c_init(struct device *dev) chip->vendor.timeout_d = msecs_to_jiffies(TIS_SHORT_TIMEOUT); if (request_locality(chip, 0) != 0) { + dev_err(dev, "could not request locality\n"); rc = -ENODEV; goto out_vendor; } /* read four bytes from DID_VID register */ if (iic_tpm_read(TPM_DID_VID(0), (u8 *)&vendor, 4) < 0) { + dev_err(dev, "could not read vendor id\n"); rc = -EIO; goto out_release; } - /* create DID_VID register value, after swapping to little-endian */ - vendor = be32_to_cpu((__be32) vendor); - - if (vendor != TPM_TIS_I2C_DID_VID) { + if (vendor == TPM_TIS_I2C_DID_VID_9645) { + tpm_dev.chip_type = SLB9645; + } else if (vendor == TPM_TIS_I2C_DID_VID_9635) { + tpm_dev.chip_type = SLB9635; + } else { + dev_err(dev, "vendor id did not match! ID was %08x\n", vendor); rc = -ENODEV; goto out_release; } @@ -631,22 +694,53 @@ out_err: static const struct i2c_device_id tpm_tis_i2c_table[] = { {"tpm_i2c_infineon", 0}, + {"slb9635tt", 0}, + {"slb9645tt", 1}, {}, }; MODULE_DEVICE_TABLE(i2c, tpm_tis_i2c_table); + +#ifdef CONFIG_OF +static const struct of_device_id tpm_tis_i2c_of_match[] = { + { + .name = "tpm_i2c_infineon", + .type = "tpm", + .compatible = "infineon,tpm_i2c_infineon", + .data = (void *)0 + }, + { + .name = "slb9635tt", + .type = "tpm", + .compatible = "infineon,slb9635tt", + .data = (void *)0 + }, + { + .name = "slb9645tt", + .type = "tpm", + .compatible = "infineon,slb9645tt", + .data = (void *)1 + }, + {}, +}; +MODULE_DEVICE_TABLE(of, tpm_tis_i2c_of_match); +#endif + static SIMPLE_DEV_PM_OPS(tpm_tis_i2c_ops, tpm_pm_suspend, tpm_pm_resume); static int tpm_tis_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) { int rc; - if (tpm_dev.client != NULL) + struct device *dev = &(client->dev); + + if (tpm_dev.client != NULL) { + dev_err(dev, "This driver only supports one client at a time\n"); return -EBUSY; /* We only support one client */ + } if (!i2c_check_functionality(client->adapter, I2C_FUNC_I2C)) { - dev_err(&client->dev, - "no algorithms associated to the i2c bus\n"); + dev_err(dev, "no algorithms associated to the i2c bus\n"); return -ENODEV; } @@ -682,7 +776,6 @@ static int tpm_tis_i2c_remove(struct i2c_client *client) } static struct i2c_driver tpm_tis_i2c_driver = { - .id_table = tpm_tis_i2c_table, .probe = tpm_tis_i2c_probe, .remove = tpm_tis_i2c_remove, @@ -690,11 +783,12 @@ static struct i2c_driver tpm_tis_i2c_driver = { .name = "tpm_i2c_infineon", .owner = THIS_MODULE, .pm = &tpm_tis_i2c_ops, + .of_match_table = of_match_ptr(tpm_tis_i2c_of_match), }, }; module_i2c_driver(tpm_tis_i2c_driver); MODULE_AUTHOR("Peter Huewe <peter.huewe@infineon.com>"); MODULE_DESCRIPTION("TPM TIS I2C Infineon Driver"); -MODULE_VERSION("2.1.5"); +MODULE_VERSION("2.2.0"); MODULE_LICENSE("GPL"); diff --git a/drivers/char/tpm/tpm_i2c_stm_st33.c b/drivers/char/tpm/tpm_i2c_stm_st33.c index 1f5f71e14ab..5bb8e2ddd3b 100644 --- a/drivers/char/tpm/tpm_i2c_stm_st33.c +++ b/drivers/char/tpm/tpm_i2c_stm_st33.c @@ -36,7 +36,6 @@ #include <linux/i2c.h> #include <linux/fs.h> #include <linux/miscdevice.h> -#include <linux/module.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/init.h> @@ -50,7 +49,6 @@ #include <linux/uaccess.h> #include <linux/io.h> #include <linux/slab.h> -#include <linux/sched.h> #include "tpm.h" #include "tpm_i2c_stm_st33.h" @@ -178,7 +176,7 @@ static long _wait_for_interrupt_serirq_timeout(struct tpm_chip *chip, struct i2c_client *client; struct st33zp24_platform_data *pin_infos; - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); pin_infos = client->dev.platform_data; status = wait_for_completion_interruptible_timeout( @@ -197,12 +195,12 @@ static int wait_for_serirq_timeout(struct tpm_chip *chip, bool condition, int status = 2; struct i2c_client *client; - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); status = _wait_for_interrupt_serirq_timeout(chip, timeout); if (!status) { status = -EBUSY; - } else{ + } else { clear_interruption(client); if (condition) status = 1; @@ -219,7 +217,7 @@ static void tpm_stm_i2c_cancel(struct tpm_chip *chip) struct i2c_client *client; u8 data; - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); data = TPM_STS_COMMAND_READY; I2C_WRITE_DATA(client, TPM_STS, &data, 1); @@ -236,7 +234,7 @@ static u8 tpm_stm_i2c_status(struct tpm_chip *chip) { struct i2c_client *client; u8 data; - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); I2C_READ_DATA(client, TPM_STS, &data, 1); return data; @@ -254,7 +252,7 @@ static int check_locality(struct tpm_chip *chip) u8 data; u8 status; - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); status = I2C_READ_DATA(client, TPM_ACCESS, &data, 1); if (status && (data & @@ -278,7 +276,7 @@ static int request_locality(struct tpm_chip *chip) struct i2c_client *client; u8 data; - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); if (check_locality(chip) == chip->vendor.locality) return chip->vendor.locality; @@ -294,7 +292,7 @@ static int request_locality(struct tpm_chip *chip) chip->vendor.timeout_a); if (rc > 0) return chip->vendor.locality; - } else{ + } else { stop = jiffies + chip->vendor.timeout_a; do { if (check_locality(chip) >= 0) @@ -316,7 +314,7 @@ static void release_locality(struct tpm_chip *chip) struct i2c_client *client; u8 data; - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); data = TPM_ACCESS_ACTIVE_LOCALITY; I2C_WRITE_DATA(client, TPM_ACCESS, &data, 1); @@ -333,7 +331,7 @@ static int get_burstcount(struct tpm_chip *chip) int burstcnt, status; u8 tpm_reg, temp; - struct i2c_client *client = (struct i2c_client *) TPM_VPRIV(chip); + struct i2c_client *client = (struct i2c_client *)TPM_VPRIV(chip); stop = jiffies + chip->vendor.timeout_d; do { @@ -379,7 +377,7 @@ static int wait_for_stat(struct tpm_chip *chip, u8 mask, unsigned long timeout, mask), timeout); if (rc > 0) return 0; - } else{ + } else { stop = jiffies + timeout; do { msleep(TPM_TIMEOUT); @@ -403,7 +401,7 @@ static int recv_data(struct tpm_chip *chip, u8 *buf, size_t count) int size = 0, burstcnt, len; struct i2c_client *client; - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); while (size < count && wait_for_stat(chip, @@ -433,7 +431,7 @@ static irqreturn_t tpm_ioserirq_handler(int irq, void *dev_id) disable_irq_nosync(irq); - client = (struct i2c_client *) TPM_VPRIV(chip); + client = (struct i2c_client *)TPM_VPRIV(chip); pin_infos = client->dev.platform_data; complete(&pin_infos->irq_detection); @@ -453,8 +451,7 @@ static irqreturn_t tpm_ioserirq_handler(int irq, void *dev_id) static int tpm_stm_i2c_send(struct tpm_chip *chip, unsigned char *buf, size_t len) { - u32 status, - burstcnt = 0, i, size; + u32 status, burstcnt = 0, i, size; int ret; u8 data; struct i2c_client *client; @@ -483,7 +480,7 @@ static int tpm_stm_i2c_send(struct tpm_chip *chip, unsigned char *buf, } } - for (i = 0 ; i < len - 1 ;) { + for (i = 0; i < len - 1;) { burstcnt = get_burstcount(chip); size = min_t(int, len - i - 1, burstcnt); ret = I2C_WRITE_DATA(client, TPM_DATA_FIFO, buf, size); @@ -547,7 +544,7 @@ static int tpm_stm_i2c_recv(struct tpm_chip *chip, unsigned char *buf, goto out; } - expected = be32_to_cpu(*(__be32 *) (buf + 2)); + expected = be32_to_cpu(*(__be32 *)(buf + 2)); if (expected > count) { size = -EIO; goto out; @@ -569,7 +566,7 @@ out: static bool tpm_st33_i2c_req_canceled(struct tpm_chip *chip, u8 status) { - return (status == TPM_STS_COMMAND_READY); + return (status == TPM_STS_COMMAND_READY); } static const struct file_operations tpm_st33_i2c_fops = { @@ -617,7 +614,7 @@ static struct tpm_vendor_specific st_i2c_tpm = { .miscdev = {.fops = &tpm_st33_i2c_fops,}, }; -static int interrupts ; +static int interrupts; module_param(interrupts, int, 0444); MODULE_PARM_DESC(interrupts, "Enable interrupts"); @@ -714,7 +711,7 @@ tpm_st33_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) "TPM SERIRQ management", chip); if (err < 0) { dev_err(chip->dev , "TPM SERIRQ signals %d not available\n", - gpio_to_irq(platform_data->io_serirq)); + gpio_to_irq(platform_data->io_serirq)); goto _irq_set; } @@ -754,7 +751,7 @@ tpm_st33_i2c_probe(struct i2c_client *client, const struct i2c_device_id *id) dev_info(chip->dev, "TPM I2C Initialized\n"); return 0; _irq_set: - free_irq(gpio_to_irq(platform_data->io_serirq), (void *) chip); + free_irq(gpio_to_irq(platform_data->io_serirq), (void *)chip); _gpio_init2: if (interrupts) gpio_free(platform_data->io_serirq); @@ -784,7 +781,7 @@ static int tpm_st33_i2c_remove(struct i2c_client *client) { struct tpm_chip *chip = (struct tpm_chip *)i2c_get_clientdata(client); struct st33zp24_platform_data *pin_infos = - ((struct i2c_client *) TPM_VPRIV(chip))->dev.platform_data; + ((struct i2c_client *)TPM_VPRIV(chip))->dev.platform_data; if (pin_infos != NULL) { free_irq(pin_infos->io_serirq, chip); @@ -823,9 +820,9 @@ static int tpm_st33_i2c_pm_suspend(struct device *dev) struct st33zp24_platform_data *pin_infos = dev->platform_data; int ret = 0; - if (power_mgt) + if (power_mgt) { gpio_set_value(pin_infos->io_lpcpd, 0); - else{ + } else { if (chip->data_buffer == NULL) chip->data_buffer = pin_infos->tpm_i2c_buffer[0]; ret = tpm_pm_suspend(dev); @@ -851,12 +848,12 @@ static int tpm_st33_i2c_pm_resume(struct device *dev) (chip->vendor.status(chip) & TPM_STS_VALID) == TPM_STS_VALID, chip->vendor.timeout_b); - } else{ - if (chip->data_buffer == NULL) - chip->data_buffer = pin_infos->tpm_i2c_buffer[0]; - ret = tpm_pm_resume(dev); - if (!ret) - tpm_do_selftest(chip); + } else { + if (chip->data_buffer == NULL) + chip->data_buffer = pin_infos->tpm_i2c_buffer[0]; + ret = tpm_pm_resume(dev); + if (!ret) + tpm_do_selftest(chip); } return ret; } /* tpm_st33_i2c_pm_resume() */ @@ -867,7 +864,8 @@ static const struct i2c_device_id tpm_st33_i2c_id[] = { {} }; MODULE_DEVICE_TABLE(i2c, tpm_st33_i2c_id); -static SIMPLE_DEV_PM_OPS(tpm_st33_i2c_ops, tpm_st33_i2c_pm_suspend, tpm_st33_i2c_pm_resume); +static SIMPLE_DEV_PM_OPS(tpm_st33_i2c_ops, tpm_st33_i2c_pm_suspend, + tpm_st33_i2c_pm_resume); static struct i2c_driver tpm_st33_i2c_driver = { .driver = { .owner = THIS_MODULE, diff --git a/drivers/char/tpm/tpm_ppi.c b/drivers/char/tpm/tpm_ppi.c index 720ebcf29fd..2168d15bc72 100644 --- a/drivers/char/tpm/tpm_ppi.c +++ b/drivers/char/tpm/tpm_ppi.c @@ -158,9 +158,9 @@ static ssize_t tpm_store_ppi_request(struct device *dev, ACPI_TYPE_STRING); if (ACPI_FAILURE(status)) return -ENOMEM; - strncpy(version, + strlcpy(version, ((union acpi_object *)output.pointer)->string.pointer, - PPI_VERSION_LEN); + PPI_VERSION_LEN + 1); kfree(output.pointer); output.length = ACPI_ALLOCATE_BUFFER; output.pointer = NULL; @@ -237,9 +237,9 @@ static ssize_t tpm_show_ppi_transition_action(struct device *dev, ACPI_TYPE_STRING); if (ACPI_FAILURE(status)) return -ENOMEM; - strncpy(version, + strlcpy(version, ((union acpi_object *)output.pointer)->string.pointer, - PPI_VERSION_LEN); + PPI_VERSION_LEN + 1); /* * PPI spec defines params[3].type as empty package, but some platforms * (e.g. Capella with PPI 1.0) need integer/string/buffer type, so for @@ -351,7 +351,7 @@ cleanup: static ssize_t show_ppi_operations(char *buf, u32 start, u32 end) { char *str = buf; - char version[PPI_VERSION_LEN]; + char version[PPI_VERSION_LEN + 1]; acpi_handle handle; acpi_status status; struct acpi_object_list input; @@ -381,9 +381,9 @@ static ssize_t show_ppi_operations(char *buf, u32 start, u32 end) if (ACPI_FAILURE(status)) return -ENOMEM; - strncpy(version, + strlcpy(version, ((union acpi_object *)output.pointer)->string.pointer, - PPI_VERSION_LEN); + PPI_VERSION_LEN + 1); kfree(output.pointer); output.length = ACPI_ALLOCATE_BUFFER; output.pointer = NULL; diff --git a/drivers/clk/x86/Makefile b/drivers/clk/x86/Makefile index f9ba4fab0dd..04781389d0f 100644 --- a/drivers/clk/x86/Makefile +++ b/drivers/clk/x86/Makefile @@ -1,2 +1,2 @@ -clk-x86-lpss-objs := clk-lpss.o clk-lpt.o +clk-x86-lpss-objs := clk-lpt.o obj-$(CONFIG_X86_INTEL_LPSS) += clk-x86-lpss.o diff --git a/drivers/clk/x86/clk-lpss.c b/drivers/clk/x86/clk-lpss.c deleted file mode 100644 index b5e229f3c3d..00000000000 --- a/drivers/clk/x86/clk-lpss.c +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Intel Low Power Subsystem clocks. - * - * Copyright (C) 2013, Intel Corporation - * Authors: Mika Westerberg <mika.westerberg@linux.intel.com> - * Heikki Krogerus <heikki.krogerus@linux.intel.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#include <linux/acpi.h> -#include <linux/clk.h> -#include <linux/clk-provider.h> -#include <linux/err.h> -#include <linux/io.h> -#include <linux/module.h> - -static int clk_lpss_is_mmio_resource(struct acpi_resource *res, void *data) -{ - struct resource r; - return !acpi_dev_resource_memory(res, &r); -} - -static acpi_status clk_lpss_find_mmio(acpi_handle handle, u32 level, - void *data, void **retval) -{ - struct resource_list_entry *rentry; - struct list_head resource_list; - struct acpi_device *adev; - const char *uid = data; - int ret; - - if (acpi_bus_get_device(handle, &adev)) - return AE_OK; - - if (uid) { - if (!adev->pnp.unique_id) - return AE_OK; - if (strcmp(uid, adev->pnp.unique_id)) - return AE_OK; - } - - INIT_LIST_HEAD(&resource_list); - ret = acpi_dev_get_resources(adev, &resource_list, - clk_lpss_is_mmio_resource, NULL); - if (ret < 0) - return AE_NO_MEMORY; - - list_for_each_entry(rentry, &resource_list, node) - if (resource_type(&rentry->res) == IORESOURCE_MEM) { - *(struct resource *)retval = rentry->res; - break; - } - - acpi_dev_free_resource_list(&resource_list); - return AE_OK; -} - -/** - * clk_register_lpss_gate - register LPSS clock gate - * @name: name of this clock gate - * @parent_name: parent clock name - * @hid: ACPI _HID of the device - * @uid: ACPI _UID of the device (optional) - * @offset: LPSS PRV_CLOCK_PARAMS offset - * - * Creates and registers LPSS clock gate. - */ -struct clk *clk_register_lpss_gate(const char *name, const char *parent_name, - const char *hid, const char *uid, - unsigned offset) -{ - struct resource res = { }; - void __iomem *mmio_base; - acpi_status status; - struct clk *clk; - - /* - * First try to look the device and its mmio resource from the - * ACPI namespace. - */ - status = acpi_get_devices(hid, clk_lpss_find_mmio, (void *)uid, - (void **)&res); - if (ACPI_FAILURE(status) || !res.start) - return ERR_PTR(-ENODEV); - - mmio_base = ioremap(res.start, resource_size(&res)); - if (!mmio_base) - return ERR_PTR(-ENOMEM); - - clk = clk_register_gate(NULL, name, parent_name, 0, mmio_base + offset, - 0, 0, NULL); - if (IS_ERR(clk)) - iounmap(mmio_base); - - return clk; -} diff --git a/drivers/clk/x86/clk-lpss.h b/drivers/clk/x86/clk-lpss.h deleted file mode 100644 index e9460f44229..00000000000 --- a/drivers/clk/x86/clk-lpss.h +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Intel Low Power Subsystem clock. - * - * Copyright (C) 2013, Intel Corporation - * Authors: Mika Westerberg <mika.westerberg@linux.intel.com> - * Heikki Krogerus <heikki.krogerus@linux.intel.com> - * - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License version 2 as - * published by the Free Software Foundation. - */ - -#ifndef __CLK_LPSS_H -#define __CLK_LPSS_H - -#include <linux/err.h> -#include <linux/errno.h> -#include <linux/clk.h> - -#ifdef CONFIG_ACPI -extern struct clk *clk_register_lpss_gate(const char *name, - const char *parent_name, - const char *hid, const char *uid, - unsigned offset); -#else -static inline struct clk *clk_register_lpss_gate(const char *name, - const char *parent_name, - const char *hid, - const char *uid, - unsigned offset) -{ - return ERR_PTR(-ENODEV); -} -#endif - -#endif /* __CLK_LPSS_H */ diff --git a/drivers/clk/x86/clk-lpt.c b/drivers/clk/x86/clk-lpt.c index 81298aeef7e..5cf4f468640 100644 --- a/drivers/clk/x86/clk-lpt.c +++ b/drivers/clk/x86/clk-lpt.c @@ -10,7 +10,6 @@ * published by the Free Software Foundation. */ -#include <linux/acpi.h> #include <linux/clk.h> #include <linux/clkdev.h> #include <linux/clk-provider.h> @@ -18,8 +17,6 @@ #include <linux/module.h> #include <linux/platform_device.h> -#include "clk-lpss.h" - #define PRV_CLOCK_PARAMS 0x800 static int lpt_clk_probe(struct platform_device *pdev) @@ -34,40 +31,6 @@ static int lpt_clk_probe(struct platform_device *pdev) /* Shared DMA clock */ clk_register_clkdev(clk, "hclk", "INTL9C60.0.auto"); - - /* SPI clocks */ - clk = clk_register_lpss_gate("spi0_clk", "lpss_clk", "INT33C0", NULL, - PRV_CLOCK_PARAMS); - if (!IS_ERR(clk)) - clk_register_clkdev(clk, NULL, "INT33C0:00"); - - clk = clk_register_lpss_gate("spi1_clk", "lpss_clk", "INT33C1", NULL, - PRV_CLOCK_PARAMS); - if (!IS_ERR(clk)) - clk_register_clkdev(clk, NULL, "INT33C1:00"); - - /* I2C clocks */ - clk = clk_register_lpss_gate("i2c0_clk", "lpss_clk", "INT33C2", NULL, - PRV_CLOCK_PARAMS); - if (!IS_ERR(clk)) - clk_register_clkdev(clk, NULL, "INT33C2:00"); - - clk = clk_register_lpss_gate("i2c1_clk", "lpss_clk", "INT33C3", NULL, - PRV_CLOCK_PARAMS); - if (!IS_ERR(clk)) - clk_register_clkdev(clk, NULL, "INT33C3:00"); - - /* UART clocks */ - clk = clk_register_lpss_gate("uart0_clk", "lpss_clk", "INT33C4", NULL, - PRV_CLOCK_PARAMS); - if (!IS_ERR(clk)) - clk_register_clkdev(clk, NULL, "INT33C4:00"); - - clk = clk_register_lpss_gate("uart1_clk", "lpss_clk", "INT33C5", NULL, - PRV_CLOCK_PARAMS); - if (!IS_ERR(clk)) - clk_register_clkdev(clk, NULL, "INT33C5:00"); - return 0; } @@ -79,8 +42,7 @@ static struct platform_driver lpt_clk_driver = { .probe = lpt_clk_probe, }; -static int __init lpt_clk_init(void) +int __init lpt_clk_init(void) { return platform_driver_register(&lpt_clk_driver); } -arch_initcall(lpt_clk_init); diff --git a/drivers/cpufreq/Kconfig b/drivers/cpufreq/Kconfig index cbcb21e3277..a1488f58f6c 100644 --- a/drivers/cpufreq/Kconfig +++ b/drivers/cpufreq/Kconfig @@ -205,10 +205,99 @@ depends on ARM source "drivers/cpufreq/Kconfig.arm" endmenu +menu "AVR32 CPU frequency scaling drivers" +depends on AVR32 + +config AVR32_AT32AP_CPUFREQ + bool "CPU frequency driver for AT32AP" + depends on PLATFORM_AT32AP + default n + help + This enables the CPU frequency driver for AT32AP processors. + If in doubt, say N. + +endmenu + +menu "CPUFreq processor drivers" +depends on IA64 + +config IA64_ACPI_CPUFREQ + tristate "ACPI Processor P-States driver" + select CPU_FREQ_TABLE + depends on ACPI_PROCESSOR + help + This driver adds a CPUFreq driver which utilizes the ACPI + Processor Performance States. + + For details, take a look at <file:Documentation/cpu-freq/>. + + If in doubt, say N. + +endmenu + +menu "MIPS CPUFreq processor drivers" +depends on MIPS + +config LOONGSON2_CPUFREQ + tristate "Loongson2 CPUFreq Driver" + select CPU_FREQ_TABLE + help + This option adds a CPUFreq driver for loongson processors which + support software configurable cpu frequency. + + Loongson2F and it's successors support this feature. + + For details, take a look at <file:Documentation/cpu-freq/>. + + If in doubt, say N. + +endmenu + menu "PowerPC CPU frequency scaling drivers" depends on PPC32 || PPC64 source "drivers/cpufreq/Kconfig.powerpc" endmenu +menu "SPARC CPU frequency scaling drivers" +depends on SPARC64 +config SPARC_US3_CPUFREQ + tristate "UltraSPARC-III CPU Frequency driver" + select CPU_FREQ_TABLE + help + This adds the CPUFreq driver for UltraSPARC-III processors. + + For details, take a look at <file:Documentation/cpu-freq>. + + If in doubt, say N. + +config SPARC_US2E_CPUFREQ + tristate "UltraSPARC-IIe CPU Frequency driver" + select CPU_FREQ_TABLE + help + This adds the CPUFreq driver for UltraSPARC-IIe processors. + + For details, take a look at <file:Documentation/cpu-freq>. + + If in doubt, say N. +endmenu + +menu "SH CPU Frequency scaling" +depends on SUPERH +config SH_CPU_FREQ + tristate "SuperH CPU Frequency driver" + select CPU_FREQ_TABLE + help + This adds the cpufreq driver for SuperH. Any CPU that supports + clock rate rounding through the clock framework can use this + driver. While it will make the kernel slightly larger, this is + harmless for CPUs that don't support rate rounding. The driver + will also generate a notice in the boot log before disabling + itself if the CPU in question is not capable of rate rounding. + + For details, take a look at <file:Documentation/cpu-freq>. + + If unsure, say N. +endmenu + endif endmenu diff --git a/drivers/cpufreq/Kconfig.arm b/drivers/cpufreq/Kconfig.arm index 030ddf6dd3f..f3af18b9acc 100644 --- a/drivers/cpufreq/Kconfig.arm +++ b/drivers/cpufreq/Kconfig.arm @@ -2,6 +2,93 @@ # ARM CPU Frequency scaling drivers # +config ARM_BIG_LITTLE_CPUFREQ + tristate + depends on ARM_CPU_TOPOLOGY + +config ARM_DT_BL_CPUFREQ + tristate "Generic ARM big LITTLE CPUfreq driver probed via DT" + select ARM_BIG_LITTLE_CPUFREQ + depends on OF && HAVE_CLK + help + This enables the Generic CPUfreq driver for ARM big.LITTLE platform. + This gets frequency tables from DT. + +config ARM_EXYNOS_CPUFREQ + bool "SAMSUNG EXYNOS SoCs" + depends on ARCH_EXYNOS + default y + help + This adds the CPUFreq driver common part for Samsung + EXYNOS SoCs. + + If in doubt, say N. + +config ARM_EXYNOS4210_CPUFREQ + def_bool CPU_EXYNOS4210 + help + This adds the CPUFreq driver for Samsung EXYNOS4210 + SoC (S5PV310 or S5PC210). + +config ARM_EXYNOS4X12_CPUFREQ + def_bool (SOC_EXYNOS4212 || SOC_EXYNOS4412) + help + This adds the CPUFreq driver for Samsung EXYNOS4X12 + SoC (EXYNOS4212 or EXYNOS4412). + +config ARM_EXYNOS5250_CPUFREQ + def_bool SOC_EXYNOS5250 + help + This adds the CPUFreq driver for Samsung EXYNOS5250 + SoC. + +config ARM_EXYNOS5440_CPUFREQ + def_bool SOC_EXYNOS5440 + depends on HAVE_CLK && PM_OPP && OF + help + This adds the CPUFreq driver for Samsung EXYNOS5440 + SoC. The nature of exynos5440 clock controller is + different than previous exynos controllers so not using + the common exynos framework. + +config ARM_HIGHBANK_CPUFREQ + tristate "Calxeda Highbank-based" + depends on ARCH_HIGHBANK + select CPU_FREQ_TABLE + select GENERIC_CPUFREQ_CPU0 + select PM_OPP + select REGULATOR + + default m + help + This adds the CPUFreq driver for Calxeda Highbank SoC + based boards. + + If in doubt, say N. + +config ARM_IMX6Q_CPUFREQ + tristate "Freescale i.MX6Q cpufreq support" + depends on SOC_IMX6Q + depends on REGULATOR_ANATOP + help + This adds cpufreq driver support for Freescale i.MX6Q SOC. + + If in doubt, say N. + +config ARM_INTEGRATOR + tristate "CPUfreq driver for ARM Integrator CPUs" + depends on ARCH_INTEGRATOR + default y + help + This enables the CPUfreq driver for ARM Integrator CPUs. + If in doubt, say Y. + +config ARM_KIRKWOOD_CPUFREQ + def_bool ARCH_KIRKWOOD && OF + help + This adds the CPUFreq driver for Marvell Kirkwood + SoCs. + config ARM_OMAP2PLUS_CPUFREQ bool "TI OMAP2+" depends on ARCH_OMAP2PLUS @@ -42,6 +129,7 @@ config ARM_S3C64XX_CPUFREQ config ARM_S5PV210_CPUFREQ bool "Samsung S5PV210 and S5PC110" depends on CPU_S5PV210 + select CPU_FREQ_TABLE default y help This adds the CPUFreq driver for Samsung S5PV210 and @@ -49,48 +137,11 @@ config ARM_S5PV210_CPUFREQ If in doubt, say N. -config ARM_EXYNOS_CPUFREQ - bool "SAMSUNG EXYNOS SoCs" - depends on ARCH_EXYNOS - default y - help - This adds the CPUFreq driver common part for Samsung - EXYNOS SoCs. - - If in doubt, say N. +config ARM_SA1100_CPUFREQ + bool -config ARM_EXYNOS4210_CPUFREQ - def_bool CPU_EXYNOS4210 - help - This adds the CPUFreq driver for Samsung EXYNOS4210 - SoC (S5PV310 or S5PC210). - -config ARM_EXYNOS4X12_CPUFREQ - def_bool (SOC_EXYNOS4212 || SOC_EXYNOS4412) - help - This adds the CPUFreq driver for Samsung EXYNOS4X12 - SoC (EXYNOS4212 or EXYNOS4412). - -config ARM_EXYNOS5250_CPUFREQ - def_bool SOC_EXYNOS5250 - help - This adds the CPUFreq driver for Samsung EXYNOS5250 - SoC. - -config ARM_KIRKWOOD_CPUFREQ - def_bool ARCH_KIRKWOOD && OF - help - This adds the CPUFreq driver for Marvell Kirkwood - SoCs. - -config ARM_IMX6Q_CPUFREQ - tristate "Freescale i.MX6Q cpufreq support" - depends on SOC_IMX6Q - depends on REGULATOR_ANATOP - help - This adds cpufreq driver support for Freescale i.MX6Q SOC. - - If in doubt, say N. +config ARM_SA1110_CPUFREQ + bool config ARM_SPEAR_CPUFREQ bool "SPEAr CPUFreq support" @@ -98,18 +149,3 @@ config ARM_SPEAR_CPUFREQ default y help This adds the CPUFreq driver support for SPEAr SOCs. - -config ARM_HIGHBANK_CPUFREQ - tristate "Calxeda Highbank-based" - depends on ARCH_HIGHBANK - select CPU_FREQ_TABLE - select GENERIC_CPUFREQ_CPU0 - select PM_OPP - select REGULATOR - - default m - help - This adds the CPUFreq driver for Calxeda Highbank SoC - based boards. - - If in doubt, say N. diff --git a/drivers/cpufreq/Kconfig.powerpc b/drivers/cpufreq/Kconfig.powerpc index e76992f7968..9c926ca0d71 100644 --- a/drivers/cpufreq/Kconfig.powerpc +++ b/drivers/cpufreq/Kconfig.powerpc @@ -1,3 +1,21 @@ +config CPU_FREQ_CBE + tristate "CBE frequency scaling" + depends on CBE_RAS && PPC_CELL + default m + help + This adds the cpufreq driver for Cell BE processors. + For details, take a look at <file:Documentation/cpu-freq/>. + If you don't have such processor, say N + +config CPU_FREQ_CBE_PMI + bool "CBE frequency scaling using PMI interface" + depends on CPU_FREQ_CBE + default n + help + Select this, if you want to use the PMI interface to switch + frequencies. Using PMI, the processor will not only be able to run at + lower speed, but also at lower core voltage. + config CPU_FREQ_MAPLE bool "Support for Maple 970FX Evaluation Board" depends on PPC_MAPLE diff --git a/drivers/cpufreq/Kconfig.x86 b/drivers/cpufreq/Kconfig.x86 index d7dc0ed6adb..2b8a8c37454 100644 --- a/drivers/cpufreq/Kconfig.x86 +++ b/drivers/cpufreq/Kconfig.x86 @@ -129,6 +129,23 @@ config X86_POWERNOW_K8 For details, take a look at <file:Documentation/cpu-freq/>. +config X86_AMD_FREQ_SENSITIVITY + tristate "AMD frequency sensitivity feedback powersave bias" + depends on CPU_FREQ_GOV_ONDEMAND && X86_ACPI_CPUFREQ && CPU_SUP_AMD + help + This adds AMD-specific powersave bias function to the ondemand + governor, which allows it to make more power-conscious frequency + change decisions based on feedback from hardware (availble on AMD + Family 16h and above). + + Hardware feedback tells software how "sensitive" to frequency changes + the CPUs' workloads are. CPU-bound workloads will be more sensitive + -- they will perform better as frequency increases. Memory/IO-bound + workloads will be less sensitive -- they will not necessarily perform + better as frequency increases. + + If in doubt, say N. + config X86_GX_SUSPMOD tristate "Cyrix MediaGX/NatSemi Geode Suspend Modulation" depends on X86_32 && PCI diff --git a/drivers/cpufreq/Makefile b/drivers/cpufreq/Makefile index 863fd1865d4..315b9231feb 100644 --- a/drivers/cpufreq/Makefile +++ b/drivers/cpufreq/Makefile @@ -41,23 +41,54 @@ obj-$(CONFIG_X86_SPEEDSTEP_CENTRINO) += speedstep-centrino.o obj-$(CONFIG_X86_P4_CLOCKMOD) += p4-clockmod.o obj-$(CONFIG_X86_CPUFREQ_NFORCE2) += cpufreq-nforce2.o obj-$(CONFIG_X86_INTEL_PSTATE) += intel_pstate.o +obj-$(CONFIG_X86_AMD_FREQ_SENSITIVITY) += amd_freq_sensitivity.o ################################################################################## # ARM SoC drivers +obj-$(CONFIG_ARM_BIG_LITTLE_CPUFREQ) += arm_big_little.o +# big LITTLE per platform glues. Keep DT_BL_CPUFREQ as the last entry in all big +# LITTLE drivers, so that it is probed last. +obj-$(CONFIG_ARM_DT_BL_CPUFREQ) += arm_big_little_dt.o + +obj-$(CONFIG_ARCH_DAVINCI_DA850) += davinci-cpufreq.o obj-$(CONFIG_UX500_SOC_DB8500) += dbx500-cpufreq.o -obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o -obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o -obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o obj-$(CONFIG_ARM_EXYNOS_CPUFREQ) += exynos-cpufreq.o obj-$(CONFIG_ARM_EXYNOS4210_CPUFREQ) += exynos4210-cpufreq.o obj-$(CONFIG_ARM_EXYNOS4X12_CPUFREQ) += exynos4x12-cpufreq.o obj-$(CONFIG_ARM_EXYNOS5250_CPUFREQ) += exynos5250-cpufreq.o +obj-$(CONFIG_ARM_EXYNOS5440_CPUFREQ) += exynos5440-cpufreq.o +obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o +obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o +obj-$(CONFIG_ARM_INTEGRATOR) += integrator-cpufreq.o obj-$(CONFIG_ARM_KIRKWOOD_CPUFREQ) += kirkwood-cpufreq.o obj-$(CONFIG_ARM_OMAP2PLUS_CPUFREQ) += omap-cpufreq.o +obj-$(CONFIG_PXA25x) += pxa2xx-cpufreq.o +obj-$(CONFIG_PXA27x) += pxa2xx-cpufreq.o +obj-$(CONFIG_PXA3xx) += pxa3xx-cpufreq.o +obj-$(CONFIG_ARM_S3C2416_CPUFREQ) += s3c2416-cpufreq.o +obj-$(CONFIG_ARM_S3C64XX_CPUFREQ) += s3c64xx-cpufreq.o +obj-$(CONFIG_ARM_S5PV210_CPUFREQ) += s5pv210-cpufreq.o +obj-$(CONFIG_ARM_SA1100_CPUFREQ) += sa1100-cpufreq.o +obj-$(CONFIG_ARM_SA1110_CPUFREQ) += sa1110-cpufreq.o obj-$(CONFIG_ARM_SPEAR_CPUFREQ) += spear-cpufreq.o -obj-$(CONFIG_ARM_HIGHBANK_CPUFREQ) += highbank-cpufreq.o -obj-$(CONFIG_ARM_IMX6Q_CPUFREQ) += imx6q-cpufreq.o +obj-$(CONFIG_ARCH_TEGRA) += tegra-cpufreq.o ################################################################################## # PowerPC platform drivers +obj-$(CONFIG_CPU_FREQ_CBE) += ppc-cbe-cpufreq.o +ppc-cbe-cpufreq-y += ppc_cbe_cpufreq_pervasive.o ppc_cbe_cpufreq.o +obj-$(CONFIG_CPU_FREQ_CBE_PMI) += ppc_cbe_cpufreq_pmi.o obj-$(CONFIG_CPU_FREQ_MAPLE) += maple-cpufreq.o + +################################################################################## +# Other platform drivers +obj-$(CONFIG_AVR32_AT32AP_CPUFREQ) += at32ap-cpufreq.o +obj-$(CONFIG_BLACKFIN) += blackfin-cpufreq.o +obj-$(CONFIG_CRIS_MACH_ARTPEC3) += cris-artpec3-cpufreq.o +obj-$(CONFIG_ETRAXFS) += cris-etraxfs-cpufreq.o +obj-$(CONFIG_IA64_ACPI_CPUFREQ) += ia64-acpi-cpufreq.o +obj-$(CONFIG_LOONGSON2_CPUFREQ) += loongson2_cpufreq.o +obj-$(CONFIG_SH_CPU_FREQ) += sh-cpufreq.o +obj-$(CONFIG_SPARC_US2E_CPUFREQ) += sparc-us2e-cpufreq.o +obj-$(CONFIG_SPARC_US3_CPUFREQ) += sparc-us3-cpufreq.o +obj-$(CONFIG_UNICORE32) += unicore2-cpufreq.o diff --git a/drivers/cpufreq/acpi-cpufreq.c b/drivers/cpufreq/acpi-cpufreq.c index 57a8774f0b4..11b8b4b54ce 100644 --- a/drivers/cpufreq/acpi-cpufreq.c +++ b/drivers/cpufreq/acpi-cpufreq.c @@ -423,7 +423,6 @@ static int acpi_cpufreq_target(struct cpufreq_policy *policy, struct drv_cmd cmd; unsigned int next_state = 0; /* Index into freq_table */ unsigned int next_perf_state = 0; /* Index into perf table */ - unsigned int i; int result = 0; pr_debug("acpi_cpufreq_target %d (%d)\n", target_freq, policy->cpu); @@ -486,10 +485,7 @@ static int acpi_cpufreq_target(struct cpufreq_policy *policy, freqs.old = perf->states[perf->state].core_frequency * 1000; freqs.new = data->freq_table[next_state].frequency; - for_each_cpu(i, policy->cpus) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); drv_write(&cmd); @@ -502,10 +498,7 @@ static int acpi_cpufreq_target(struct cpufreq_policy *policy, } } - for_each_cpu(i, policy->cpus) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); perf->state = next_perf_state; out: diff --git a/drivers/cpufreq/amd_freq_sensitivity.c b/drivers/cpufreq/amd_freq_sensitivity.c new file mode 100644 index 00000000000..f6b79ab0070 --- /dev/null +++ b/drivers/cpufreq/amd_freq_sensitivity.c @@ -0,0 +1,148 @@ +/* + * amd_freq_sensitivity.c: AMD frequency sensitivity feedback powersave bias + * for the ondemand governor. + * + * Copyright (C) 2013 Advanced Micro Devices, Inc. + * + * Author: Jacob Shin <jacob.shin@amd.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/percpu-defs.h> +#include <linux/init.h> +#include <linux/mod_devicetable.h> + +#include <asm/msr.h> +#include <asm/cpufeature.h> + +#include "cpufreq_governor.h" + +#define MSR_AMD64_FREQ_SENSITIVITY_ACTUAL 0xc0010080 +#define MSR_AMD64_FREQ_SENSITIVITY_REFERENCE 0xc0010081 +#define CLASS_CODE_SHIFT 56 +#define POWERSAVE_BIAS_MAX 1000 +#define POWERSAVE_BIAS_DEF 400 + +struct cpu_data_t { + u64 actual; + u64 reference; + unsigned int freq_prev; +}; + +static DEFINE_PER_CPU(struct cpu_data_t, cpu_data); + +static unsigned int amd_powersave_bias_target(struct cpufreq_policy *policy, + unsigned int freq_next, + unsigned int relation) +{ + int sensitivity; + long d_actual, d_reference; + struct msr actual, reference; + struct cpu_data_t *data = &per_cpu(cpu_data, policy->cpu); + struct dbs_data *od_data = policy->governor_data; + struct od_dbs_tuners *od_tuners = od_data->tuners; + struct od_cpu_dbs_info_s *od_info = + od_data->cdata->get_cpu_dbs_info_s(policy->cpu); + + if (!od_info->freq_table) + return freq_next; + + rdmsr_on_cpu(policy->cpu, MSR_AMD64_FREQ_SENSITIVITY_ACTUAL, + &actual.l, &actual.h); + rdmsr_on_cpu(policy->cpu, MSR_AMD64_FREQ_SENSITIVITY_REFERENCE, + &reference.l, &reference.h); + actual.h &= 0x00ffffff; + reference.h &= 0x00ffffff; + + /* counter wrapped around, so stay on current frequency */ + if (actual.q < data->actual || reference.q < data->reference) { + freq_next = policy->cur; + goto out; + } + + d_actual = actual.q - data->actual; + d_reference = reference.q - data->reference; + + /* divide by 0, so stay on current frequency as well */ + if (d_reference == 0) { + freq_next = policy->cur; + goto out; + } + + sensitivity = POWERSAVE_BIAS_MAX - + (POWERSAVE_BIAS_MAX * (d_reference - d_actual) / d_reference); + + clamp(sensitivity, 0, POWERSAVE_BIAS_MAX); + + /* this workload is not CPU bound, so choose a lower freq */ + if (sensitivity < od_tuners->powersave_bias) { + if (data->freq_prev == policy->cur) + freq_next = policy->cur; + + if (freq_next > policy->cur) + freq_next = policy->cur; + else if (freq_next < policy->cur) + freq_next = policy->min; + else { + unsigned int index; + + cpufreq_frequency_table_target(policy, + od_info->freq_table, policy->cur - 1, + CPUFREQ_RELATION_H, &index); + freq_next = od_info->freq_table[index].frequency; + } + + data->freq_prev = freq_next; + } else + data->freq_prev = 0; + +out: + data->actual = actual.q; + data->reference = reference.q; + return freq_next; +} + +static int __init amd_freq_sensitivity_init(void) +{ + u64 val; + + if (boot_cpu_data.x86_vendor != X86_VENDOR_AMD) + return -ENODEV; + + if (!static_cpu_has(X86_FEATURE_PROC_FEEDBACK)) + return -ENODEV; + + if (rdmsrl_safe(MSR_AMD64_FREQ_SENSITIVITY_ACTUAL, &val)) + return -ENODEV; + + if (!(val >> CLASS_CODE_SHIFT)) + return -ENODEV; + + od_register_powersave_bias_handler(amd_powersave_bias_target, + POWERSAVE_BIAS_DEF); + return 0; +} +late_initcall(amd_freq_sensitivity_init); + +static void __exit amd_freq_sensitivity_exit(void) +{ + od_unregister_powersave_bias_handler(); +} +module_exit(amd_freq_sensitivity_exit); + +static const struct x86_cpu_id amd_freq_sensitivity_ids[] = { + X86_FEATURE_MATCH(X86_FEATURE_PROC_FEEDBACK), + {} +}; +MODULE_DEVICE_TABLE(x86cpu, amd_freq_sensitivity_ids); + +MODULE_AUTHOR("Jacob Shin <jacob.shin@amd.com>"); +MODULE_DESCRIPTION("AMD frequency sensitivity feedback powersave bias for " + "the ondemand governor."); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/arm_big_little.c b/drivers/cpufreq/arm_big_little.c new file mode 100644 index 00000000000..dbdf677d2f3 --- /dev/null +++ b/drivers/cpufreq/arm_big_little.c @@ -0,0 +1,278 @@ +/* + * ARM big.LITTLE Platforms CPUFreq support + * + * Copyright (C) 2013 ARM Ltd. + * Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com> + * + * Copyright (C) 2013 Linaro. + * Viresh Kumar <viresh.kumar@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/clk.h> +#include <linux/cpu.h> +#include <linux/cpufreq.h> +#include <linux/cpumask.h> +#include <linux/export.h> +#include <linux/of_platform.h> +#include <linux/opp.h> +#include <linux/slab.h> +#include <linux/topology.h> +#include <linux/types.h> + +#include "arm_big_little.h" + +/* Currently we support only two clusters */ +#define MAX_CLUSTERS 2 + +static struct cpufreq_arm_bL_ops *arm_bL_ops; +static struct clk *clk[MAX_CLUSTERS]; +static struct cpufreq_frequency_table *freq_table[MAX_CLUSTERS]; +static atomic_t cluster_usage[MAX_CLUSTERS] = {ATOMIC_INIT(0), ATOMIC_INIT(0)}; + +static int cpu_to_cluster(int cpu) +{ + return topology_physical_package_id(cpu); +} + +static unsigned int bL_cpufreq_get(unsigned int cpu) +{ + u32 cur_cluster = cpu_to_cluster(cpu); + + return clk_get_rate(clk[cur_cluster]) / 1000; +} + +/* Validate policy frequency range */ +static int bL_cpufreq_verify_policy(struct cpufreq_policy *policy) +{ + u32 cur_cluster = cpu_to_cluster(policy->cpu); + + return cpufreq_frequency_table_verify(policy, freq_table[cur_cluster]); +} + +/* Set clock frequency */ +static int bL_cpufreq_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + struct cpufreq_freqs freqs; + u32 cpu = policy->cpu, freq_tab_idx, cur_cluster; + int ret = 0; + + cur_cluster = cpu_to_cluster(policy->cpu); + + freqs.old = bL_cpufreq_get(policy->cpu); + + /* Determine valid target frequency using freq_table */ + cpufreq_frequency_table_target(policy, freq_table[cur_cluster], + target_freq, relation, &freq_tab_idx); + freqs.new = freq_table[cur_cluster][freq_tab_idx].frequency; + + pr_debug("%s: cpu: %d, cluster: %d, oldfreq: %d, target freq: %d, new freq: %d\n", + __func__, cpu, cur_cluster, freqs.old, target_freq, + freqs.new); + + if (freqs.old == freqs.new) + return 0; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + ret = clk_set_rate(clk[cur_cluster], freqs.new * 1000); + if (ret) { + pr_err("clk_set_rate failed: %d\n", ret); + return ret; + } + + policy->cur = freqs.new; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + return ret; +} + +static void put_cluster_clk_and_freq_table(struct device *cpu_dev) +{ + u32 cluster = cpu_to_cluster(cpu_dev->id); + + if (!atomic_dec_return(&cluster_usage[cluster])) { + clk_put(clk[cluster]); + opp_free_cpufreq_table(cpu_dev, &freq_table[cluster]); + dev_dbg(cpu_dev, "%s: cluster: %d\n", __func__, cluster); + } +} + +static int get_cluster_clk_and_freq_table(struct device *cpu_dev) +{ + u32 cluster = cpu_to_cluster(cpu_dev->id); + char name[14] = "cpu-cluster."; + int ret; + + if (atomic_inc_return(&cluster_usage[cluster]) != 1) + return 0; + + ret = arm_bL_ops->init_opp_table(cpu_dev); + if (ret) { + dev_err(cpu_dev, "%s: init_opp_table failed, cpu: %d, err: %d\n", + __func__, cpu_dev->id, ret); + goto atomic_dec; + } + + ret = opp_init_cpufreq_table(cpu_dev, &freq_table[cluster]); + if (ret) { + dev_err(cpu_dev, "%s: failed to init cpufreq table, cpu: %d, err: %d\n", + __func__, cpu_dev->id, ret); + goto atomic_dec; + } + + name[12] = cluster + '0'; + clk[cluster] = clk_get_sys(name, NULL); + if (!IS_ERR(clk[cluster])) { + dev_dbg(cpu_dev, "%s: clk: %p & freq table: %p, cluster: %d\n", + __func__, clk[cluster], freq_table[cluster], + cluster); + return 0; + } + + dev_err(cpu_dev, "%s: Failed to get clk for cpu: %d, cluster: %d\n", + __func__, cpu_dev->id, cluster); + ret = PTR_ERR(clk[cluster]); + opp_free_cpufreq_table(cpu_dev, &freq_table[cluster]); + +atomic_dec: + atomic_dec(&cluster_usage[cluster]); + dev_err(cpu_dev, "%s: Failed to get data for cluster: %d\n", __func__, + cluster); + return ret; +} + +/* Per-CPU initialization */ +static int bL_cpufreq_init(struct cpufreq_policy *policy) +{ + u32 cur_cluster = cpu_to_cluster(policy->cpu); + struct device *cpu_dev; + int ret; + + cpu_dev = get_cpu_device(policy->cpu); + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + policy->cpu); + return -ENODEV; + } + + ret = get_cluster_clk_and_freq_table(cpu_dev); + if (ret) + return ret; + + ret = cpufreq_frequency_table_cpuinfo(policy, freq_table[cur_cluster]); + if (ret) { + dev_err(cpu_dev, "CPU %d, cluster: %d invalid freq table\n", + policy->cpu, cur_cluster); + put_cluster_clk_and_freq_table(cpu_dev); + return ret; + } + + cpufreq_frequency_table_get_attr(freq_table[cur_cluster], policy->cpu); + + if (arm_bL_ops->get_transition_latency) + policy->cpuinfo.transition_latency = + arm_bL_ops->get_transition_latency(cpu_dev); + else + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + + policy->cur = bL_cpufreq_get(policy->cpu); + + cpumask_copy(policy->cpus, topology_core_cpumask(policy->cpu)); + + dev_info(cpu_dev, "CPU %d initialized\n", policy->cpu); + return 0; +} + +static int bL_cpufreq_exit(struct cpufreq_policy *policy) +{ + struct device *cpu_dev; + + cpu_dev = get_cpu_device(policy->cpu); + if (!cpu_dev) { + pr_err("%s: failed to get cpu%d device\n", __func__, + policy->cpu); + return -ENODEV; + } + + put_cluster_clk_and_freq_table(cpu_dev); + dev_dbg(cpu_dev, "%s: Exited, cpu: %d\n", __func__, policy->cpu); + + return 0; +} + +/* Export freq_table to sysfs */ +static struct freq_attr *bL_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver bL_cpufreq_driver = { + .name = "arm-big-little", + .flags = CPUFREQ_STICKY, + .verify = bL_cpufreq_verify_policy, + .target = bL_cpufreq_set_target, + .get = bL_cpufreq_get, + .init = bL_cpufreq_init, + .exit = bL_cpufreq_exit, + .have_governor_per_policy = true, + .attr = bL_cpufreq_attr, +}; + +int bL_cpufreq_register(struct cpufreq_arm_bL_ops *ops) +{ + int ret; + + if (arm_bL_ops) { + pr_debug("%s: Already registered: %s, exiting\n", __func__, + arm_bL_ops->name); + return -EBUSY; + } + + if (!ops || !strlen(ops->name) || !ops->init_opp_table) { + pr_err("%s: Invalid arm_bL_ops, exiting\n", __func__); + return -ENODEV; + } + + arm_bL_ops = ops; + + ret = cpufreq_register_driver(&bL_cpufreq_driver); + if (ret) { + pr_info("%s: Failed registering platform driver: %s, err: %d\n", + __func__, ops->name, ret); + arm_bL_ops = NULL; + } else { + pr_info("%s: Registered platform driver: %s\n", __func__, + ops->name); + } + + return ret; +} +EXPORT_SYMBOL_GPL(bL_cpufreq_register); + +void bL_cpufreq_unregister(struct cpufreq_arm_bL_ops *ops) +{ + if (arm_bL_ops != ops) { + pr_err("%s: Registered with: %s, can't unregister, exiting\n", + __func__, arm_bL_ops->name); + return; + } + + cpufreq_unregister_driver(&bL_cpufreq_driver); + pr_info("%s: Un-registered platform driver: %s\n", __func__, + arm_bL_ops->name); + arm_bL_ops = NULL; +} +EXPORT_SYMBOL_GPL(bL_cpufreq_unregister); diff --git a/drivers/cpufreq/arm_big_little.h b/drivers/cpufreq/arm_big_little.h new file mode 100644 index 00000000000..70f18fc12d4 --- /dev/null +++ b/drivers/cpufreq/arm_big_little.h @@ -0,0 +1,40 @@ +/* + * ARM big.LITTLE platform's CPUFreq header file + * + * Copyright (C) 2013 ARM Ltd. + * Sudeep KarkadaNagesha <sudeep.karkadanagesha@arm.com> + * + * Copyright (C) 2013 Linaro. + * Viresh Kumar <viresh.kumar@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ +#ifndef CPUFREQ_ARM_BIG_LITTLE_H +#define CPUFREQ_ARM_BIG_LITTLE_H + +#include <linux/cpufreq.h> +#include <linux/device.h> +#include <linux/types.h> + +struct cpufreq_arm_bL_ops { + char name[CPUFREQ_NAME_LEN]; + int (*get_transition_latency)(struct device *cpu_dev); + + /* + * This must set opp table for cpu_dev in a similar way as done by + * of_init_opp_table(). + */ + int (*init_opp_table)(struct device *cpu_dev); +}; + +int bL_cpufreq_register(struct cpufreq_arm_bL_ops *ops); +void bL_cpufreq_unregister(struct cpufreq_arm_bL_ops *ops); + +#endif /* CPUFREQ_ARM_BIG_LITTLE_H */ diff --git a/drivers/cpufreq/arm_big_little_dt.c b/drivers/cpufreq/arm_big_little_dt.c new file mode 100644 index 00000000000..44be3115375 --- /dev/null +++ b/drivers/cpufreq/arm_big_little_dt.c @@ -0,0 +1,107 @@ +/* + * Generic big.LITTLE CPUFreq Interface driver + * + * It provides necessary ops to arm_big_little cpufreq driver and gets + * Frequency information from Device Tree. Freq table in DT must be in KHz. + * + * Copyright (C) 2013 Linaro. + * Viresh Kumar <viresh.kumar@linaro.org> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed "as is" WITHOUT ANY WARRANTY of any + * kind, whether express or implied; without even the implied warranty + * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + */ + +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt + +#include <linux/cpufreq.h> +#include <linux/device.h> +#include <linux/export.h> +#include <linux/module.h> +#include <linux/of.h> +#include <linux/opp.h> +#include <linux/slab.h> +#include <linux/types.h> +#include "arm_big_little.h" + +static int dt_init_opp_table(struct device *cpu_dev) +{ + struct device_node *np, *parent; + int count = 0, ret; + + parent = of_find_node_by_path("/cpus"); + if (!parent) { + pr_err("failed to find OF /cpus\n"); + return -ENOENT; + } + + for_each_child_of_node(parent, np) { + if (count++ != cpu_dev->id) + continue; + if (!of_get_property(np, "operating-points", NULL)) { + ret = -ENODATA; + } else { + cpu_dev->of_node = np; + ret = of_init_opp_table(cpu_dev); + } + of_node_put(np); + of_node_put(parent); + + return ret; + } + + return -ENODEV; +} + +static int dt_get_transition_latency(struct device *cpu_dev) +{ + struct device_node *np, *parent; + u32 transition_latency = CPUFREQ_ETERNAL; + int count = 0; + + parent = of_find_node_by_path("/cpus"); + if (!parent) { + pr_err("failed to find OF /cpus\n"); + return -ENOENT; + } + + for_each_child_of_node(parent, np) { + if (count++ != cpu_dev->id) + continue; + + of_property_read_u32(np, "clock-latency", &transition_latency); + of_node_put(np); + of_node_put(parent); + + return 0; + } + + return -ENODEV; +} + +static struct cpufreq_arm_bL_ops dt_bL_ops = { + .name = "dt-bl", + .get_transition_latency = dt_get_transition_latency, + .init_opp_table = dt_init_opp_table, +}; + +static int generic_bL_init(void) +{ + return bL_cpufreq_register(&dt_bL_ops); +} +module_init(generic_bL_init); + +static void generic_bL_exit(void) +{ + return bL_cpufreq_unregister(&dt_bL_ops); +} +module_exit(generic_bL_exit); + +MODULE_AUTHOR("Viresh Kumar <viresh.kumar@linaro.org>"); +MODULE_DESCRIPTION("Generic ARM big LITTLE cpufreq driver via DT"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/at32ap-cpufreq.c b/drivers/cpufreq/at32ap-cpufreq.c new file mode 100644 index 00000000000..654488723cb --- /dev/null +++ b/drivers/cpufreq/at32ap-cpufreq.c @@ -0,0 +1,123 @@ +/* + * Copyright (C) 2004-2007 Atmel Corporation + * + * Based on MIPS implementation arch/mips/kernel/time.c + * Copyright 2001 MontaVista Software Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +/*#define DEBUG*/ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/io.h> +#include <linux/clk.h> +#include <linux/err.h> +#include <linux/export.h> + +static struct clk *cpuclk; + +static int at32_verify_speed(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + return 0; +} + +static unsigned int at32_get_speed(unsigned int cpu) +{ + /* No SMP support */ + if (cpu) + return 0; + return (unsigned int)((clk_get_rate(cpuclk) + 500) / 1000); +} + +static unsigned int ref_freq; +static unsigned long loops_per_jiffy_ref; + +static int at32_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct cpufreq_freqs freqs; + long freq; + + /* Convert target_freq from kHz to Hz */ + freq = clk_round_rate(cpuclk, target_freq * 1000); + + /* Check if policy->min <= new_freq <= policy->max */ + if(freq < (policy->min * 1000) || freq > (policy->max * 1000)) + return -EINVAL; + + pr_debug("cpufreq: requested frequency %u Hz\n", target_freq * 1000); + + freqs.old = at32_get_speed(0); + freqs.new = (freq + 500) / 1000; + freqs.flags = 0; + + if (!ref_freq) { + ref_freq = freqs.old; + loops_per_jiffy_ref = boot_cpu_data.loops_per_jiffy; + } + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + if (freqs.old < freqs.new) + boot_cpu_data.loops_per_jiffy = cpufreq_scale( + loops_per_jiffy_ref, ref_freq, freqs.new); + clk_set_rate(cpuclk, freq); + if (freqs.new < freqs.old) + boot_cpu_data.loops_per_jiffy = cpufreq_scale( + loops_per_jiffy_ref, ref_freq, freqs.new); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + pr_debug("cpufreq: set frequency %lu Hz\n", freq); + + return 0; +} + +static int __init at32_cpufreq_driver_init(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + + cpuclk = clk_get(NULL, "cpu"); + if (IS_ERR(cpuclk)) { + pr_debug("cpufreq: could not get CPU clk\n"); + return PTR_ERR(cpuclk); + } + + policy->cpuinfo.min_freq = (clk_round_rate(cpuclk, 1) + 500) / 1000; + policy->cpuinfo.max_freq = (clk_round_rate(cpuclk, ~0UL) + 500) / 1000; + policy->cpuinfo.transition_latency = 0; + policy->cur = at32_get_speed(0); + policy->min = policy->cpuinfo.min_freq; + policy->max = policy->cpuinfo.max_freq; + + printk("cpufreq: AT32AP CPU frequency driver\n"); + + return 0; +} + +static struct cpufreq_driver at32_driver = { + .name = "at32ap", + .owner = THIS_MODULE, + .init = at32_cpufreq_driver_init, + .verify = at32_verify_speed, + .target = at32_set_target, + .get = at32_get_speed, + .flags = CPUFREQ_STICKY, +}; + +static int __init at32_cpufreq_init(void) +{ + return cpufreq_register_driver(&at32_driver); +} +late_initcall(at32_cpufreq_init); diff --git a/drivers/cpufreq/blackfin-cpufreq.c b/drivers/cpufreq/blackfin-cpufreq.c new file mode 100644 index 00000000000..995511e80be --- /dev/null +++ b/drivers/cpufreq/blackfin-cpufreq.c @@ -0,0 +1,247 @@ +/* + * Blackfin core clock scaling + * + * Copyright 2008-2011 Analog Devices Inc. + * + * Licensed under the GPL-2 or later. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> +#include <linux/fs.h> +#include <linux/delay.h> +#include <asm/blackfin.h> +#include <asm/time.h> +#include <asm/dpmc.h> + + +/* this is the table of CCLK frequencies, in Hz */ +/* .index is the entry in the auxiliary dpm_state_table[] */ +static struct cpufreq_frequency_table bfin_freq_table[] = { + { + .frequency = CPUFREQ_TABLE_END, + .index = 0, + }, + { + .frequency = CPUFREQ_TABLE_END, + .index = 1, + }, + { + .frequency = CPUFREQ_TABLE_END, + .index = 2, + }, + { + .frequency = CPUFREQ_TABLE_END, + .index = 0, + }, +}; + +static struct bfin_dpm_state { + unsigned int csel; /* system clock divider */ + unsigned int tscale; /* change the divider on the core timer interrupt */ +} dpm_state_table[3]; + +#if defined(CONFIG_CYCLES_CLOCKSOURCE) +/* + * normalized to maximum frequency offset for CYCLES, + * used in time-ts cycles clock source, but could be used + * somewhere also. + */ +unsigned long long __bfin_cycles_off; +unsigned int __bfin_cycles_mod; +#endif + +/**************************************************************************/ +static void __init bfin_init_tables(unsigned long cclk, unsigned long sclk) +{ + + unsigned long csel, min_cclk; + int index; + + /* Anomaly 273 seems to still exist on non-BF54x w/dcache turned on */ +#if ANOMALY_05000273 || ANOMALY_05000274 || \ + (!(defined(CONFIG_BF54x) || defined(CONFIG_BF60x)) \ + && defined(CONFIG_BFIN_EXTMEM_DCACHEABLE)) + min_cclk = sclk * 2; +#else + min_cclk = sclk; +#endif + +#ifndef CONFIG_BF60x + csel = ((bfin_read_PLL_DIV() & CSEL) >> 4); +#else + csel = bfin_read32(CGU0_DIV) & 0x1F; +#endif + + for (index = 0; (cclk >> index) >= min_cclk && csel <= 3 && index < 3; index++, csel++) { + bfin_freq_table[index].frequency = cclk >> index; +#ifndef CONFIG_BF60x + dpm_state_table[index].csel = csel << 4; /* Shift now into PLL_DIV bitpos */ +#else + dpm_state_table[index].csel = csel; +#endif + dpm_state_table[index].tscale = (TIME_SCALE >> index) - 1; + + pr_debug("cpufreq: freq:%d csel:0x%x tscale:%d\n", + bfin_freq_table[index].frequency, + dpm_state_table[index].csel, + dpm_state_table[index].tscale); + } + return; +} + +static void bfin_adjust_core_timer(void *info) +{ + unsigned int tscale; + unsigned int index = *(unsigned int *)info; + + /* we have to adjust the core timer, because it is using cclk */ + tscale = dpm_state_table[index].tscale; + bfin_write_TSCALE(tscale); + return; +} + +static unsigned int bfin_getfreq_khz(unsigned int cpu) +{ + /* Both CoreA/B have the same core clock */ + return get_cclk() / 1000; +} + +#ifdef CONFIG_BF60x +unsigned long cpu_set_cclk(int cpu, unsigned long new) +{ + struct clk *clk; + int ret; + + clk = clk_get(NULL, "CCLK"); + if (IS_ERR(clk)) + return -ENODEV; + + ret = clk_set_rate(clk, new); + clk_put(clk); + return ret; +} +#endif + +static int bfin_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ +#ifndef CONFIG_BF60x + unsigned int plldiv; +#endif + unsigned int index; + unsigned long cclk_hz; + struct cpufreq_freqs freqs; + static unsigned long lpj_ref; + static unsigned int lpj_ref_freq; + int ret = 0; + +#if defined(CONFIG_CYCLES_CLOCKSOURCE) + cycles_t cycles; +#endif + + if (cpufreq_frequency_table_target(policy, bfin_freq_table, target_freq, + relation, &index)) + return -EINVAL; + + cclk_hz = bfin_freq_table[index].frequency; + + freqs.old = bfin_getfreq_khz(0); + freqs.new = cclk_hz; + + pr_debug("cpufreq: changing cclk to %lu; target = %u, oldfreq = %u\n", + cclk_hz, target_freq, freqs.old); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); +#ifndef CONFIG_BF60x + plldiv = (bfin_read_PLL_DIV() & SSEL) | dpm_state_table[index].csel; + bfin_write_PLL_DIV(plldiv); +#else + ret = cpu_set_cclk(policy->cpu, freqs.new * 1000); + if (ret != 0) { + WARN_ONCE(ret, "cpufreq set freq failed %d\n", ret); + return ret; + } +#endif + on_each_cpu(bfin_adjust_core_timer, &index, 1); +#if defined(CONFIG_CYCLES_CLOCKSOURCE) + cycles = get_cycles(); + SSYNC(); + cycles += 10; /* ~10 cycles we lose after get_cycles() */ + __bfin_cycles_off += (cycles << __bfin_cycles_mod) - (cycles << index); + __bfin_cycles_mod = index; +#endif + if (!lpj_ref_freq) { + lpj_ref = loops_per_jiffy; + lpj_ref_freq = freqs.old; + } + if (freqs.new != freqs.old) { + loops_per_jiffy = cpufreq_scale(lpj_ref, + lpj_ref_freq, freqs.new); + } + + /* TODO: just test case for cycles clock source, remove later */ + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + pr_debug("cpufreq: done\n"); + return ret; +} + +static int bfin_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, bfin_freq_table); +} + +static int __bfin_cpu_init(struct cpufreq_policy *policy) +{ + + unsigned long cclk, sclk; + + cclk = get_cclk() / 1000; + sclk = get_sclk() / 1000; + + if (policy->cpu == CPUFREQ_CPU) + bfin_init_tables(cclk, sclk); + + policy->cpuinfo.transition_latency = 50000; /* 50us assumed */ + + policy->cur = cclk; + cpufreq_frequency_table_get_attr(bfin_freq_table, policy->cpu); + return cpufreq_frequency_table_cpuinfo(policy, bfin_freq_table); +} + +static struct freq_attr *bfin_freq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver bfin_driver = { + .verify = bfin_verify_speed, + .target = bfin_target, + .get = bfin_getfreq_khz, + .init = __bfin_cpu_init, + .name = "bfin cpufreq", + .owner = THIS_MODULE, + .attr = bfin_freq_attr, +}; + +static int __init bfin_cpu_init(void) +{ + return cpufreq_register_driver(&bfin_driver); +} + +static void __exit bfin_cpu_exit(void) +{ + cpufreq_unregister_driver(&bfin_driver); +} + +MODULE_AUTHOR("Michael Hennerich <hennerich@blackfin.uclinux.org>"); +MODULE_DESCRIPTION("cpufreq driver for Blackfin"); +MODULE_LICENSE("GPL"); + +module_init(bfin_cpu_init); +module_exit(bfin_cpu_exit); diff --git a/drivers/cpufreq/cpufreq-cpu0.c b/drivers/cpufreq/cpufreq-cpu0.c index 37d23a0f8c5..3ab8294eab0 100644 --- a/drivers/cpufreq/cpufreq-cpu0.c +++ b/drivers/cpufreq/cpufreq-cpu0.c @@ -44,8 +44,9 @@ static int cpu0_set_target(struct cpufreq_policy *policy, { struct cpufreq_freqs freqs; struct opp *opp; - unsigned long freq_Hz, volt = 0, volt_old = 0, tol = 0; - unsigned int index, cpu; + unsigned long volt = 0, volt_old = 0, tol = 0; + long freq_Hz; + unsigned int index; int ret; ret = cpufreq_frequency_table_target(policy, freq_table, target_freq, @@ -65,10 +66,7 @@ static int cpu0_set_target(struct cpufreq_policy *policy, if (freqs.old == freqs.new) return 0; - for_each_online_cpu(cpu) { - freqs.cpu = cpu; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); if (cpu_reg) { rcu_read_lock(); @@ -76,7 +74,9 @@ static int cpu0_set_target(struct cpufreq_policy *policy, if (IS_ERR(opp)) { rcu_read_unlock(); pr_err("failed to find OPP for %ld\n", freq_Hz); - return PTR_ERR(opp); + freqs.new = freqs.old; + ret = PTR_ERR(opp); + goto post_notify; } volt = opp_get_voltage(opp); rcu_read_unlock(); @@ -94,7 +94,7 @@ static int cpu0_set_target(struct cpufreq_policy *policy, if (ret) { pr_err("failed to scale voltage up: %d\n", ret); freqs.new = freqs.old; - return ret; + goto post_notify; } } @@ -103,7 +103,8 @@ static int cpu0_set_target(struct cpufreq_policy *policy, pr_err("failed to set clock rate: %d\n", ret); if (cpu_reg) regulator_set_voltage_tol(cpu_reg, volt_old, tol); - return ret; + freqs.new = freqs.old; + goto post_notify; } /* scaling down? scale voltage after frequency */ @@ -113,25 +114,19 @@ static int cpu0_set_target(struct cpufreq_policy *policy, pr_err("failed to scale voltage down: %d\n", ret); clk_set_rate(cpu_clk, freqs.old * 1000); freqs.new = freqs.old; - return ret; } } - for_each_online_cpu(cpu) { - freqs.cpu = cpu; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } +post_notify: + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); - return 0; + return ret; } static int cpu0_cpufreq_init(struct cpufreq_policy *policy) { int ret; - if (policy->cpu != 0) - return -EINVAL; - ret = cpufreq_frequency_table_cpuinfo(policy, freq_table); if (ret) { pr_err("invalid frequency table: %d\n", ret); @@ -262,6 +257,7 @@ static int cpu0_cpufreq_probe(struct platform_device *pdev) } of_node_put(np); + of_node_put(parent); return 0; out_free_table: diff --git a/drivers/cpufreq/cpufreq-nforce2.c b/drivers/cpufreq/cpufreq-nforce2.c index 13d311ee08b..af1542d4144 100644 --- a/drivers/cpufreq/cpufreq-nforce2.c +++ b/drivers/cpufreq/cpufreq-nforce2.c @@ -263,7 +263,6 @@ static int nforce2_target(struct cpufreq_policy *policy, freqs.old = nforce2_get(policy->cpu); freqs.new = target_fsb * fid * 100; - freqs.cpu = 0; /* Only one CPU on nForce2 platforms */ if (freqs.old == freqs.new) return 0; @@ -271,7 +270,7 @@ static int nforce2_target(struct cpufreq_policy *policy, pr_debug("Old CPU frequency %d kHz, new %d kHz\n", freqs.old, freqs.new); - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); /* Disable IRQs */ /* local_irq_save(flags); */ @@ -286,7 +285,7 @@ static int nforce2_target(struct cpufreq_policy *policy, /* Enable IRQs */ /* local_irq_restore(flags); */ - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return 0; } @@ -360,12 +359,10 @@ static int nforce2_cpu_init(struct cpufreq_policy *policy) min_fsb = NFORCE2_MIN_FSB; /* cpuinfo and default policy values */ - policy->cpuinfo.min_freq = min_fsb * fid * 100; - policy->cpuinfo.max_freq = max_fsb * fid * 100; + policy->min = policy->cpuinfo.min_freq = min_fsb * fid * 100; + policy->max = policy->cpuinfo.max_freq = max_fsb * fid * 100; policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; policy->cur = nforce2_get(policy->cpu); - policy->min = policy->cpuinfo.min_freq; - policy->max = policy->cpuinfo.max_freq; return 0; } diff --git a/drivers/cpufreq/cpufreq.c b/drivers/cpufreq/cpufreq.c index b02824d092e..1b8a48eaf90 100644 --- a/drivers/cpufreq/cpufreq.c +++ b/drivers/cpufreq/cpufreq.c @@ -45,7 +45,7 @@ static DEFINE_PER_CPU(struct cpufreq_policy *, cpufreq_cpu_data); /* This one keeps track of the previously set governor of a removed CPU */ static DEFINE_PER_CPU(char[CPUFREQ_NAME_LEN], cpufreq_cpu_governor); #endif -static DEFINE_SPINLOCK(cpufreq_driver_lock); +static DEFINE_RWLOCK(cpufreq_driver_lock); /* * cpu_policy_rwsem is a per CPU reader-writer semaphore designed to cure @@ -128,6 +128,11 @@ void disable_cpufreq(void) static LIST_HEAD(cpufreq_governor_list); static DEFINE_MUTEX(cpufreq_governor_mutex); +bool have_governor_per_policy(void) +{ + return cpufreq_driver->have_governor_per_policy; +} + static struct cpufreq_policy *__cpufreq_cpu_get(unsigned int cpu, bool sysfs) { struct cpufreq_policy *data; @@ -137,7 +142,7 @@ static struct cpufreq_policy *__cpufreq_cpu_get(unsigned int cpu, bool sysfs) goto err_out; /* get the cpufreq driver */ - spin_lock_irqsave(&cpufreq_driver_lock, flags); + read_lock_irqsave(&cpufreq_driver_lock, flags); if (!cpufreq_driver) goto err_out_unlock; @@ -155,13 +160,13 @@ static struct cpufreq_policy *__cpufreq_cpu_get(unsigned int cpu, bool sysfs) if (!sysfs && !kobject_get(&data->kobj)) goto err_out_put_module; - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + read_unlock_irqrestore(&cpufreq_driver_lock, flags); return data; err_out_put_module: module_put(cpufreq_driver->owner); err_out_unlock: - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + read_unlock_irqrestore(&cpufreq_driver_lock, flags); err_out: return NULL; } @@ -244,19 +249,9 @@ static inline void adjust_jiffies(unsigned long val, struct cpufreq_freqs *ci) #endif -/** - * cpufreq_notify_transition - call notifier chain and adjust_jiffies - * on frequency transition. - * - * This function calls the transition notifiers and the "adjust_jiffies" - * function. It is called twice on all CPU frequency changes that have - * external effects. - */ -void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state) +void __cpufreq_notify_transition(struct cpufreq_policy *policy, + struct cpufreq_freqs *freqs, unsigned int state) { - struct cpufreq_policy *policy; - unsigned long flags; - BUG_ON(irqs_disabled()); if (cpufreq_disabled()) @@ -266,10 +261,6 @@ void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state) pr_debug("notification %u of frequency transition to %u kHz\n", state, freqs->new); - spin_lock_irqsave(&cpufreq_driver_lock, flags); - policy = per_cpu(cpufreq_cpu_data, freqs->cpu); - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); - switch (state) { case CPUFREQ_PRECHANGE: @@ -303,6 +294,20 @@ void cpufreq_notify_transition(struct cpufreq_freqs *freqs, unsigned int state) break; } } +/** + * cpufreq_notify_transition - call notifier chain and adjust_jiffies + * on frequency transition. + * + * This function calls the transition notifiers and the "adjust_jiffies" + * function. It is called twice on all CPU frequency changes that have + * external effects. + */ +void cpufreq_notify_transition(struct cpufreq_policy *policy, + struct cpufreq_freqs *freqs, unsigned int state) +{ + for_each_cpu(freqs->cpu, policy->cpus) + __cpufreq_notify_transition(policy, freqs, state); +} EXPORT_SYMBOL_GPL(cpufreq_notify_transition); @@ -765,12 +770,12 @@ static int cpufreq_add_dev_interface(unsigned int cpu, goto err_out_kobj_put; } - spin_lock_irqsave(&cpufreq_driver_lock, flags); + write_lock_irqsave(&cpufreq_driver_lock, flags); for_each_cpu(j, policy->cpus) { per_cpu(cpufreq_cpu_data, j) = policy; per_cpu(cpufreq_policy_cpu, j) = policy->cpu; } - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); ret = cpufreq_add_dev_symlink(cpu, policy); if (ret) @@ -803,27 +808,30 @@ static int cpufreq_add_policy_cpu(unsigned int cpu, unsigned int sibling, struct device *dev) { struct cpufreq_policy *policy; - int ret = 0; + int ret = 0, has_target = !!cpufreq_driver->target; unsigned long flags; policy = cpufreq_cpu_get(sibling); WARN_ON(!policy); - __cpufreq_governor(policy, CPUFREQ_GOV_STOP); + if (has_target) + __cpufreq_governor(policy, CPUFREQ_GOV_STOP); lock_policy_rwsem_write(sibling); - spin_lock_irqsave(&cpufreq_driver_lock, flags); + write_lock_irqsave(&cpufreq_driver_lock, flags); cpumask_set_cpu(cpu, policy->cpus); per_cpu(cpufreq_policy_cpu, cpu) = policy->cpu; per_cpu(cpufreq_cpu_data, cpu) = policy; - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); unlock_policy_rwsem_write(sibling); - __cpufreq_governor(policy, CPUFREQ_GOV_START); - __cpufreq_governor(policy, CPUFREQ_GOV_LIMITS); + if (has_target) { + __cpufreq_governor(policy, CPUFREQ_GOV_START); + __cpufreq_governor(policy, CPUFREQ_GOV_LIMITS); + } ret = sysfs_create_link(&dev->kobj, &policy->kobj, "cpufreq"); if (ret) { @@ -871,15 +879,15 @@ static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif) #ifdef CONFIG_HOTPLUG_CPU /* Check if this cpu was hot-unplugged earlier and has siblings */ - spin_lock_irqsave(&cpufreq_driver_lock, flags); + read_lock_irqsave(&cpufreq_driver_lock, flags); for_each_online_cpu(sibling) { struct cpufreq_policy *cp = per_cpu(cpufreq_cpu_data, sibling); if (cp && cpumask_test_cpu(cpu, cp->related_cpus)) { - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + read_unlock_irqrestore(&cpufreq_driver_lock, flags); return cpufreq_add_policy_cpu(cpu, sibling, dev); } } - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + read_unlock_irqrestore(&cpufreq_driver_lock, flags); #endif #endif @@ -952,10 +960,10 @@ static int cpufreq_add_dev(struct device *dev, struct subsys_interface *sif) return 0; err_out_unregister: - spin_lock_irqsave(&cpufreq_driver_lock, flags); + write_lock_irqsave(&cpufreq_driver_lock, flags); for_each_cpu(j, policy->cpus) per_cpu(cpufreq_cpu_data, j) = NULL; - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); kobject_put(&policy->kobj); wait_for_completion(&policy->kobj_unregister); @@ -1008,12 +1016,12 @@ static int __cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif pr_debug("%s: unregistering CPU %u\n", __func__, cpu); - spin_lock_irqsave(&cpufreq_driver_lock, flags); + write_lock_irqsave(&cpufreq_driver_lock, flags); data = per_cpu(cpufreq_cpu_data, cpu); per_cpu(cpufreq_cpu_data, cpu) = NULL; - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); if (!data) { pr_debug("%s: No cpu_data found\n", __func__); @@ -1031,7 +1039,9 @@ static int __cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif WARN_ON(lock_policy_rwsem_write(cpu)); cpus = cpumask_weight(data->cpus); - cpumask_clear_cpu(cpu, data->cpus); + + if (cpus > 1) + cpumask_clear_cpu(cpu, data->cpus); unlock_policy_rwsem_write(cpu); if (cpu != data->cpu) { @@ -1047,9 +1057,9 @@ static int __cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif WARN_ON(lock_policy_rwsem_write(cpu)); cpumask_set_cpu(cpu, data->cpus); - spin_lock_irqsave(&cpufreq_driver_lock, flags); + write_lock_irqsave(&cpufreq_driver_lock, flags); per_cpu(cpufreq_cpu_data, cpu) = data; - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); unlock_policy_rwsem_write(cpu); @@ -1070,6 +1080,9 @@ static int __cpufreq_remove_dev(struct device *dev, struct subsys_interface *sif /* If cpu is last user of policy, free policy */ if (cpus == 1) { + if (cpufreq_driver->target) + __cpufreq_governor(data, CPUFREQ_GOV_POLICY_EXIT); + lock_policy_rwsem_read(cpu); kobj = &data->kobj; cmp = &data->kobj_unregister; @@ -1134,16 +1147,23 @@ static void handle_update(struct work_struct *work) static void cpufreq_out_of_sync(unsigned int cpu, unsigned int old_freq, unsigned int new_freq) { + struct cpufreq_policy *policy; struct cpufreq_freqs freqs; + unsigned long flags; + pr_debug("Warning: CPU frequency out of sync: cpufreq and timing " "core thinks of %u, is %u kHz.\n", old_freq, new_freq); - freqs.cpu = cpu; freqs.old = old_freq; freqs.new = new_freq; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + + read_lock_irqsave(&cpufreq_driver_lock, flags); + policy = per_cpu(cpufreq_cpu_data, cpu); + read_unlock_irqrestore(&cpufreq_driver_lock, flags); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); } @@ -1544,10 +1564,12 @@ static int __cpufreq_governor(struct cpufreq_policy *policy, policy->cpu, event); ret = policy->governor->governor(policy, event); - if (event == CPUFREQ_GOV_START) - policy->governor->initialized++; - else if (event == CPUFREQ_GOV_STOP) - policy->governor->initialized--; + if (!ret) { + if (event == CPUFREQ_GOV_POLICY_INIT) + policy->governor->initialized++; + else if (event == CPUFREQ_GOV_POLICY_EXIT) + policy->governor->initialized--; + } /* we keep one module reference alive for each CPU governed by this CPU */ @@ -1651,7 +1673,7 @@ EXPORT_SYMBOL(cpufreq_get_policy); static int __cpufreq_set_policy(struct cpufreq_policy *data, struct cpufreq_policy *policy) { - int ret = 0; + int ret = 0, failed = 1; pr_debug("setting new policy for CPU %u: %u - %u kHz\n", policy->cpu, policy->min, policy->max); @@ -1705,18 +1727,31 @@ static int __cpufreq_set_policy(struct cpufreq_policy *data, pr_debug("governor switch\n"); /* end old governor */ - if (data->governor) + if (data->governor) { __cpufreq_governor(data, CPUFREQ_GOV_STOP); + __cpufreq_governor(data, + CPUFREQ_GOV_POLICY_EXIT); + } /* start new governor */ data->governor = policy->governor; - if (__cpufreq_governor(data, CPUFREQ_GOV_START)) { + if (!__cpufreq_governor(data, CPUFREQ_GOV_POLICY_INIT)) { + if (!__cpufreq_governor(data, CPUFREQ_GOV_START)) + failed = 0; + else + __cpufreq_governor(data, + CPUFREQ_GOV_POLICY_EXIT); + } + + if (failed) { /* new governor failed, so re-start old one */ pr_debug("starting governor %s failed\n", data->governor->name); if (old_gov) { data->governor = old_gov; __cpufreq_governor(data, + CPUFREQ_GOV_POLICY_INIT); + __cpufreq_governor(data, CPUFREQ_GOV_START); } ret = -EINVAL; @@ -1848,13 +1883,13 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data) if (driver_data->setpolicy) driver_data->flags |= CPUFREQ_CONST_LOOPS; - spin_lock_irqsave(&cpufreq_driver_lock, flags); + write_lock_irqsave(&cpufreq_driver_lock, flags); if (cpufreq_driver) { - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); return -EBUSY; } cpufreq_driver = driver_data; - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); ret = subsys_interface_register(&cpufreq_interface); if (ret) @@ -1886,9 +1921,9 @@ int cpufreq_register_driver(struct cpufreq_driver *driver_data) err_if_unreg: subsys_interface_unregister(&cpufreq_interface); err_null_driver: - spin_lock_irqsave(&cpufreq_driver_lock, flags); + write_lock_irqsave(&cpufreq_driver_lock, flags); cpufreq_driver = NULL; - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); return ret; } EXPORT_SYMBOL_GPL(cpufreq_register_driver); @@ -1914,9 +1949,9 @@ int cpufreq_unregister_driver(struct cpufreq_driver *driver) subsys_interface_unregister(&cpufreq_interface); unregister_hotcpu_notifier(&cpufreq_cpu_notifier); - spin_lock_irqsave(&cpufreq_driver_lock, flags); + write_lock_irqsave(&cpufreq_driver_lock, flags); cpufreq_driver = NULL; - spin_unlock_irqrestore(&cpufreq_driver_lock, flags); + write_unlock_irqrestore(&cpufreq_driver_lock, flags); return 0; } diff --git a/drivers/cpufreq/cpufreq_conservative.c b/drivers/cpufreq/cpufreq_conservative.c index 4fd0006b129..0ceb2eff5a7 100644 --- a/drivers/cpufreq/cpufreq_conservative.c +++ b/drivers/cpufreq/cpufreq_conservative.c @@ -20,6 +20,7 @@ #include <linux/mutex.h> #include <linux/notifier.h> #include <linux/percpu-defs.h> +#include <linux/slab.h> #include <linux/sysfs.h> #include <linux/types.h> @@ -28,25 +29,29 @@ /* Conservative governor macros */ #define DEF_FREQUENCY_UP_THRESHOLD (80) #define DEF_FREQUENCY_DOWN_THRESHOLD (20) +#define DEF_FREQUENCY_STEP (5) #define DEF_SAMPLING_DOWN_FACTOR (1) #define MAX_SAMPLING_DOWN_FACTOR (10) -static struct dbs_data cs_dbs_data; static DEFINE_PER_CPU(struct cs_cpu_dbs_info_s, cs_cpu_dbs_info); -static struct cs_dbs_tuners cs_tuners = { - .up_threshold = DEF_FREQUENCY_UP_THRESHOLD, - .down_threshold = DEF_FREQUENCY_DOWN_THRESHOLD, - .sampling_down_factor = DEF_SAMPLING_DOWN_FACTOR, - .ignore_nice = 0, - .freq_step = 5, -}; +static inline unsigned int get_freq_target(struct cs_dbs_tuners *cs_tuners, + struct cpufreq_policy *policy) +{ + unsigned int freq_target = (cs_tuners->freq_step * policy->max) / 100; + + /* max freq cannot be less than 100. But who knows... */ + if (unlikely(freq_target == 0)) + freq_target = DEF_FREQUENCY_STEP; + + return freq_target; +} /* * Every sampling_rate, we check, if current idle time is less than 20% - * (default), then we try to increase frequency Every sampling_rate * - * sampling_down_factor, we check, if current idle time is more than 80%, then - * we try to decrease frequency + * (default), then we try to increase frequency. Every sampling_rate * + * sampling_down_factor, we check, if current idle time is more than 80% + * (default), then we try to decrease frequency * * Any frequency increase takes it to the maximum frequency. Frequency reduction * happens at minimum steps of 5% (default) of maximum frequency @@ -55,30 +60,25 @@ static void cs_check_cpu(int cpu, unsigned int load) { struct cs_cpu_dbs_info_s *dbs_info = &per_cpu(cs_cpu_dbs_info, cpu); struct cpufreq_policy *policy = dbs_info->cdbs.cur_policy; - unsigned int freq_target; + struct dbs_data *dbs_data = policy->governor_data; + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; /* * break out if we 'cannot' reduce the speed as the user might * want freq_step to be zero */ - if (cs_tuners.freq_step == 0) + if (cs_tuners->freq_step == 0) return; /* Check for frequency increase */ - if (load > cs_tuners.up_threshold) { + if (load > cs_tuners->up_threshold) { dbs_info->down_skip = 0; /* if we are already at full speed then break out early */ if (dbs_info->requested_freq == policy->max) return; - freq_target = (cs_tuners.freq_step * policy->max) / 100; - - /* max freq cannot be less than 100. But who knows.... */ - if (unlikely(freq_target == 0)) - freq_target = 5; - - dbs_info->requested_freq += freq_target; + dbs_info->requested_freq += get_freq_target(cs_tuners, policy); if (dbs_info->requested_freq > policy->max) dbs_info->requested_freq = policy->max; @@ -87,45 +87,48 @@ static void cs_check_cpu(int cpu, unsigned int load) return; } - /* - * The optimal frequency is the frequency that is the lowest that can - * support the current CPU usage without triggering the up policy. To be - * safe, we focus 10 points under the threshold. - */ - if (load < (cs_tuners.down_threshold - 10)) { - freq_target = (cs_tuners.freq_step * policy->max) / 100; - - dbs_info->requested_freq -= freq_target; - if (dbs_info->requested_freq < policy->min) - dbs_info->requested_freq = policy->min; + /* if sampling_down_factor is active break out early */ + if (++dbs_info->down_skip < cs_tuners->sampling_down_factor) + return; + dbs_info->down_skip = 0; + /* Check for frequency decrease */ + if (load < cs_tuners->down_threshold) { /* * if we cannot reduce the frequency anymore, break out early */ if (policy->cur == policy->min) return; + dbs_info->requested_freq -= get_freq_target(cs_tuners, policy); + if (dbs_info->requested_freq < policy->min) + dbs_info->requested_freq = policy->min; + __cpufreq_driver_target(policy, dbs_info->requested_freq, - CPUFREQ_RELATION_H); + CPUFREQ_RELATION_L); return; } } static void cs_dbs_timer(struct work_struct *work) { - struct delayed_work *dw = to_delayed_work(work); struct cs_cpu_dbs_info_s *dbs_info = container_of(work, struct cs_cpu_dbs_info_s, cdbs.work.work); unsigned int cpu = dbs_info->cdbs.cur_policy->cpu; struct cs_cpu_dbs_info_s *core_dbs_info = &per_cpu(cs_cpu_dbs_info, cpu); - int delay = delay_for_sampling_rate(cs_tuners.sampling_rate); + struct dbs_data *dbs_data = dbs_info->cdbs.cur_policy->governor_data; + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; + int delay = delay_for_sampling_rate(cs_tuners->sampling_rate); + bool modify_all = true; mutex_lock(&core_dbs_info->cdbs.timer_mutex); - if (need_load_eval(&core_dbs_info->cdbs, cs_tuners.sampling_rate)) - dbs_check_cpu(&cs_dbs_data, cpu); + if (!need_load_eval(&core_dbs_info->cdbs, cs_tuners->sampling_rate)) + modify_all = false; + else + dbs_check_cpu(dbs_data, cpu); - schedule_delayed_work_on(smp_processor_id(), dw, delay); + gov_queue_work(dbs_data, dbs_info->cdbs.cur_policy, delay, modify_all); mutex_unlock(&core_dbs_info->cdbs.timer_mutex); } @@ -154,16 +157,12 @@ static int dbs_cpufreq_notifier(struct notifier_block *nb, unsigned long val, } /************************** sysfs interface ************************/ -static ssize_t show_sampling_rate_min(struct kobject *kobj, - struct attribute *attr, char *buf) -{ - return sprintf(buf, "%u\n", cs_dbs_data.min_sampling_rate); -} +static struct common_dbs_data cs_dbs_cdata; -static ssize_t store_sampling_down_factor(struct kobject *a, - struct attribute *b, - const char *buf, size_t count) +static ssize_t store_sampling_down_factor(struct dbs_data *dbs_data, + const char *buf, size_t count) { + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; unsigned int input; int ret; ret = sscanf(buf, "%u", &input); @@ -171,13 +170,14 @@ static ssize_t store_sampling_down_factor(struct kobject *a, if (ret != 1 || input > MAX_SAMPLING_DOWN_FACTOR || input < 1) return -EINVAL; - cs_tuners.sampling_down_factor = input; + cs_tuners->sampling_down_factor = input; return count; } -static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_sampling_rate(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; unsigned int input; int ret; ret = sscanf(buf, "%u", &input); @@ -185,43 +185,46 @@ static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b, if (ret != 1) return -EINVAL; - cs_tuners.sampling_rate = max(input, cs_dbs_data.min_sampling_rate); + cs_tuners->sampling_rate = max(input, dbs_data->min_sampling_rate); return count; } -static ssize_t store_up_threshold(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_up_threshold(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; unsigned int input; int ret; ret = sscanf(buf, "%u", &input); - if (ret != 1 || input > 100 || input <= cs_tuners.down_threshold) + if (ret != 1 || input > 100 || input <= cs_tuners->down_threshold) return -EINVAL; - cs_tuners.up_threshold = input; + cs_tuners->up_threshold = input; return count; } -static ssize_t store_down_threshold(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_down_threshold(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; unsigned int input; int ret; ret = sscanf(buf, "%u", &input); /* cannot be lower than 11 otherwise freq will not fall */ if (ret != 1 || input < 11 || input > 100 || - input >= cs_tuners.up_threshold) + input >= cs_tuners->up_threshold) return -EINVAL; - cs_tuners.down_threshold = input; + cs_tuners->down_threshold = input; return count; } -static ssize_t store_ignore_nice_load(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_ignore_nice(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; unsigned int input, j; int ret; @@ -232,27 +235,28 @@ static ssize_t store_ignore_nice_load(struct kobject *a, struct attribute *b, if (input > 1) input = 1; - if (input == cs_tuners.ignore_nice) /* nothing to do */ + if (input == cs_tuners->ignore_nice) /* nothing to do */ return count; - cs_tuners.ignore_nice = input; + cs_tuners->ignore_nice = input; /* we need to re-evaluate prev_cpu_idle */ for_each_online_cpu(j) { struct cs_cpu_dbs_info_s *dbs_info; dbs_info = &per_cpu(cs_cpu_dbs_info, j); dbs_info->cdbs.prev_cpu_idle = get_cpu_idle_time(j, - &dbs_info->cdbs.prev_cpu_wall); - if (cs_tuners.ignore_nice) + &dbs_info->cdbs.prev_cpu_wall, 0); + if (cs_tuners->ignore_nice) dbs_info->cdbs.prev_cpu_nice = kcpustat_cpu(j).cpustat[CPUTIME_NICE]; } return count; } -static ssize_t store_freq_step(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_freq_step(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; unsigned int input; int ret; ret = sscanf(buf, "%u", &input); @@ -267,43 +271,88 @@ static ssize_t store_freq_step(struct kobject *a, struct attribute *b, * no need to test here if freq_step is zero as the user might actually * want this, they would be crazy though :) */ - cs_tuners.freq_step = input; + cs_tuners->freq_step = input; return count; } -show_one(cs, sampling_rate, sampling_rate); -show_one(cs, sampling_down_factor, sampling_down_factor); -show_one(cs, up_threshold, up_threshold); -show_one(cs, down_threshold, down_threshold); -show_one(cs, ignore_nice_load, ignore_nice); -show_one(cs, freq_step, freq_step); - -define_one_global_rw(sampling_rate); -define_one_global_rw(sampling_down_factor); -define_one_global_rw(up_threshold); -define_one_global_rw(down_threshold); -define_one_global_rw(ignore_nice_load); -define_one_global_rw(freq_step); -define_one_global_ro(sampling_rate_min); - -static struct attribute *dbs_attributes[] = { - &sampling_rate_min.attr, - &sampling_rate.attr, - &sampling_down_factor.attr, - &up_threshold.attr, - &down_threshold.attr, - &ignore_nice_load.attr, - &freq_step.attr, +show_store_one(cs, sampling_rate); +show_store_one(cs, sampling_down_factor); +show_store_one(cs, up_threshold); +show_store_one(cs, down_threshold); +show_store_one(cs, ignore_nice); +show_store_one(cs, freq_step); +declare_show_sampling_rate_min(cs); + +gov_sys_pol_attr_rw(sampling_rate); +gov_sys_pol_attr_rw(sampling_down_factor); +gov_sys_pol_attr_rw(up_threshold); +gov_sys_pol_attr_rw(down_threshold); +gov_sys_pol_attr_rw(ignore_nice); +gov_sys_pol_attr_rw(freq_step); +gov_sys_pol_attr_ro(sampling_rate_min); + +static struct attribute *dbs_attributes_gov_sys[] = { + &sampling_rate_min_gov_sys.attr, + &sampling_rate_gov_sys.attr, + &sampling_down_factor_gov_sys.attr, + &up_threshold_gov_sys.attr, + &down_threshold_gov_sys.attr, + &ignore_nice_gov_sys.attr, + &freq_step_gov_sys.attr, NULL }; -static struct attribute_group cs_attr_group = { - .attrs = dbs_attributes, +static struct attribute_group cs_attr_group_gov_sys = { + .attrs = dbs_attributes_gov_sys, + .name = "conservative", +}; + +static struct attribute *dbs_attributes_gov_pol[] = { + &sampling_rate_min_gov_pol.attr, + &sampling_rate_gov_pol.attr, + &sampling_down_factor_gov_pol.attr, + &up_threshold_gov_pol.attr, + &down_threshold_gov_pol.attr, + &ignore_nice_gov_pol.attr, + &freq_step_gov_pol.attr, + NULL +}; + +static struct attribute_group cs_attr_group_gov_pol = { + .attrs = dbs_attributes_gov_pol, .name = "conservative", }; /************************** sysfs end ************************/ +static int cs_init(struct dbs_data *dbs_data) +{ + struct cs_dbs_tuners *tuners; + + tuners = kzalloc(sizeof(struct cs_dbs_tuners), GFP_KERNEL); + if (!tuners) { + pr_err("%s: kzalloc failed\n", __func__); + return -ENOMEM; + } + + tuners->up_threshold = DEF_FREQUENCY_UP_THRESHOLD; + tuners->down_threshold = DEF_FREQUENCY_DOWN_THRESHOLD; + tuners->sampling_down_factor = DEF_SAMPLING_DOWN_FACTOR; + tuners->ignore_nice = 0; + tuners->freq_step = DEF_FREQUENCY_STEP; + + dbs_data->tuners = tuners; + dbs_data->min_sampling_rate = MIN_SAMPLING_RATE_RATIO * + jiffies_to_usecs(10); + mutex_init(&dbs_data->mutex); + return 0; +} + +static void cs_exit(struct dbs_data *dbs_data) +{ + kfree(dbs_data->tuners); +} + define_get_cpu_dbs_routines(cs_cpu_dbs_info); static struct notifier_block cs_cpufreq_notifier_block = { @@ -314,21 +363,23 @@ static struct cs_ops cs_ops = { .notifier_block = &cs_cpufreq_notifier_block, }; -static struct dbs_data cs_dbs_data = { +static struct common_dbs_data cs_dbs_cdata = { .governor = GOV_CONSERVATIVE, - .attr_group = &cs_attr_group, - .tuners = &cs_tuners, + .attr_group_gov_sys = &cs_attr_group_gov_sys, + .attr_group_gov_pol = &cs_attr_group_gov_pol, .get_cpu_cdbs = get_cpu_cdbs, .get_cpu_dbs_info_s = get_cpu_dbs_info_s, .gov_dbs_timer = cs_dbs_timer, .gov_check_cpu = cs_check_cpu, .gov_ops = &cs_ops, + .init = cs_init, + .exit = cs_exit, }; static int cs_cpufreq_governor_dbs(struct cpufreq_policy *policy, unsigned int event) { - return cpufreq_governor_dbs(&cs_dbs_data, policy, event); + return cpufreq_governor_dbs(policy, &cs_dbs_cdata, event); } #ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_CONSERVATIVE @@ -343,7 +394,6 @@ struct cpufreq_governor cpufreq_gov_conservative = { static int __init cpufreq_gov_dbs_init(void) { - mutex_init(&cs_dbs_data.mutex); return cpufreq_register_governor(&cpufreq_gov_conservative); } diff --git a/drivers/cpufreq/cpufreq_governor.c b/drivers/cpufreq/cpufreq_governor.c index 5a76086ff09..443442df113 100644 --- a/drivers/cpufreq/cpufreq_governor.c +++ b/drivers/cpufreq/cpufreq_governor.c @@ -22,12 +22,29 @@ #include <linux/export.h> #include <linux/kernel_stat.h> #include <linux/mutex.h> +#include <linux/slab.h> #include <linux/tick.h> #include <linux/types.h> #include <linux/workqueue.h> #include "cpufreq_governor.h" +static struct kobject *get_governor_parent_kobj(struct cpufreq_policy *policy) +{ + if (have_governor_per_policy()) + return &policy->kobj; + else + return cpufreq_global_kobject; +} + +static struct attribute_group *get_sysfs_attr(struct dbs_data *dbs_data) +{ + if (have_governor_per_policy()) + return dbs_data->cdata->attr_group_gov_pol; + else + return dbs_data->cdata->attr_group_gov_sys; +} + static inline u64 get_cpu_idle_time_jiffy(unsigned int cpu, u64 *wall) { u64 idle_time; @@ -50,13 +67,13 @@ static inline u64 get_cpu_idle_time_jiffy(unsigned int cpu, u64 *wall) return cputime_to_usecs(idle_time); } -u64 get_cpu_idle_time(unsigned int cpu, u64 *wall) +u64 get_cpu_idle_time(unsigned int cpu, u64 *wall, int io_busy) { - u64 idle_time = get_cpu_idle_time_us(cpu, NULL); + u64 idle_time = get_cpu_idle_time_us(cpu, io_busy ? wall : NULL); if (idle_time == -1ULL) return get_cpu_idle_time_jiffy(cpu, wall); - else + else if (!io_busy) idle_time += get_cpu_iowait_time_us(cpu, wall); return idle_time; @@ -65,7 +82,7 @@ EXPORT_SYMBOL_GPL(get_cpu_idle_time); void dbs_check_cpu(struct dbs_data *dbs_data, int cpu) { - struct cpu_dbs_common_info *cdbs = dbs_data->get_cpu_cdbs(cpu); + struct cpu_dbs_common_info *cdbs = dbs_data->cdata->get_cpu_cdbs(cpu); struct od_dbs_tuners *od_tuners = dbs_data->tuners; struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; struct cpufreq_policy *policy; @@ -73,7 +90,7 @@ void dbs_check_cpu(struct dbs_data *dbs_data, int cpu) unsigned int ignore_nice; unsigned int j; - if (dbs_data->governor == GOV_ONDEMAND) + if (dbs_data->cdata->governor == GOV_ONDEMAND) ignore_nice = od_tuners->ignore_nice; else ignore_nice = cs_tuners->ignore_nice; @@ -83,13 +100,22 @@ void dbs_check_cpu(struct dbs_data *dbs_data, int cpu) /* Get Absolute Load (in terms of freq for ondemand gov) */ for_each_cpu(j, policy->cpus) { struct cpu_dbs_common_info *j_cdbs; - u64 cur_wall_time, cur_idle_time, cur_iowait_time; - unsigned int idle_time, wall_time, iowait_time; + u64 cur_wall_time, cur_idle_time; + unsigned int idle_time, wall_time; unsigned int load; + int io_busy = 0; - j_cdbs = dbs_data->get_cpu_cdbs(j); + j_cdbs = dbs_data->cdata->get_cpu_cdbs(j); - cur_idle_time = get_cpu_idle_time(j, &cur_wall_time); + /* + * For the purpose of ondemand, waiting for disk IO is + * an indication that you're performance critical, and + * not that the system is actually idle. So do not add + * the iowait time to the cpu idle time. + */ + if (dbs_data->cdata->governor == GOV_ONDEMAND) + io_busy = od_tuners->io_is_busy; + cur_idle_time = get_cpu_idle_time(j, &cur_wall_time, io_busy); wall_time = (unsigned int) (cur_wall_time - j_cdbs->prev_cpu_wall); @@ -117,35 +143,12 @@ void dbs_check_cpu(struct dbs_data *dbs_data, int cpu) idle_time += jiffies_to_usecs(cur_nice_jiffies); } - if (dbs_data->governor == GOV_ONDEMAND) { - struct od_cpu_dbs_info_s *od_j_dbs_info = - dbs_data->get_cpu_dbs_info_s(cpu); - - cur_iowait_time = get_cpu_iowait_time_us(j, - &cur_wall_time); - if (cur_iowait_time == -1ULL) - cur_iowait_time = 0; - - iowait_time = (unsigned int) (cur_iowait_time - - od_j_dbs_info->prev_cpu_iowait); - od_j_dbs_info->prev_cpu_iowait = cur_iowait_time; - - /* - * For the purpose of ondemand, waiting for disk IO is - * an indication that you're performance critical, and - * not that the system is actually idle. So subtract the - * iowait time from the cpu idle time. - */ - if (od_tuners->io_is_busy && idle_time >= iowait_time) - idle_time -= iowait_time; - } - if (unlikely(!wall_time || wall_time < idle_time)) continue; load = 100 * (wall_time - idle_time) / wall_time; - if (dbs_data->governor == GOV_ONDEMAND) { + if (dbs_data->cdata->governor == GOV_ONDEMAND) { int freq_avg = __cpufreq_driver_getavg(policy, j); if (freq_avg <= 0) freq_avg = policy->cur; @@ -157,24 +160,42 @@ void dbs_check_cpu(struct dbs_data *dbs_data, int cpu) max_load = load; } - dbs_data->gov_check_cpu(cpu, max_load); + dbs_data->cdata->gov_check_cpu(cpu, max_load); } EXPORT_SYMBOL_GPL(dbs_check_cpu); -static inline void dbs_timer_init(struct dbs_data *dbs_data, int cpu, - unsigned int sampling_rate) +static inline void __gov_queue_work(int cpu, struct dbs_data *dbs_data, + unsigned int delay) { - int delay = delay_for_sampling_rate(sampling_rate); - struct cpu_dbs_common_info *cdbs = dbs_data->get_cpu_cdbs(cpu); + struct cpu_dbs_common_info *cdbs = dbs_data->cdata->get_cpu_cdbs(cpu); - schedule_delayed_work_on(cpu, &cdbs->work, delay); + mod_delayed_work_on(cpu, system_wq, &cdbs->work, delay); } -static inline void dbs_timer_exit(struct dbs_data *dbs_data, int cpu) +void gov_queue_work(struct dbs_data *dbs_data, struct cpufreq_policy *policy, + unsigned int delay, bool all_cpus) { - struct cpu_dbs_common_info *cdbs = dbs_data->get_cpu_cdbs(cpu); + int i; - cancel_delayed_work_sync(&cdbs->work); + if (!all_cpus) { + __gov_queue_work(smp_processor_id(), dbs_data, delay); + } else { + for_each_cpu(i, policy->cpus) + __gov_queue_work(i, dbs_data, delay); + } +} +EXPORT_SYMBOL_GPL(gov_queue_work); + +static inline void gov_cancel_work(struct dbs_data *dbs_data, + struct cpufreq_policy *policy) +{ + struct cpu_dbs_common_info *cdbs; + int i; + + for_each_cpu(i, policy->cpus) { + cdbs = dbs_data->cdata->get_cpu_cdbs(i); + cancel_delayed_work_sync(&cdbs->work); + } } /* Will return if we need to evaluate cpu load again or not */ @@ -196,31 +217,130 @@ bool need_load_eval(struct cpu_dbs_common_info *cdbs, } EXPORT_SYMBOL_GPL(need_load_eval); -int cpufreq_governor_dbs(struct dbs_data *dbs_data, - struct cpufreq_policy *policy, unsigned int event) +static void set_sampling_rate(struct dbs_data *dbs_data, + unsigned int sampling_rate) { + if (dbs_data->cdata->governor == GOV_CONSERVATIVE) { + struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; + cs_tuners->sampling_rate = sampling_rate; + } else { + struct od_dbs_tuners *od_tuners = dbs_data->tuners; + od_tuners->sampling_rate = sampling_rate; + } +} + +int cpufreq_governor_dbs(struct cpufreq_policy *policy, + struct common_dbs_data *cdata, unsigned int event) +{ + struct dbs_data *dbs_data; struct od_cpu_dbs_info_s *od_dbs_info = NULL; struct cs_cpu_dbs_info_s *cs_dbs_info = NULL; - struct cs_ops *cs_ops = NULL; struct od_ops *od_ops = NULL; - struct od_dbs_tuners *od_tuners = dbs_data->tuners; - struct cs_dbs_tuners *cs_tuners = dbs_data->tuners; + struct od_dbs_tuners *od_tuners = NULL; + struct cs_dbs_tuners *cs_tuners = NULL; struct cpu_dbs_common_info *cpu_cdbs; - unsigned int *sampling_rate, latency, ignore_nice, j, cpu = policy->cpu; + unsigned int sampling_rate, latency, ignore_nice, j, cpu = policy->cpu; + int io_busy = 0; int rc; - cpu_cdbs = dbs_data->get_cpu_cdbs(cpu); + if (have_governor_per_policy()) + dbs_data = policy->governor_data; + else + dbs_data = cdata->gdbs_data; + + WARN_ON(!dbs_data && (event != CPUFREQ_GOV_POLICY_INIT)); + + switch (event) { + case CPUFREQ_GOV_POLICY_INIT: + if (have_governor_per_policy()) { + WARN_ON(dbs_data); + } else if (dbs_data) { + policy->governor_data = dbs_data; + return 0; + } + + dbs_data = kzalloc(sizeof(*dbs_data), GFP_KERNEL); + if (!dbs_data) { + pr_err("%s: POLICY_INIT: kzalloc failed\n", __func__); + return -ENOMEM; + } + + dbs_data->cdata = cdata; + rc = cdata->init(dbs_data); + if (rc) { + pr_err("%s: POLICY_INIT: init() failed\n", __func__); + kfree(dbs_data); + return rc; + } + + rc = sysfs_create_group(get_governor_parent_kobj(policy), + get_sysfs_attr(dbs_data)); + if (rc) { + cdata->exit(dbs_data); + kfree(dbs_data); + return rc; + } + + policy->governor_data = dbs_data; - if (dbs_data->governor == GOV_CONSERVATIVE) { - cs_dbs_info = dbs_data->get_cpu_dbs_info_s(cpu); - sampling_rate = &cs_tuners->sampling_rate; + /* policy latency is in nS. Convert it to uS first */ + latency = policy->cpuinfo.transition_latency / 1000; + if (latency == 0) + latency = 1; + + /* Bring kernel and HW constraints together */ + dbs_data->min_sampling_rate = max(dbs_data->min_sampling_rate, + MIN_LATENCY_MULTIPLIER * latency); + set_sampling_rate(dbs_data, max(dbs_data->min_sampling_rate, + latency * LATENCY_MULTIPLIER)); + + if (dbs_data->cdata->governor == GOV_CONSERVATIVE) { + struct cs_ops *cs_ops = dbs_data->cdata->gov_ops; + + cpufreq_register_notifier(cs_ops->notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + } + + if (!have_governor_per_policy()) + cdata->gdbs_data = dbs_data; + + return 0; + case CPUFREQ_GOV_POLICY_EXIT: + if ((policy->governor->initialized == 1) || + have_governor_per_policy()) { + sysfs_remove_group(get_governor_parent_kobj(policy), + get_sysfs_attr(dbs_data)); + + if (dbs_data->cdata->governor == GOV_CONSERVATIVE) { + struct cs_ops *cs_ops = dbs_data->cdata->gov_ops; + + cpufreq_unregister_notifier(cs_ops->notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + } + + cdata->exit(dbs_data); + kfree(dbs_data); + cdata->gdbs_data = NULL; + } + + policy->governor_data = NULL; + return 0; + } + + cpu_cdbs = dbs_data->cdata->get_cpu_cdbs(cpu); + + if (dbs_data->cdata->governor == GOV_CONSERVATIVE) { + cs_tuners = dbs_data->tuners; + cs_dbs_info = dbs_data->cdata->get_cpu_dbs_info_s(cpu); + sampling_rate = cs_tuners->sampling_rate; ignore_nice = cs_tuners->ignore_nice; - cs_ops = dbs_data->gov_ops; } else { - od_dbs_info = dbs_data->get_cpu_dbs_info_s(cpu); - sampling_rate = &od_tuners->sampling_rate; + od_tuners = dbs_data->tuners; + od_dbs_info = dbs_data->cdata->get_cpu_dbs_info_s(cpu); + sampling_rate = od_tuners->sampling_rate; ignore_nice = od_tuners->ignore_nice; - od_ops = dbs_data->gov_ops; + od_ops = dbs_data->cdata->gov_ops; + io_busy = od_tuners->io_is_busy; } switch (event) { @@ -232,96 +352,53 @@ int cpufreq_governor_dbs(struct dbs_data *dbs_data, for_each_cpu(j, policy->cpus) { struct cpu_dbs_common_info *j_cdbs = - dbs_data->get_cpu_cdbs(j); + dbs_data->cdata->get_cpu_cdbs(j); j_cdbs->cpu = j; j_cdbs->cur_policy = policy; j_cdbs->prev_cpu_idle = get_cpu_idle_time(j, - &j_cdbs->prev_cpu_wall); + &j_cdbs->prev_cpu_wall, io_busy); if (ignore_nice) j_cdbs->prev_cpu_nice = kcpustat_cpu(j).cpustat[CPUTIME_NICE]; mutex_init(&j_cdbs->timer_mutex); INIT_DEFERRABLE_WORK(&j_cdbs->work, - dbs_data->gov_dbs_timer); - } - - if (!policy->governor->initialized) { - rc = sysfs_create_group(cpufreq_global_kobject, - dbs_data->attr_group); - if (rc) { - mutex_unlock(&dbs_data->mutex); - return rc; - } + dbs_data->cdata->gov_dbs_timer); } /* * conservative does not implement micro like ondemand * governor, thus we are bound to jiffes/HZ */ - if (dbs_data->governor == GOV_CONSERVATIVE) { + if (dbs_data->cdata->governor == GOV_CONSERVATIVE) { cs_dbs_info->down_skip = 0; cs_dbs_info->enable = 1; cs_dbs_info->requested_freq = policy->cur; - - if (!policy->governor->initialized) { - cpufreq_register_notifier(cs_ops->notifier_block, - CPUFREQ_TRANSITION_NOTIFIER); - - dbs_data->min_sampling_rate = - MIN_SAMPLING_RATE_RATIO * - jiffies_to_usecs(10); - } } else { od_dbs_info->rate_mult = 1; od_dbs_info->sample_type = OD_NORMAL_SAMPLE; od_ops->powersave_bias_init_cpu(cpu); - - if (!policy->governor->initialized) - od_tuners->io_is_busy = od_ops->io_busy(); } - if (policy->governor->initialized) - goto unlock; - - /* policy latency is in nS. Convert it to uS first */ - latency = policy->cpuinfo.transition_latency / 1000; - if (latency == 0) - latency = 1; - - /* Bring kernel and HW constraints together */ - dbs_data->min_sampling_rate = max(dbs_data->min_sampling_rate, - MIN_LATENCY_MULTIPLIER * latency); - *sampling_rate = max(dbs_data->min_sampling_rate, latency * - LATENCY_MULTIPLIER); -unlock: mutex_unlock(&dbs_data->mutex); /* Initiate timer time stamp */ cpu_cdbs->time_stamp = ktime_get(); - for_each_cpu(j, policy->cpus) - dbs_timer_init(dbs_data, j, *sampling_rate); + gov_queue_work(dbs_data, policy, + delay_for_sampling_rate(sampling_rate), true); break; case CPUFREQ_GOV_STOP: - if (dbs_data->governor == GOV_CONSERVATIVE) + if (dbs_data->cdata->governor == GOV_CONSERVATIVE) cs_dbs_info->enable = 0; - for_each_cpu(j, policy->cpus) - dbs_timer_exit(dbs_data, j); + gov_cancel_work(dbs_data, policy); mutex_lock(&dbs_data->mutex); mutex_destroy(&cpu_cdbs->timer_mutex); - if (policy->governor->initialized == 1) { - sysfs_remove_group(cpufreq_global_kobject, - dbs_data->attr_group); - if (dbs_data->governor == GOV_CONSERVATIVE) - cpufreq_unregister_notifier(cs_ops->notifier_block, - CPUFREQ_TRANSITION_NOTIFIER); - } mutex_unlock(&dbs_data->mutex); break; diff --git a/drivers/cpufreq/cpufreq_governor.h b/drivers/cpufreq/cpufreq_governor.h index cc4bd2f6838..8ac33538d0b 100644 --- a/drivers/cpufreq/cpufreq_governor.h +++ b/drivers/cpufreq/cpufreq_governor.h @@ -34,20 +34,81 @@ */ #define MIN_SAMPLING_RATE_RATIO (2) #define LATENCY_MULTIPLIER (1000) -#define MIN_LATENCY_MULTIPLIER (100) +#define MIN_LATENCY_MULTIPLIER (20) #define TRANSITION_LATENCY_LIMIT (10 * 1000 * 1000) /* Ondemand Sampling types */ enum {OD_NORMAL_SAMPLE, OD_SUB_SAMPLE}; -/* Macro creating sysfs show routines */ -#define show_one(_gov, file_name, object) \ -static ssize_t show_##file_name \ +/* + * Macro for creating governors sysfs routines + * + * - gov_sys: One governor instance per whole system + * - gov_pol: One governor instance per policy + */ + +/* Create attributes */ +#define gov_sys_attr_ro(_name) \ +static struct global_attr _name##_gov_sys = \ +__ATTR(_name, 0444, show_##_name##_gov_sys, NULL) + +#define gov_sys_attr_rw(_name) \ +static struct global_attr _name##_gov_sys = \ +__ATTR(_name, 0644, show_##_name##_gov_sys, store_##_name##_gov_sys) + +#define gov_pol_attr_ro(_name) \ +static struct freq_attr _name##_gov_pol = \ +__ATTR(_name, 0444, show_##_name##_gov_pol, NULL) + +#define gov_pol_attr_rw(_name) \ +static struct freq_attr _name##_gov_pol = \ +__ATTR(_name, 0644, show_##_name##_gov_pol, store_##_name##_gov_pol) + +#define gov_sys_pol_attr_rw(_name) \ + gov_sys_attr_rw(_name); \ + gov_pol_attr_rw(_name) + +#define gov_sys_pol_attr_ro(_name) \ + gov_sys_attr_ro(_name); \ + gov_pol_attr_ro(_name) + +/* Create show/store routines */ +#define show_one(_gov, file_name) \ +static ssize_t show_##file_name##_gov_sys \ (struct kobject *kobj, struct attribute *attr, char *buf) \ { \ - return sprintf(buf, "%u\n", _gov##_tuners.object); \ + struct _gov##_dbs_tuners *tuners = _gov##_dbs_cdata.gdbs_data->tuners; \ + return sprintf(buf, "%u\n", tuners->file_name); \ +} \ + \ +static ssize_t show_##file_name##_gov_pol \ +(struct cpufreq_policy *policy, char *buf) \ +{ \ + struct dbs_data *dbs_data = policy->governor_data; \ + struct _gov##_dbs_tuners *tuners = dbs_data->tuners; \ + return sprintf(buf, "%u\n", tuners->file_name); \ +} + +#define store_one(_gov, file_name) \ +static ssize_t store_##file_name##_gov_sys \ +(struct kobject *kobj, struct attribute *attr, const char *buf, size_t count) \ +{ \ + struct dbs_data *dbs_data = _gov##_dbs_cdata.gdbs_data; \ + return store_##file_name(dbs_data, buf, count); \ +} \ + \ +static ssize_t store_##file_name##_gov_pol \ +(struct cpufreq_policy *policy, const char *buf, size_t count) \ +{ \ + struct dbs_data *dbs_data = policy->governor_data; \ + return store_##file_name(dbs_data, buf, count); \ } +#define show_store_one(_gov, file_name) \ +show_one(_gov, file_name); \ +store_one(_gov, file_name) + +/* create helper routines */ #define define_get_cpu_dbs_routines(_dbs_info) \ static struct cpu_dbs_common_info *get_cpu_cdbs(int cpu) \ { \ @@ -87,7 +148,6 @@ struct cpu_dbs_common_info { struct od_cpu_dbs_info_s { struct cpu_dbs_common_info cdbs; - u64 prev_cpu_iowait; struct cpufreq_frequency_table *freq_table; unsigned int freq_lo; unsigned int freq_lo_jiffies; @@ -103,7 +163,7 @@ struct cs_cpu_dbs_info_s { unsigned int enable:1; }; -/* Governers sysfs tunables */ +/* Per policy Governers sysfs tunables */ struct od_dbs_tuners { unsigned int ignore_nice; unsigned int sampling_rate; @@ -123,31 +183,42 @@ struct cs_dbs_tuners { unsigned int freq_step; }; -/* Per Governer data */ -struct dbs_data { +/* Common Governer data across policies */ +struct dbs_data; +struct common_dbs_data { /* Common across governors */ #define GOV_ONDEMAND 0 #define GOV_CONSERVATIVE 1 int governor; - unsigned int min_sampling_rate; - struct attribute_group *attr_group; - void *tuners; + struct attribute_group *attr_group_gov_sys; /* one governor - system */ + struct attribute_group *attr_group_gov_pol; /* one governor - policy */ - /* dbs_mutex protects dbs_enable in governor start/stop */ - struct mutex mutex; + /* Common data for platforms that don't set have_governor_per_policy */ + struct dbs_data *gdbs_data; struct cpu_dbs_common_info *(*get_cpu_cdbs)(int cpu); void *(*get_cpu_dbs_info_s)(int cpu); void (*gov_dbs_timer)(struct work_struct *work); void (*gov_check_cpu)(int cpu, unsigned int load); + int (*init)(struct dbs_data *dbs_data); + void (*exit)(struct dbs_data *dbs_data); /* Governor specific ops, see below */ void *gov_ops; }; +/* Governer Per policy data */ +struct dbs_data { + struct common_dbs_data *cdata; + unsigned int min_sampling_rate; + void *tuners; + + /* dbs_mutex protects dbs_enable in governor start/stop */ + struct mutex mutex; +}; + /* Governor specific ops, will be passed to dbs_data->gov_ops */ struct od_ops { - int (*io_busy)(void); void (*powersave_bias_init_cpu)(int cpu); unsigned int (*powersave_bias_target)(struct cpufreq_policy *policy, unsigned int freq_next, unsigned int relation); @@ -169,10 +240,31 @@ static inline int delay_for_sampling_rate(unsigned int sampling_rate) return delay; } -u64 get_cpu_idle_time(unsigned int cpu, u64 *wall); +#define declare_show_sampling_rate_min(_gov) \ +static ssize_t show_sampling_rate_min_gov_sys \ +(struct kobject *kobj, struct attribute *attr, char *buf) \ +{ \ + struct dbs_data *dbs_data = _gov##_dbs_cdata.gdbs_data; \ + return sprintf(buf, "%u\n", dbs_data->min_sampling_rate); \ +} \ + \ +static ssize_t show_sampling_rate_min_gov_pol \ +(struct cpufreq_policy *policy, char *buf) \ +{ \ + struct dbs_data *dbs_data = policy->governor_data; \ + return sprintf(buf, "%u\n", dbs_data->min_sampling_rate); \ +} + +u64 get_cpu_idle_time(unsigned int cpu, u64 *wall, int io_busy); void dbs_check_cpu(struct dbs_data *dbs_data, int cpu); bool need_load_eval(struct cpu_dbs_common_info *cdbs, unsigned int sampling_rate); -int cpufreq_governor_dbs(struct dbs_data *dbs_data, - struct cpufreq_policy *policy, unsigned int event); +int cpufreq_governor_dbs(struct cpufreq_policy *policy, + struct common_dbs_data *cdata, unsigned int event); +void gov_queue_work(struct dbs_data *dbs_data, struct cpufreq_policy *policy, + unsigned int delay, bool all_cpus); +void od_register_powersave_bias_handler(unsigned int (*f) + (struct cpufreq_policy *, unsigned int, unsigned int), + unsigned int powersave_bias); +void od_unregister_powersave_bias_handler(void); #endif /* _CPUFREQ_GOVERNOR_H */ diff --git a/drivers/cpufreq/cpufreq_ondemand.c b/drivers/cpufreq/cpufreq_ondemand.c index f3eb26cd848..b0ffef96bf7 100644 --- a/drivers/cpufreq/cpufreq_ondemand.c +++ b/drivers/cpufreq/cpufreq_ondemand.c @@ -20,9 +20,11 @@ #include <linux/module.h> #include <linux/mutex.h> #include <linux/percpu-defs.h> +#include <linux/slab.h> #include <linux/sysfs.h> #include <linux/tick.h> #include <linux/types.h> +#include <linux/cpu.h> #include "cpufreq_governor.h" @@ -37,22 +39,14 @@ #define MIN_FREQUENCY_UP_THRESHOLD (11) #define MAX_FREQUENCY_UP_THRESHOLD (100) -static struct dbs_data od_dbs_data; static DEFINE_PER_CPU(struct od_cpu_dbs_info_s, od_cpu_dbs_info); +static struct od_ops od_ops; + #ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND static struct cpufreq_governor cpufreq_gov_ondemand; #endif -static struct od_dbs_tuners od_tuners = { - .up_threshold = DEF_FREQUENCY_UP_THRESHOLD, - .sampling_down_factor = DEF_SAMPLING_DOWN_FACTOR, - .adj_up_threshold = DEF_FREQUENCY_UP_THRESHOLD - - DEF_FREQUENCY_DOWN_DIFFERENTIAL, - .ignore_nice = 0, - .powersave_bias = 0, -}; - static void ondemand_powersave_bias_init_cpu(int cpu) { struct od_cpu_dbs_info_s *dbs_info = &per_cpu(od_cpu_dbs_info, cpu); @@ -89,7 +83,7 @@ static int should_io_be_busy(void) * Returns the freq_hi to be used right now and will set freq_hi_jiffies, * freq_lo, and freq_lo_jiffies in percpu area for averaging freqs. */ -static unsigned int powersave_bias_target(struct cpufreq_policy *policy, +static unsigned int generic_powersave_bias_target(struct cpufreq_policy *policy, unsigned int freq_next, unsigned int relation) { unsigned int freq_req, freq_reduc, freq_avg; @@ -98,6 +92,8 @@ static unsigned int powersave_bias_target(struct cpufreq_policy *policy, unsigned int jiffies_total, jiffies_hi, jiffies_lo; struct od_cpu_dbs_info_s *dbs_info = &per_cpu(od_cpu_dbs_info, policy->cpu); + struct dbs_data *dbs_data = policy->governor_data; + struct od_dbs_tuners *od_tuners = dbs_data->tuners; if (!dbs_info->freq_table) { dbs_info->freq_lo = 0; @@ -108,7 +104,7 @@ static unsigned int powersave_bias_target(struct cpufreq_policy *policy, cpufreq_frequency_table_target(policy, dbs_info->freq_table, freq_next, relation, &index); freq_req = dbs_info->freq_table[index].frequency; - freq_reduc = freq_req * od_tuners.powersave_bias / 1000; + freq_reduc = freq_req * od_tuners->powersave_bias / 1000; freq_avg = freq_req - freq_reduc; /* Find freq bounds for freq_avg in freq_table */ @@ -127,7 +123,7 @@ static unsigned int powersave_bias_target(struct cpufreq_policy *policy, dbs_info->freq_lo_jiffies = 0; return freq_lo; } - jiffies_total = usecs_to_jiffies(od_tuners.sampling_rate); + jiffies_total = usecs_to_jiffies(od_tuners->sampling_rate); jiffies_hi = (freq_avg - freq_lo) * jiffies_total; jiffies_hi += ((freq_hi - freq_lo) / 2); jiffies_hi /= (freq_hi - freq_lo); @@ -148,12 +144,16 @@ static void ondemand_powersave_bias_init(void) static void dbs_freq_increase(struct cpufreq_policy *p, unsigned int freq) { - if (od_tuners.powersave_bias) - freq = powersave_bias_target(p, freq, CPUFREQ_RELATION_H); + struct dbs_data *dbs_data = p->governor_data; + struct od_dbs_tuners *od_tuners = dbs_data->tuners; + + if (od_tuners->powersave_bias) + freq = od_ops.powersave_bias_target(p, freq, + CPUFREQ_RELATION_H); else if (p->cur == p->max) return; - __cpufreq_driver_target(p, freq, od_tuners.powersave_bias ? + __cpufreq_driver_target(p, freq, od_tuners->powersave_bias ? CPUFREQ_RELATION_L : CPUFREQ_RELATION_H); } @@ -170,15 +170,17 @@ static void od_check_cpu(int cpu, unsigned int load_freq) { struct od_cpu_dbs_info_s *dbs_info = &per_cpu(od_cpu_dbs_info, cpu); struct cpufreq_policy *policy = dbs_info->cdbs.cur_policy; + struct dbs_data *dbs_data = policy->governor_data; + struct od_dbs_tuners *od_tuners = dbs_data->tuners; dbs_info->freq_lo = 0; /* Check for frequency increase */ - if (load_freq > od_tuners.up_threshold * policy->cur) { + if (load_freq > od_tuners->up_threshold * policy->cur) { /* If switching to max speed, apply sampling_down_factor */ if (policy->cur < policy->max) dbs_info->rate_mult = - od_tuners.sampling_down_factor; + od_tuners->sampling_down_factor; dbs_freq_increase(policy, policy->max); return; } @@ -193,9 +195,10 @@ static void od_check_cpu(int cpu, unsigned int load_freq) * support the current CPU usage without triggering the up policy. To be * safe, we focus 10 points under the threshold. */ - if (load_freq < od_tuners.adj_up_threshold * policy->cur) { + if (load_freq < od_tuners->adj_up_threshold + * policy->cur) { unsigned int freq_next; - freq_next = load_freq / od_tuners.adj_up_threshold; + freq_next = load_freq / od_tuners->adj_up_threshold; /* No longer fully busy, reset rate_mult */ dbs_info->rate_mult = 1; @@ -203,65 +206,62 @@ static void od_check_cpu(int cpu, unsigned int load_freq) if (freq_next < policy->min) freq_next = policy->min; - if (!od_tuners.powersave_bias) { + if (!od_tuners->powersave_bias) { __cpufreq_driver_target(policy, freq_next, CPUFREQ_RELATION_L); - } else { - int freq = powersave_bias_target(policy, freq_next, - CPUFREQ_RELATION_L); - __cpufreq_driver_target(policy, freq, - CPUFREQ_RELATION_L); + return; } + + freq_next = od_ops.powersave_bias_target(policy, freq_next, + CPUFREQ_RELATION_L); + __cpufreq_driver_target(policy, freq_next, CPUFREQ_RELATION_L); } } static void od_dbs_timer(struct work_struct *work) { - struct delayed_work *dw = to_delayed_work(work); struct od_cpu_dbs_info_s *dbs_info = container_of(work, struct od_cpu_dbs_info_s, cdbs.work.work); unsigned int cpu = dbs_info->cdbs.cur_policy->cpu; struct od_cpu_dbs_info_s *core_dbs_info = &per_cpu(od_cpu_dbs_info, cpu); - int delay, sample_type = core_dbs_info->sample_type; - bool eval_load; + struct dbs_data *dbs_data = dbs_info->cdbs.cur_policy->governor_data; + struct od_dbs_tuners *od_tuners = dbs_data->tuners; + int delay = 0, sample_type = core_dbs_info->sample_type; + bool modify_all = true; mutex_lock(&core_dbs_info->cdbs.timer_mutex); - eval_load = need_load_eval(&core_dbs_info->cdbs, - od_tuners.sampling_rate); + if (!need_load_eval(&core_dbs_info->cdbs, od_tuners->sampling_rate)) { + modify_all = false; + goto max_delay; + } /* Common NORMAL_SAMPLE setup */ core_dbs_info->sample_type = OD_NORMAL_SAMPLE; if (sample_type == OD_SUB_SAMPLE) { delay = core_dbs_info->freq_lo_jiffies; - if (eval_load) - __cpufreq_driver_target(core_dbs_info->cdbs.cur_policy, - core_dbs_info->freq_lo, - CPUFREQ_RELATION_H); + __cpufreq_driver_target(core_dbs_info->cdbs.cur_policy, + core_dbs_info->freq_lo, CPUFREQ_RELATION_H); } else { - if (eval_load) - dbs_check_cpu(&od_dbs_data, cpu); + dbs_check_cpu(dbs_data, cpu); if (core_dbs_info->freq_lo) { /* Setup timer for SUB_SAMPLE */ core_dbs_info->sample_type = OD_SUB_SAMPLE; delay = core_dbs_info->freq_hi_jiffies; - } else { - delay = delay_for_sampling_rate(od_tuners.sampling_rate - * core_dbs_info->rate_mult); } } - schedule_delayed_work_on(smp_processor_id(), dw, delay); +max_delay: + if (!delay) + delay = delay_for_sampling_rate(od_tuners->sampling_rate + * core_dbs_info->rate_mult); + + gov_queue_work(dbs_data, dbs_info->cdbs.cur_policy, delay, modify_all); mutex_unlock(&core_dbs_info->cdbs.timer_mutex); } /************************** sysfs interface ************************/ - -static ssize_t show_sampling_rate_min(struct kobject *kobj, - struct attribute *attr, char *buf) -{ - return sprintf(buf, "%u\n", od_dbs_data.min_sampling_rate); -} +static struct common_dbs_data od_dbs_cdata; /** * update_sampling_rate - update sampling rate effective immediately if needed. @@ -276,12 +276,14 @@ static ssize_t show_sampling_rate_min(struct kobject *kobj, * reducing the sampling rate, we need to make the new value effective * immediately. */ -static void update_sampling_rate(unsigned int new_rate) +static void update_sampling_rate(struct dbs_data *dbs_data, + unsigned int new_rate) { + struct od_dbs_tuners *od_tuners = dbs_data->tuners; int cpu; - od_tuners.sampling_rate = new_rate = max(new_rate, - od_dbs_data.min_sampling_rate); + od_tuners->sampling_rate = new_rate = max(new_rate, + dbs_data->min_sampling_rate); for_each_online_cpu(cpu) { struct cpufreq_policy *policy; @@ -314,42 +316,54 @@ static void update_sampling_rate(unsigned int new_rate) cancel_delayed_work_sync(&dbs_info->cdbs.work); mutex_lock(&dbs_info->cdbs.timer_mutex); - schedule_delayed_work_on(cpu, &dbs_info->cdbs.work, - usecs_to_jiffies(new_rate)); + gov_queue_work(dbs_data, dbs_info->cdbs.cur_policy, + usecs_to_jiffies(new_rate), true); } mutex_unlock(&dbs_info->cdbs.timer_mutex); } } -static ssize_t store_sampling_rate(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_sampling_rate(struct dbs_data *dbs_data, const char *buf, + size_t count) { unsigned int input; int ret; ret = sscanf(buf, "%u", &input); if (ret != 1) return -EINVAL; - update_sampling_rate(input); + + update_sampling_rate(dbs_data, input); return count; } -static ssize_t store_io_is_busy(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_io_is_busy(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct od_dbs_tuners *od_tuners = dbs_data->tuners; unsigned int input; int ret; + unsigned int j; ret = sscanf(buf, "%u", &input); if (ret != 1) return -EINVAL; - od_tuners.io_is_busy = !!input; + od_tuners->io_is_busy = !!input; + + /* we need to re-evaluate prev_cpu_idle */ + for_each_online_cpu(j) { + struct od_cpu_dbs_info_s *dbs_info = &per_cpu(od_cpu_dbs_info, + j); + dbs_info->cdbs.prev_cpu_idle = get_cpu_idle_time(j, + &dbs_info->cdbs.prev_cpu_wall, od_tuners->io_is_busy); + } return count; } -static ssize_t store_up_threshold(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_up_threshold(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct od_dbs_tuners *od_tuners = dbs_data->tuners; unsigned int input; int ret; ret = sscanf(buf, "%u", &input); @@ -359,23 +373,24 @@ static ssize_t store_up_threshold(struct kobject *a, struct attribute *b, return -EINVAL; } /* Calculate the new adj_up_threshold */ - od_tuners.adj_up_threshold += input; - od_tuners.adj_up_threshold -= od_tuners.up_threshold; + od_tuners->adj_up_threshold += input; + od_tuners->adj_up_threshold -= od_tuners->up_threshold; - od_tuners.up_threshold = input; + od_tuners->up_threshold = input; return count; } -static ssize_t store_sampling_down_factor(struct kobject *a, - struct attribute *b, const char *buf, size_t count) +static ssize_t store_sampling_down_factor(struct dbs_data *dbs_data, + const char *buf, size_t count) { + struct od_dbs_tuners *od_tuners = dbs_data->tuners; unsigned int input, j; int ret; ret = sscanf(buf, "%u", &input); if (ret != 1 || input > MAX_SAMPLING_DOWN_FACTOR || input < 1) return -EINVAL; - od_tuners.sampling_down_factor = input; + od_tuners->sampling_down_factor = input; /* Reset down sampling multiplier in case it was active */ for_each_online_cpu(j) { @@ -386,9 +401,10 @@ static ssize_t store_sampling_down_factor(struct kobject *a, return count; } -static ssize_t store_ignore_nice_load(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_ignore_nice(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct od_dbs_tuners *od_tuners = dbs_data->tuners; unsigned int input; int ret; @@ -401,18 +417,18 @@ static ssize_t store_ignore_nice_load(struct kobject *a, struct attribute *b, if (input > 1) input = 1; - if (input == od_tuners.ignore_nice) { /* nothing to do */ + if (input == od_tuners->ignore_nice) { /* nothing to do */ return count; } - od_tuners.ignore_nice = input; + od_tuners->ignore_nice = input; /* we need to re-evaluate prev_cpu_idle */ for_each_online_cpu(j) { struct od_cpu_dbs_info_s *dbs_info; dbs_info = &per_cpu(od_cpu_dbs_info, j); dbs_info->cdbs.prev_cpu_idle = get_cpu_idle_time(j, - &dbs_info->cdbs.prev_cpu_wall); - if (od_tuners.ignore_nice) + &dbs_info->cdbs.prev_cpu_wall, od_tuners->io_is_busy); + if (od_tuners->ignore_nice) dbs_info->cdbs.prev_cpu_nice = kcpustat_cpu(j).cpustat[CPUTIME_NICE]; @@ -420,9 +436,10 @@ static ssize_t store_ignore_nice_load(struct kobject *a, struct attribute *b, return count; } -static ssize_t store_powersave_bias(struct kobject *a, struct attribute *b, - const char *buf, size_t count) +static ssize_t store_powersave_bias(struct dbs_data *dbs_data, const char *buf, + size_t count) { + struct od_dbs_tuners *od_tuners = dbs_data->tuners; unsigned int input; int ret; ret = sscanf(buf, "%u", &input); @@ -433,68 +450,179 @@ static ssize_t store_powersave_bias(struct kobject *a, struct attribute *b, if (input > 1000) input = 1000; - od_tuners.powersave_bias = input; + od_tuners->powersave_bias = input; ondemand_powersave_bias_init(); return count; } -show_one(od, sampling_rate, sampling_rate); -show_one(od, io_is_busy, io_is_busy); -show_one(od, up_threshold, up_threshold); -show_one(od, sampling_down_factor, sampling_down_factor); -show_one(od, ignore_nice_load, ignore_nice); -show_one(od, powersave_bias, powersave_bias); - -define_one_global_rw(sampling_rate); -define_one_global_rw(io_is_busy); -define_one_global_rw(up_threshold); -define_one_global_rw(sampling_down_factor); -define_one_global_rw(ignore_nice_load); -define_one_global_rw(powersave_bias); -define_one_global_ro(sampling_rate_min); - -static struct attribute *dbs_attributes[] = { - &sampling_rate_min.attr, - &sampling_rate.attr, - &up_threshold.attr, - &sampling_down_factor.attr, - &ignore_nice_load.attr, - &powersave_bias.attr, - &io_is_busy.attr, +show_store_one(od, sampling_rate); +show_store_one(od, io_is_busy); +show_store_one(od, up_threshold); +show_store_one(od, sampling_down_factor); +show_store_one(od, ignore_nice); +show_store_one(od, powersave_bias); +declare_show_sampling_rate_min(od); + +gov_sys_pol_attr_rw(sampling_rate); +gov_sys_pol_attr_rw(io_is_busy); +gov_sys_pol_attr_rw(up_threshold); +gov_sys_pol_attr_rw(sampling_down_factor); +gov_sys_pol_attr_rw(ignore_nice); +gov_sys_pol_attr_rw(powersave_bias); +gov_sys_pol_attr_ro(sampling_rate_min); + +static struct attribute *dbs_attributes_gov_sys[] = { + &sampling_rate_min_gov_sys.attr, + &sampling_rate_gov_sys.attr, + &up_threshold_gov_sys.attr, + &sampling_down_factor_gov_sys.attr, + &ignore_nice_gov_sys.attr, + &powersave_bias_gov_sys.attr, + &io_is_busy_gov_sys.attr, + NULL +}; + +static struct attribute_group od_attr_group_gov_sys = { + .attrs = dbs_attributes_gov_sys, + .name = "ondemand", +}; + +static struct attribute *dbs_attributes_gov_pol[] = { + &sampling_rate_min_gov_pol.attr, + &sampling_rate_gov_pol.attr, + &up_threshold_gov_pol.attr, + &sampling_down_factor_gov_pol.attr, + &ignore_nice_gov_pol.attr, + &powersave_bias_gov_pol.attr, + &io_is_busy_gov_pol.attr, NULL }; -static struct attribute_group od_attr_group = { - .attrs = dbs_attributes, +static struct attribute_group od_attr_group_gov_pol = { + .attrs = dbs_attributes_gov_pol, .name = "ondemand", }; /************************** sysfs end ************************/ +static int od_init(struct dbs_data *dbs_data) +{ + struct od_dbs_tuners *tuners; + u64 idle_time; + int cpu; + + tuners = kzalloc(sizeof(struct od_dbs_tuners), GFP_KERNEL); + if (!tuners) { + pr_err("%s: kzalloc failed\n", __func__); + return -ENOMEM; + } + + cpu = get_cpu(); + idle_time = get_cpu_idle_time_us(cpu, NULL); + put_cpu(); + if (idle_time != -1ULL) { + /* Idle micro accounting is supported. Use finer thresholds */ + tuners->up_threshold = MICRO_FREQUENCY_UP_THRESHOLD; + tuners->adj_up_threshold = MICRO_FREQUENCY_UP_THRESHOLD - + MICRO_FREQUENCY_DOWN_DIFFERENTIAL; + /* + * In nohz/micro accounting case we set the minimum frequency + * not depending on HZ, but fixed (very low). The deferred + * timer might skip some samples if idle/sleeping as needed. + */ + dbs_data->min_sampling_rate = MICRO_FREQUENCY_MIN_SAMPLE_RATE; + } else { + tuners->up_threshold = DEF_FREQUENCY_UP_THRESHOLD; + tuners->adj_up_threshold = DEF_FREQUENCY_UP_THRESHOLD - + DEF_FREQUENCY_DOWN_DIFFERENTIAL; + + /* For correct statistics, we need 10 ticks for each measure */ + dbs_data->min_sampling_rate = MIN_SAMPLING_RATE_RATIO * + jiffies_to_usecs(10); + } + + tuners->sampling_down_factor = DEF_SAMPLING_DOWN_FACTOR; + tuners->ignore_nice = 0; + tuners->powersave_bias = 0; + tuners->io_is_busy = should_io_be_busy(); + + dbs_data->tuners = tuners; + pr_info("%s: tuners %p\n", __func__, tuners); + mutex_init(&dbs_data->mutex); + return 0; +} + +static void od_exit(struct dbs_data *dbs_data) +{ + kfree(dbs_data->tuners); +} + define_get_cpu_dbs_routines(od_cpu_dbs_info); static struct od_ops od_ops = { - .io_busy = should_io_be_busy, .powersave_bias_init_cpu = ondemand_powersave_bias_init_cpu, - .powersave_bias_target = powersave_bias_target, + .powersave_bias_target = generic_powersave_bias_target, .freq_increase = dbs_freq_increase, }; -static struct dbs_data od_dbs_data = { +static struct common_dbs_data od_dbs_cdata = { .governor = GOV_ONDEMAND, - .attr_group = &od_attr_group, - .tuners = &od_tuners, + .attr_group_gov_sys = &od_attr_group_gov_sys, + .attr_group_gov_pol = &od_attr_group_gov_pol, .get_cpu_cdbs = get_cpu_cdbs, .get_cpu_dbs_info_s = get_cpu_dbs_info_s, .gov_dbs_timer = od_dbs_timer, .gov_check_cpu = od_check_cpu, .gov_ops = &od_ops, + .init = od_init, + .exit = od_exit, }; +static void od_set_powersave_bias(unsigned int powersave_bias) +{ + struct cpufreq_policy *policy; + struct dbs_data *dbs_data; + struct od_dbs_tuners *od_tuners; + unsigned int cpu; + cpumask_t done; + + cpumask_clear(&done); + + get_online_cpus(); + for_each_online_cpu(cpu) { + if (cpumask_test_cpu(cpu, &done)) + continue; + + policy = per_cpu(od_cpu_dbs_info, cpu).cdbs.cur_policy; + dbs_data = policy->governor_data; + od_tuners = dbs_data->tuners; + od_tuners->powersave_bias = powersave_bias; + + cpumask_or(&done, &done, policy->cpus); + } + put_online_cpus(); +} + +void od_register_powersave_bias_handler(unsigned int (*f) + (struct cpufreq_policy *, unsigned int, unsigned int), + unsigned int powersave_bias) +{ + od_ops.powersave_bias_target = f; + od_set_powersave_bias(powersave_bias); +} +EXPORT_SYMBOL_GPL(od_register_powersave_bias_handler); + +void od_unregister_powersave_bias_handler(void) +{ + od_ops.powersave_bias_target = generic_powersave_bias_target; + od_set_powersave_bias(0); +} +EXPORT_SYMBOL_GPL(od_unregister_powersave_bias_handler); + static int od_cpufreq_governor_dbs(struct cpufreq_policy *policy, unsigned int event) { - return cpufreq_governor_dbs(&od_dbs_data, policy, event); + return cpufreq_governor_dbs(policy, &od_dbs_cdata, event); } #ifndef CONFIG_CPU_FREQ_DEFAULT_GOV_ONDEMAND @@ -509,29 +637,6 @@ struct cpufreq_governor cpufreq_gov_ondemand = { static int __init cpufreq_gov_dbs_init(void) { - u64 idle_time; - int cpu = get_cpu(); - - mutex_init(&od_dbs_data.mutex); - idle_time = get_cpu_idle_time_us(cpu, NULL); - put_cpu(); - if (idle_time != -1ULL) { - /* Idle micro accounting is supported. Use finer thresholds */ - od_tuners.up_threshold = MICRO_FREQUENCY_UP_THRESHOLD; - od_tuners.adj_up_threshold = MICRO_FREQUENCY_UP_THRESHOLD - - MICRO_FREQUENCY_DOWN_DIFFERENTIAL; - /* - * In nohz/micro accounting case we set the minimum frequency - * not depending on HZ, but fixed (very low). The deferred - * timer might skip some samples if idle/sleeping as needed. - */ - od_dbs_data.min_sampling_rate = MICRO_FREQUENCY_MIN_SAMPLE_RATE; - } else { - /* For correct statistics, we need 10 ticks for each measure */ - od_dbs_data.min_sampling_rate = MIN_SAMPLING_RATE_RATIO * - jiffies_to_usecs(10); - } - return cpufreq_register_governor(&cpufreq_gov_ondemand); } diff --git a/drivers/cpufreq/cris-artpec3-cpufreq.c b/drivers/cpufreq/cris-artpec3-cpufreq.c new file mode 100644 index 00000000000..ee142c49057 --- /dev/null +++ b/drivers/cpufreq/cris-artpec3-cpufreq.c @@ -0,0 +1,146 @@ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/cpufreq.h> +#include <hwregs/reg_map.h> +#include <hwregs/reg_rdwr.h> +#include <hwregs/clkgen_defs.h> +#include <hwregs/ddr2_defs.h> + +static int +cris_sdram_freq_notifier(struct notifier_block *nb, unsigned long val, + void *data); + +static struct notifier_block cris_sdram_freq_notifier_block = { + .notifier_call = cris_sdram_freq_notifier +}; + +static struct cpufreq_frequency_table cris_freq_table[] = { + {0x01, 6000}, + {0x02, 200000}, + {0, CPUFREQ_TABLE_END}, +}; + +static unsigned int cris_freq_get_cpu_frequency(unsigned int cpu) +{ + reg_clkgen_rw_clk_ctrl clk_ctrl; + clk_ctrl = REG_RD(clkgen, regi_clkgen, rw_clk_ctrl); + return clk_ctrl.pll ? 200000 : 6000; +} + +static void cris_freq_set_cpu_state(struct cpufreq_policy *policy, + unsigned int state) +{ + struct cpufreq_freqs freqs; + reg_clkgen_rw_clk_ctrl clk_ctrl; + clk_ctrl = REG_RD(clkgen, regi_clkgen, rw_clk_ctrl); + + freqs.old = cris_freq_get_cpu_frequency(policy->cpu); + freqs.new = cris_freq_table[state].frequency; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + local_irq_disable(); + + /* Even though we may be SMP they will share the same clock + * so all settings are made on CPU0. */ + if (cris_freq_table[state].frequency == 200000) + clk_ctrl.pll = 1; + else + clk_ctrl.pll = 0; + REG_WR(clkgen, regi_clkgen, rw_clk_ctrl, clk_ctrl); + + local_irq_enable(); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); +}; + +static int cris_freq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, &cris_freq_table[0]); +} + +static int cris_freq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int newstate = 0; + + if (cpufreq_frequency_table_target(policy, cris_freq_table, + target_freq, relation, &newstate)) + return -EINVAL; + + cris_freq_set_cpu_state(policy, newstate); + + return 0; +} + +static int cris_freq_cpu_init(struct cpufreq_policy *policy) +{ + int result; + + /* cpuinfo and default policy values */ + policy->cpuinfo.transition_latency = 1000000; /* 1ms */ + policy->cur = cris_freq_get_cpu_frequency(0); + + result = cpufreq_frequency_table_cpuinfo(policy, cris_freq_table); + if (result) + return (result); + + cpufreq_frequency_table_get_attr(cris_freq_table, policy->cpu); + + return 0; +} + + +static int cris_freq_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + return 0; +} + + +static struct freq_attr *cris_freq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver cris_freq_driver = { + .get = cris_freq_get_cpu_frequency, + .verify = cris_freq_verify, + .target = cris_freq_target, + .init = cris_freq_cpu_init, + .exit = cris_freq_cpu_exit, + .name = "cris_freq", + .owner = THIS_MODULE, + .attr = cris_freq_attr, +}; + +static int __init cris_freq_init(void) +{ + int ret; + ret = cpufreq_register_driver(&cris_freq_driver); + cpufreq_register_notifier(&cris_sdram_freq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + return ret; +} + +static int +cris_sdram_freq_notifier(struct notifier_block *nb, unsigned long val, + void *data) +{ + int i; + struct cpufreq_freqs *freqs = data; + if (val == CPUFREQ_PRECHANGE) { + reg_ddr2_rw_cfg cfg = + REG_RD(ddr2, regi_ddr2_ctrl, rw_cfg); + cfg.ref_interval = (freqs->new == 200000 ? 1560 : 46); + + if (freqs->new == 200000) + for (i = 0; i < 50000; i++); + REG_WR(bif_core, regi_bif_core, rw_sdram_timing, timing); + } + return 0; +} + + +module_init(cris_freq_init); diff --git a/drivers/cpufreq/cris-etraxfs-cpufreq.c b/drivers/cpufreq/cris-etraxfs-cpufreq.c new file mode 100644 index 00000000000..12952235d5d --- /dev/null +++ b/drivers/cpufreq/cris-etraxfs-cpufreq.c @@ -0,0 +1,142 @@ +#include <linux/init.h> +#include <linux/module.h> +#include <linux/cpufreq.h> +#include <hwregs/reg_map.h> +#include <arch/hwregs/reg_rdwr.h> +#include <arch/hwregs/config_defs.h> +#include <arch/hwregs/bif_core_defs.h> + +static int +cris_sdram_freq_notifier(struct notifier_block *nb, unsigned long val, + void *data); + +static struct notifier_block cris_sdram_freq_notifier_block = { + .notifier_call = cris_sdram_freq_notifier +}; + +static struct cpufreq_frequency_table cris_freq_table[] = { + {0x01, 6000}, + {0x02, 200000}, + {0, CPUFREQ_TABLE_END}, +}; + +static unsigned int cris_freq_get_cpu_frequency(unsigned int cpu) +{ + reg_config_rw_clk_ctrl clk_ctrl; + clk_ctrl = REG_RD(config, regi_config, rw_clk_ctrl); + return clk_ctrl.pll ? 200000 : 6000; +} + +static void cris_freq_set_cpu_state(struct cpufreq_policy *policy, + unsigned int state) +{ + struct cpufreq_freqs freqs; + reg_config_rw_clk_ctrl clk_ctrl; + clk_ctrl = REG_RD(config, regi_config, rw_clk_ctrl); + + freqs.old = cris_freq_get_cpu_frequency(policy->cpu); + freqs.new = cris_freq_table[state].frequency; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + local_irq_disable(); + + /* Even though we may be SMP they will share the same clock + * so all settings are made on CPU0. */ + if (cris_freq_table[state].frequency == 200000) + clk_ctrl.pll = 1; + else + clk_ctrl.pll = 0; + REG_WR(config, regi_config, rw_clk_ctrl, clk_ctrl); + + local_irq_enable(); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); +}; + +static int cris_freq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, &cris_freq_table[0]); +} + +static int cris_freq_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + unsigned int newstate = 0; + + if (cpufreq_frequency_table_target + (policy, cris_freq_table, target_freq, relation, &newstate)) + return -EINVAL; + + cris_freq_set_cpu_state(policy, newstate); + + return 0; +} + +static int cris_freq_cpu_init(struct cpufreq_policy *policy) +{ + int result; + + /* cpuinfo and default policy values */ + policy->cpuinfo.transition_latency = 1000000; /* 1ms */ + policy->cur = cris_freq_get_cpu_frequency(0); + + result = cpufreq_frequency_table_cpuinfo(policy, cris_freq_table); + if (result) + return (result); + + cpufreq_frequency_table_get_attr(cris_freq_table, policy->cpu); + + return 0; +} + +static int cris_freq_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + return 0; +} + +static struct freq_attr *cris_freq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver cris_freq_driver = { + .get = cris_freq_get_cpu_frequency, + .verify = cris_freq_verify, + .target = cris_freq_target, + .init = cris_freq_cpu_init, + .exit = cris_freq_cpu_exit, + .name = "cris_freq", + .owner = THIS_MODULE, + .attr = cris_freq_attr, +}; + +static int __init cris_freq_init(void) +{ + int ret; + ret = cpufreq_register_driver(&cris_freq_driver); + cpufreq_register_notifier(&cris_sdram_freq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + return ret; +} + +static int +cris_sdram_freq_notifier(struct notifier_block *nb, unsigned long val, + void *data) +{ + int i; + struct cpufreq_freqs *freqs = data; + if (val == CPUFREQ_PRECHANGE) { + reg_bif_core_rw_sdram_timing timing = + REG_RD(bif_core, regi_bif_core, rw_sdram_timing); + timing.cpd = (freqs->new == 200000 ? 0 : 1); + + if (freqs->new == 200000) + for (i = 0; i < 50000; i++) ; + REG_WR(bif_core, regi_bif_core, rw_sdram_timing, timing); + } + return 0; +} + +module_init(cris_freq_init); diff --git a/drivers/cpufreq/davinci-cpufreq.c b/drivers/cpufreq/davinci-cpufreq.c new file mode 100644 index 00000000000..c33c76c360f --- /dev/null +++ b/drivers/cpufreq/davinci-cpufreq.c @@ -0,0 +1,231 @@ +/* + * CPU frequency scaling for DaVinci + * + * Copyright (C) 2009 Texas Instruments Incorporated - http://www.ti.com/ + * + * Based on linux/arch/arm/plat-omap/cpu-omap.c. Original Copyright follows: + * + * Copyright (C) 2005 Nokia Corporation + * Written by Tony Lindgren <tony@atomide.com> + * + * Based on cpu-sa1110.c, Copyright (C) 2001 Russell King + * + * Copyright (C) 2007-2008 Texas Instruments, Inc. + * Updated to support OMAP3 + * Rajendra Nayak <rnayak@ti.com> + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ +#include <linux/types.h> +#include <linux/cpufreq.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/platform_device.h> +#include <linux/export.h> + +#include <mach/hardware.h> +#include <mach/cpufreq.h> +#include <mach/common.h> + +struct davinci_cpufreq { + struct device *dev; + struct clk *armclk; + struct clk *asyncclk; + unsigned long asyncrate; +}; +static struct davinci_cpufreq cpufreq; + +static int davinci_verify_speed(struct cpufreq_policy *policy) +{ + struct davinci_cpufreq_config *pdata = cpufreq.dev->platform_data; + struct cpufreq_frequency_table *freq_table = pdata->freq_table; + struct clk *armclk = cpufreq.armclk; + + if (freq_table) + return cpufreq_frequency_table_verify(policy, freq_table); + + if (policy->cpu) + return -EINVAL; + + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + + policy->min = clk_round_rate(armclk, policy->min * 1000) / 1000; + policy->max = clk_round_rate(armclk, policy->max * 1000) / 1000; + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + return 0; +} + +static unsigned int davinci_getspeed(unsigned int cpu) +{ + if (cpu) + return 0; + + return clk_get_rate(cpufreq.armclk) / 1000; +} + +static int davinci_target(struct cpufreq_policy *policy, + unsigned int target_freq, unsigned int relation) +{ + int ret = 0; + unsigned int idx; + struct cpufreq_freqs freqs; + struct davinci_cpufreq_config *pdata = cpufreq.dev->platform_data; + struct clk *armclk = cpufreq.armclk; + + freqs.old = davinci_getspeed(0); + freqs.new = clk_round_rate(armclk, target_freq * 1000) / 1000; + + if (freqs.old == freqs.new) + return ret; + + dev_dbg(cpufreq.dev, "transition: %u --> %u\n", freqs.old, freqs.new); + + ret = cpufreq_frequency_table_target(policy, pdata->freq_table, + freqs.new, relation, &idx); + if (ret) + return -EINVAL; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + /* if moving to higher frequency, up the voltage beforehand */ + if (pdata->set_voltage && freqs.new > freqs.old) { + ret = pdata->set_voltage(idx); + if (ret) + goto out; + } + + ret = clk_set_rate(armclk, idx); + if (ret) + goto out; + + if (cpufreq.asyncclk) { + ret = clk_set_rate(cpufreq.asyncclk, cpufreq.asyncrate); + if (ret) + goto out; + } + + /* if moving to lower freq, lower the voltage after lowering freq */ + if (pdata->set_voltage && freqs.new < freqs.old) + pdata->set_voltage(idx); + +out: + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + return ret; +} + +static int davinci_cpu_init(struct cpufreq_policy *policy) +{ + int result = 0; + struct davinci_cpufreq_config *pdata = cpufreq.dev->platform_data; + struct cpufreq_frequency_table *freq_table = pdata->freq_table; + + if (policy->cpu != 0) + return -EINVAL; + + /* Finish platform specific initialization */ + if (pdata->init) { + result = pdata->init(); + if (result) + return result; + } + + policy->cur = davinci_getspeed(0); + + result = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (result) { + pr_err("%s: cpufreq_frequency_table_cpuinfo() failed", + __func__); + return result; + } + + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + + /* + * Time measurement across the target() function yields ~1500-1800us + * time taken with no drivers on notification list. + * Setting the latency to 2000 us to accommodate addition of drivers + * to pre/post change notification list. + */ + policy->cpuinfo.transition_latency = 2000 * 1000; + return 0; +} + +static int davinci_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + return 0; +} + +static struct freq_attr *davinci_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver davinci_driver = { + .flags = CPUFREQ_STICKY, + .verify = davinci_verify_speed, + .target = davinci_target, + .get = davinci_getspeed, + .init = davinci_cpu_init, + .exit = davinci_cpu_exit, + .name = "davinci", + .attr = davinci_cpufreq_attr, +}; + +static int __init davinci_cpufreq_probe(struct platform_device *pdev) +{ + struct davinci_cpufreq_config *pdata = pdev->dev.platform_data; + struct clk *asyncclk; + + if (!pdata) + return -EINVAL; + if (!pdata->freq_table) + return -EINVAL; + + cpufreq.dev = &pdev->dev; + + cpufreq.armclk = clk_get(NULL, "arm"); + if (IS_ERR(cpufreq.armclk)) { + dev_err(cpufreq.dev, "Unable to get ARM clock\n"); + return PTR_ERR(cpufreq.armclk); + } + + asyncclk = clk_get(cpufreq.dev, "async"); + if (!IS_ERR(asyncclk)) { + cpufreq.asyncclk = asyncclk; + cpufreq.asyncrate = clk_get_rate(asyncclk); + } + + return cpufreq_register_driver(&davinci_driver); +} + +static int __exit davinci_cpufreq_remove(struct platform_device *pdev) +{ + clk_put(cpufreq.armclk); + + if (cpufreq.asyncclk) + clk_put(cpufreq.asyncclk); + + return cpufreq_unregister_driver(&davinci_driver); +} + +static struct platform_driver davinci_cpufreq_driver = { + .driver = { + .name = "cpufreq-davinci", + .owner = THIS_MODULE, + }, + .remove = __exit_p(davinci_cpufreq_remove), +}; + +int __init davinci_cpufreq_init(void) +{ + return platform_driver_probe(&davinci_cpufreq_driver, + davinci_cpufreq_probe); +} + diff --git a/drivers/cpufreq/dbx500-cpufreq.c b/drivers/cpufreq/dbx500-cpufreq.c index 72f0c3efa76..6ec6539ae04 100644 --- a/drivers/cpufreq/dbx500-cpufreq.c +++ b/drivers/cpufreq/dbx500-cpufreq.c @@ -37,12 +37,6 @@ static int dbx500_cpufreq_target(struct cpufreq_policy *policy, unsigned int idx; int ret; - /* scale the target frequency to one of the extremes supported */ - if (target_freq < policy->cpuinfo.min_freq) - target_freq = policy->cpuinfo.min_freq; - if (target_freq > policy->cpuinfo.max_freq) - target_freq = policy->cpuinfo.max_freq; - /* Lookup the next frequency */ if (cpufreq_frequency_table_target(policy, freq_table, target_freq, relation, &idx)) @@ -55,8 +49,7 @@ static int dbx500_cpufreq_target(struct cpufreq_policy *policy, return 0; /* pre-change notification */ - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); /* update armss clk frequency */ ret = clk_set_rate(armss_clk, freqs.new * 1000); @@ -68,8 +61,7 @@ static int dbx500_cpufreq_target(struct cpufreq_policy *policy, } /* post change notification */ - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return 0; } @@ -79,15 +71,15 @@ static unsigned int dbx500_cpufreq_getspeed(unsigned int cpu) int i = 0; unsigned long freq = clk_get_rate(armss_clk) / 1000; - while (freq_table[i].frequency != CPUFREQ_TABLE_END) { - if (freq <= freq_table[i].frequency) + /* The value is rounded to closest frequency in the defined table. */ + while (freq_table[i + 1].frequency != CPUFREQ_TABLE_END) { + if (freq < freq_table[i].frequency + + (freq_table[i + 1].frequency - freq_table[i].frequency) / 2) return freq_table[i].frequency; i++; } - /* We could not find a corresponding frequency. */ - pr_err("dbx500-cpufreq: Failed to find cpufreq speed\n"); - return 0; + return freq_table[i].frequency; } static int __cpuinit dbx500_cpufreq_init(struct cpufreq_policy *policy) diff --git a/drivers/cpufreq/e_powersaver.c b/drivers/cpufreq/e_powersaver.c index 3fffbe6025c..37380fb9262 100644 --- a/drivers/cpufreq/e_powersaver.c +++ b/drivers/cpufreq/e_powersaver.c @@ -104,7 +104,7 @@ static unsigned int eps_get(unsigned int cpu) } static int eps_set_state(struct eps_cpu_data *centaur, - unsigned int cpu, + struct cpufreq_policy *policy, u32 dest_state) { struct cpufreq_freqs freqs; @@ -112,10 +112,9 @@ static int eps_set_state(struct eps_cpu_data *centaur, int err = 0; int i; - freqs.old = eps_get(cpu); + freqs.old = eps_get(policy->cpu); freqs.new = centaur->fsb * ((dest_state >> 8) & 0xff); - freqs.cpu = cpu; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); /* Wait while CPU is busy */ rdmsr(MSR_IA32_PERF_STATUS, lo, hi); @@ -162,7 +161,7 @@ postchange: current_multiplier); } #endif - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return err; } @@ -190,7 +189,7 @@ static int eps_target(struct cpufreq_policy *policy, /* Make frequency transition */ dest_state = centaur->freq_table[newstate].index & 0xffff; - ret = eps_set_state(centaur, cpu, dest_state); + ret = eps_set_state(centaur, policy, dest_state); if (ret) printk(KERN_ERR "eps: Timeout!\n"); return ret; diff --git a/drivers/cpufreq/elanfreq.c b/drivers/cpufreq/elanfreq.c index 960671fd3d7..658d860344b 100644 --- a/drivers/cpufreq/elanfreq.c +++ b/drivers/cpufreq/elanfreq.c @@ -117,15 +117,15 @@ static unsigned int elanfreq_get_cpu_frequency(unsigned int cpu) * There is no return value. */ -static void elanfreq_set_cpu_state(unsigned int state) +static void elanfreq_set_cpu_state(struct cpufreq_policy *policy, + unsigned int state) { struct cpufreq_freqs freqs; freqs.old = elanfreq_get_cpu_frequency(0); freqs.new = elan_multiplier[state].clock; - freqs.cpu = 0; /* elanfreq.c is UP only driver */ - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); printk(KERN_INFO "elanfreq: attempting to set frequency to %i kHz\n", elan_multiplier[state].clock); @@ -161,7 +161,7 @@ static void elanfreq_set_cpu_state(unsigned int state) udelay(10000); local_irq_enable(); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); }; @@ -188,7 +188,7 @@ static int elanfreq_target(struct cpufreq_policy *policy, target_freq, relation, &newstate)) return -EINVAL; - elanfreq_set_cpu_state(newstate); + elanfreq_set_cpu_state(policy, newstate); return 0; } diff --git a/drivers/cpufreq/exynos-cpufreq.c b/drivers/cpufreq/exynos-cpufreq.c index 78057a357dd..475b4f607f0 100644 --- a/drivers/cpufreq/exynos-cpufreq.c +++ b/drivers/cpufreq/exynos-cpufreq.c @@ -70,7 +70,6 @@ static int exynos_cpufreq_scale(unsigned int target_freq) freqs.old = policy->cur; freqs.new = target_freq; - freqs.cpu = policy->cpu; if (freqs.new == freqs.old) goto out; @@ -105,8 +104,7 @@ static int exynos_cpufreq_scale(unsigned int target_freq) } arm_volt = volt_table[index]; - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); /* When the new frequency is higher than current frequency */ if ((freqs.new > freqs.old) && !safe_arm_volt) { @@ -131,8 +129,7 @@ static int exynos_cpufreq_scale(unsigned int target_freq) exynos_info->set_freq(old_index, index); - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); /* When the new frequency is lower than current frequency */ if ((freqs.new < freqs.old) || @@ -297,7 +294,7 @@ static int __init exynos_cpufreq_init(void) else if (soc_is_exynos5250()) ret = exynos5250_cpufreq_init(exynos_info); else - pr_err("%s: CPU type not found\n", __func__); + return 0; if (ret) goto err_vdd_arm; diff --git a/drivers/cpufreq/exynos5440-cpufreq.c b/drivers/cpufreq/exynos5440-cpufreq.c new file mode 100644 index 00000000000..0c74018eda4 --- /dev/null +++ b/drivers/cpufreq/exynos5440-cpufreq.c @@ -0,0 +1,481 @@ +/* + * Copyright (c) 2013 Samsung Electronics Co., Ltd. + * http://www.samsung.com + * + * Amit Daniel Kachhap <amit.daniel@samsung.com> + * + * EXYNOS5440 - CPU frequency scaling support + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. +*/ + +#include <linux/clk.h> +#include <linux/cpu.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/interrupt.h> +#include <linux/io.h> +#include <linux/module.h> +#include <linux/of_address.h> +#include <linux/of_irq.h> +#include <linux/opp.h> +#include <linux/platform_device.h> +#include <linux/slab.h> + +/* Register definitions */ +#define XMU_DVFS_CTRL 0x0060 +#define XMU_PMU_P0_7 0x0064 +#define XMU_C0_3_PSTATE 0x0090 +#define XMU_P_LIMIT 0x00a0 +#define XMU_P_STATUS 0x00a4 +#define XMU_PMUEVTEN 0x00d0 +#define XMU_PMUIRQEN 0x00d4 +#define XMU_PMUIRQ 0x00d8 + +/* PMU mask and shift definations */ +#define P_VALUE_MASK 0x7 + +#define XMU_DVFS_CTRL_EN_SHIFT 0 + +#define P0_7_CPUCLKDEV_SHIFT 21 +#define P0_7_CPUCLKDEV_MASK 0x7 +#define P0_7_ATBCLKDEV_SHIFT 18 +#define P0_7_ATBCLKDEV_MASK 0x7 +#define P0_7_CSCLKDEV_SHIFT 15 +#define P0_7_CSCLKDEV_MASK 0x7 +#define P0_7_CPUEMA_SHIFT 28 +#define P0_7_CPUEMA_MASK 0xf +#define P0_7_L2EMA_SHIFT 24 +#define P0_7_L2EMA_MASK 0xf +#define P0_7_VDD_SHIFT 8 +#define P0_7_VDD_MASK 0x7f +#define P0_7_FREQ_SHIFT 0 +#define P0_7_FREQ_MASK 0xff + +#define C0_3_PSTATE_VALID_SHIFT 8 +#define C0_3_PSTATE_CURR_SHIFT 4 +#define C0_3_PSTATE_NEW_SHIFT 0 + +#define PSTATE_CHANGED_EVTEN_SHIFT 0 + +#define PSTATE_CHANGED_IRQEN_SHIFT 0 + +#define PSTATE_CHANGED_SHIFT 0 + +/* some constant values for clock divider calculation */ +#define CPU_DIV_FREQ_MAX 500 +#define CPU_DBG_FREQ_MAX 375 +#define CPU_ATB_FREQ_MAX 500 + +#define PMIC_LOW_VOLT 0x30 +#define PMIC_HIGH_VOLT 0x28 + +#define CPUEMA_HIGH 0x2 +#define CPUEMA_MID 0x4 +#define CPUEMA_LOW 0x7 + +#define L2EMA_HIGH 0x1 +#define L2EMA_MID 0x3 +#define L2EMA_LOW 0x4 + +#define DIV_TAB_MAX 2 +/* frequency unit is 20MHZ */ +#define FREQ_UNIT 20 +#define MAX_VOLTAGE 1550000 /* In microvolt */ +#define VOLTAGE_STEP 12500 /* In microvolt */ + +#define CPUFREQ_NAME "exynos5440_dvfs" +#define DEF_TRANS_LATENCY 100000 + +enum cpufreq_level_index { + L0, L1, L2, L3, L4, + L5, L6, L7, L8, L9, +}; +#define CPUFREQ_LEVEL_END (L7 + 1) + +struct exynos_dvfs_data { + void __iomem *base; + struct resource *mem; + int irq; + struct clk *cpu_clk; + unsigned int cur_frequency; + unsigned int latency; + struct cpufreq_frequency_table *freq_table; + unsigned int freq_count; + struct device *dev; + bool dvfs_enabled; + struct work_struct irq_work; +}; + +static struct exynos_dvfs_data *dvfs_info; +static DEFINE_MUTEX(cpufreq_lock); +static struct cpufreq_freqs freqs; + +static int init_div_table(void) +{ + struct cpufreq_frequency_table *freq_tbl = dvfs_info->freq_table; + unsigned int tmp, clk_div, ema_div, freq, volt_id; + int i = 0; + struct opp *opp; + + rcu_read_lock(); + for (i = 0; freq_tbl[i].frequency != CPUFREQ_TABLE_END; i++) { + + opp = opp_find_freq_exact(dvfs_info->dev, + freq_tbl[i].frequency * 1000, true); + if (IS_ERR(opp)) { + rcu_read_unlock(); + dev_err(dvfs_info->dev, + "failed to find valid OPP for %u KHZ\n", + freq_tbl[i].frequency); + return PTR_ERR(opp); + } + + freq = freq_tbl[i].frequency / 1000; /* In MHZ */ + clk_div = ((freq / CPU_DIV_FREQ_MAX) & P0_7_CPUCLKDEV_MASK) + << P0_7_CPUCLKDEV_SHIFT; + clk_div |= ((freq / CPU_ATB_FREQ_MAX) & P0_7_ATBCLKDEV_MASK) + << P0_7_ATBCLKDEV_SHIFT; + clk_div |= ((freq / CPU_DBG_FREQ_MAX) & P0_7_CSCLKDEV_MASK) + << P0_7_CSCLKDEV_SHIFT; + + /* Calculate EMA */ + volt_id = opp_get_voltage(opp); + volt_id = (MAX_VOLTAGE - volt_id) / VOLTAGE_STEP; + if (volt_id < PMIC_HIGH_VOLT) { + ema_div = (CPUEMA_HIGH << P0_7_CPUEMA_SHIFT) | + (L2EMA_HIGH << P0_7_L2EMA_SHIFT); + } else if (volt_id > PMIC_LOW_VOLT) { + ema_div = (CPUEMA_LOW << P0_7_CPUEMA_SHIFT) | + (L2EMA_LOW << P0_7_L2EMA_SHIFT); + } else { + ema_div = (CPUEMA_MID << P0_7_CPUEMA_SHIFT) | + (L2EMA_MID << P0_7_L2EMA_SHIFT); + } + + tmp = (clk_div | ema_div | (volt_id << P0_7_VDD_SHIFT) + | ((freq / FREQ_UNIT) << P0_7_FREQ_SHIFT)); + + __raw_writel(tmp, dvfs_info->base + XMU_PMU_P0_7 + 4 * i); + } + + rcu_read_unlock(); + return 0; +} + +static void exynos_enable_dvfs(void) +{ + unsigned int tmp, i, cpu; + struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table; + /* Disable DVFS */ + __raw_writel(0, dvfs_info->base + XMU_DVFS_CTRL); + + /* Enable PSTATE Change Event */ + tmp = __raw_readl(dvfs_info->base + XMU_PMUEVTEN); + tmp |= (1 << PSTATE_CHANGED_EVTEN_SHIFT); + __raw_writel(tmp, dvfs_info->base + XMU_PMUEVTEN); + + /* Enable PSTATE Change IRQ */ + tmp = __raw_readl(dvfs_info->base + XMU_PMUIRQEN); + tmp |= (1 << PSTATE_CHANGED_IRQEN_SHIFT); + __raw_writel(tmp, dvfs_info->base + XMU_PMUIRQEN); + + /* Set initial performance index */ + for (i = 0; freq_table[i].frequency != CPUFREQ_TABLE_END; i++) + if (freq_table[i].frequency == dvfs_info->cur_frequency) + break; + + if (freq_table[i].frequency == CPUFREQ_TABLE_END) { + dev_crit(dvfs_info->dev, "Boot up frequency not supported\n"); + /* Assign the highest frequency */ + i = 0; + dvfs_info->cur_frequency = freq_table[i].frequency; + } + + dev_info(dvfs_info->dev, "Setting dvfs initial frequency = %uKHZ", + dvfs_info->cur_frequency); + + for (cpu = 0; cpu < CONFIG_NR_CPUS; cpu++) { + tmp = __raw_readl(dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4); + tmp &= ~(P_VALUE_MASK << C0_3_PSTATE_NEW_SHIFT); + tmp |= (i << C0_3_PSTATE_NEW_SHIFT); + __raw_writel(tmp, dvfs_info->base + XMU_C0_3_PSTATE + cpu * 4); + } + + /* Enable DVFS */ + __raw_writel(1 << XMU_DVFS_CTRL_EN_SHIFT, + dvfs_info->base + XMU_DVFS_CTRL); +} + +static int exynos_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, + dvfs_info->freq_table); +} + +static unsigned int exynos_getspeed(unsigned int cpu) +{ + return dvfs_info->cur_frequency; +} + +static int exynos_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int index, tmp; + int ret = 0, i; + struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table; + + mutex_lock(&cpufreq_lock); + + ret = cpufreq_frequency_table_target(policy, freq_table, + target_freq, relation, &index); + if (ret) + goto out; + + freqs.old = dvfs_info->cur_frequency; + freqs.new = freq_table[index].frequency; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + /* Set the target frequency in all C0_3_PSTATE register */ + for_each_cpu(i, policy->cpus) { + tmp = __raw_readl(dvfs_info->base + XMU_C0_3_PSTATE + i * 4); + tmp &= ~(P_VALUE_MASK << C0_3_PSTATE_NEW_SHIFT); + tmp |= (index << C0_3_PSTATE_NEW_SHIFT); + + __raw_writel(tmp, dvfs_info->base + XMU_C0_3_PSTATE + i * 4); + } +out: + mutex_unlock(&cpufreq_lock); + return ret; +} + +static void exynos_cpufreq_work(struct work_struct *work) +{ + unsigned int cur_pstate, index; + struct cpufreq_policy *policy = cpufreq_cpu_get(0); /* boot CPU */ + struct cpufreq_frequency_table *freq_table = dvfs_info->freq_table; + + /* Ensure we can access cpufreq structures */ + if (unlikely(dvfs_info->dvfs_enabled == false)) + goto skip_work; + + mutex_lock(&cpufreq_lock); + freqs.old = dvfs_info->cur_frequency; + + cur_pstate = __raw_readl(dvfs_info->base + XMU_P_STATUS); + if (cur_pstate >> C0_3_PSTATE_VALID_SHIFT & 0x1) + index = (cur_pstate >> C0_3_PSTATE_CURR_SHIFT) & P_VALUE_MASK; + else + index = (cur_pstate >> C0_3_PSTATE_NEW_SHIFT) & P_VALUE_MASK; + + if (likely(index < dvfs_info->freq_count)) { + freqs.new = freq_table[index].frequency; + dvfs_info->cur_frequency = freqs.new; + } else { + dev_crit(dvfs_info->dev, "New frequency out of range\n"); + freqs.new = dvfs_info->cur_frequency; + } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + cpufreq_cpu_put(policy); + mutex_unlock(&cpufreq_lock); +skip_work: + enable_irq(dvfs_info->irq); +} + +static irqreturn_t exynos_cpufreq_irq(int irq, void *id) +{ + unsigned int tmp; + + tmp = __raw_readl(dvfs_info->base + XMU_PMUIRQ); + if (tmp >> PSTATE_CHANGED_SHIFT & 0x1) { + __raw_writel(tmp, dvfs_info->base + XMU_PMUIRQ); + disable_irq_nosync(irq); + schedule_work(&dvfs_info->irq_work); + } + return IRQ_HANDLED; +} + +static void exynos_sort_descend_freq_table(void) +{ + struct cpufreq_frequency_table *freq_tbl = dvfs_info->freq_table; + int i = 0, index; + unsigned int tmp_freq; + /* + * Exynos5440 clock controller state logic expects the cpufreq table to + * be in descending order. But the OPP library constructs the table in + * ascending order. So to make the table descending we just need to + * swap the i element with the N - i element. + */ + for (i = 0; i < dvfs_info->freq_count / 2; i++) { + index = dvfs_info->freq_count - i - 1; + tmp_freq = freq_tbl[i].frequency; + freq_tbl[i].frequency = freq_tbl[index].frequency; + freq_tbl[index].frequency = tmp_freq; + } +} + +static int exynos_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + int ret; + + ret = cpufreq_frequency_table_cpuinfo(policy, dvfs_info->freq_table); + if (ret) { + dev_err(dvfs_info->dev, "Invalid frequency table: %d\n", ret); + return ret; + } + + policy->cur = dvfs_info->cur_frequency; + policy->cpuinfo.transition_latency = dvfs_info->latency; + cpumask_setall(policy->cpus); + + cpufreq_frequency_table_get_attr(dvfs_info->freq_table, policy->cpu); + + return 0; +} + +static struct cpufreq_driver exynos_driver = { + .flags = CPUFREQ_STICKY, + .verify = exynos_verify_speed, + .target = exynos_target, + .get = exynos_getspeed, + .init = exynos_cpufreq_cpu_init, + .name = CPUFREQ_NAME, +}; + +static const struct of_device_id exynos_cpufreq_match[] = { + { + .compatible = "samsung,exynos5440-cpufreq", + }, + {}, +}; +MODULE_DEVICE_TABLE(of, exynos_cpufreq_match); + +static int exynos_cpufreq_probe(struct platform_device *pdev) +{ + int ret = -EINVAL; + struct device_node *np; + struct resource res; + + np = pdev->dev.of_node; + if (!np) + return -ENODEV; + + dvfs_info = devm_kzalloc(&pdev->dev, sizeof(*dvfs_info), GFP_KERNEL); + if (!dvfs_info) { + ret = -ENOMEM; + goto err_put_node; + } + + dvfs_info->dev = &pdev->dev; + + ret = of_address_to_resource(np, 0, &res); + if (ret) + goto err_put_node; + + dvfs_info->base = devm_ioremap_resource(dvfs_info->dev, &res); + if (IS_ERR(dvfs_info->base)) { + ret = PTR_ERR(dvfs_info->base); + goto err_put_node; + } + + dvfs_info->irq = irq_of_parse_and_map(np, 0); + if (!dvfs_info->irq) { + dev_err(dvfs_info->dev, "No cpufreq irq found\n"); + ret = -ENODEV; + goto err_put_node; + } + + ret = of_init_opp_table(dvfs_info->dev); + if (ret) { + dev_err(dvfs_info->dev, "failed to init OPP table: %d\n", ret); + goto err_put_node; + } + + ret = opp_init_cpufreq_table(dvfs_info->dev, &dvfs_info->freq_table); + if (ret) { + dev_err(dvfs_info->dev, + "failed to init cpufreq table: %d\n", ret); + goto err_put_node; + } + dvfs_info->freq_count = opp_get_opp_count(dvfs_info->dev); + exynos_sort_descend_freq_table(); + + if (of_property_read_u32(np, "clock-latency", &dvfs_info->latency)) + dvfs_info->latency = DEF_TRANS_LATENCY; + + dvfs_info->cpu_clk = devm_clk_get(dvfs_info->dev, "armclk"); + if (IS_ERR(dvfs_info->cpu_clk)) { + dev_err(dvfs_info->dev, "Failed to get cpu clock\n"); + ret = PTR_ERR(dvfs_info->cpu_clk); + goto err_free_table; + } + + dvfs_info->cur_frequency = clk_get_rate(dvfs_info->cpu_clk); + if (!dvfs_info->cur_frequency) { + dev_err(dvfs_info->dev, "Failed to get clock rate\n"); + ret = -EINVAL; + goto err_free_table; + } + dvfs_info->cur_frequency /= 1000; + + INIT_WORK(&dvfs_info->irq_work, exynos_cpufreq_work); + ret = devm_request_irq(dvfs_info->dev, dvfs_info->irq, + exynos_cpufreq_irq, IRQF_TRIGGER_NONE, + CPUFREQ_NAME, dvfs_info); + if (ret) { + dev_err(dvfs_info->dev, "Failed to register IRQ\n"); + goto err_free_table; + } + + ret = init_div_table(); + if (ret) { + dev_err(dvfs_info->dev, "Failed to initialise div table\n"); + goto err_free_table; + } + + exynos_enable_dvfs(); + ret = cpufreq_register_driver(&exynos_driver); + if (ret) { + dev_err(dvfs_info->dev, + "%s: failed to register cpufreq driver\n", __func__); + goto err_free_table; + } + + of_node_put(np); + dvfs_info->dvfs_enabled = true; + return 0; + +err_free_table: + opp_free_cpufreq_table(dvfs_info->dev, &dvfs_info->freq_table); +err_put_node: + of_node_put(np); + dev_err(dvfs_info->dev, "%s: failed initialization\n", __func__); + return ret; +} + +static int exynos_cpufreq_remove(struct platform_device *pdev) +{ + cpufreq_unregister_driver(&exynos_driver); + opp_free_cpufreq_table(dvfs_info->dev, &dvfs_info->freq_table); + return 0; +} + +static struct platform_driver exynos_cpufreq_platdrv = { + .driver = { + .name = "exynos5440-cpufreq", + .owner = THIS_MODULE, + .of_match_table = exynos_cpufreq_match, + }, + .probe = exynos_cpufreq_probe, + .remove = exynos_cpufreq_remove, +}; +module_platform_driver(exynos_cpufreq_platdrv); + +MODULE_AUTHOR("Amit Daniel Kachhap <amit.daniel@samsung.com>"); +MODULE_DESCRIPTION("Exynos5440 cpufreq driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/gx-suspmod.c b/drivers/cpufreq/gx-suspmod.c index 456bee058fe..3dfc99b9ca8 100644 --- a/drivers/cpufreq/gx-suspmod.c +++ b/drivers/cpufreq/gx-suspmod.c @@ -251,14 +251,13 @@ static unsigned int gx_validate_speed(unsigned int khz, u8 *on_duration, * set cpu speed in khz. **/ -static void gx_set_cpuspeed(unsigned int khz) +static void gx_set_cpuspeed(struct cpufreq_policy *policy, unsigned int khz) { u8 suscfg, pmer1; unsigned int new_khz; unsigned long flags; struct cpufreq_freqs freqs; - freqs.cpu = 0; freqs.old = gx_get_cpuspeed(0); new_khz = gx_validate_speed(khz, &gx_params->on_duration, @@ -266,11 +265,9 @@ static void gx_set_cpuspeed(unsigned int khz) freqs.new = new_khz; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); local_irq_save(flags); - - if (new_khz != stock_freq) { /* if new khz == 100% of CPU speed, it is special case */ switch (gx_params->cs55x0->device) { @@ -317,7 +314,7 @@ static void gx_set_cpuspeed(unsigned int khz) gx_params->pci_suscfg = suscfg; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); pr_debug("suspend modulation w/ duration of ON:%d us, OFF:%d us\n", gx_params->on_duration * 32, gx_params->off_duration * 32); @@ -397,7 +394,7 @@ static int cpufreq_gx_target(struct cpufreq_policy *policy, tmp_freq = gx_validate_speed(tmp_freq, &tmp1, &tmp2); } - gx_set_cpuspeed(tmp_freq); + gx_set_cpuspeed(policy, tmp_freq); return 0; } diff --git a/drivers/cpufreq/ia64-acpi-cpufreq.c b/drivers/cpufreq/ia64-acpi-cpufreq.c new file mode 100644 index 00000000000..c0075dbaa63 --- /dev/null +++ b/drivers/cpufreq/ia64-acpi-cpufreq.c @@ -0,0 +1,438 @@ +/* + * This file provides the ACPI based P-state support. This + * module works with generic cpufreq infrastructure. Most of + * the code is based on i386 version + * (arch/i386/kernel/cpu/cpufreq/acpi-cpufreq.c) + * + * Copyright (C) 2005 Intel Corp + * Venkatesh Pallipadi <venkatesh.pallipadi@intel.com> + */ + +#include <linux/kernel.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> +#include <asm/io.h> +#include <asm/uaccess.h> +#include <asm/pal.h> + +#include <linux/acpi.h> +#include <acpi/processor.h> + +MODULE_AUTHOR("Venkatesh Pallipadi"); +MODULE_DESCRIPTION("ACPI Processor P-States Driver"); +MODULE_LICENSE("GPL"); + + +struct cpufreq_acpi_io { + struct acpi_processor_performance acpi_data; + struct cpufreq_frequency_table *freq_table; + unsigned int resume; +}; + +static struct cpufreq_acpi_io *acpi_io_data[NR_CPUS]; + +static struct cpufreq_driver acpi_cpufreq_driver; + + +static int +processor_set_pstate ( + u32 value) +{ + s64 retval; + + pr_debug("processor_set_pstate\n"); + + retval = ia64_pal_set_pstate((u64)value); + + if (retval) { + pr_debug("Failed to set freq to 0x%x, with error 0x%lx\n", + value, retval); + return -ENODEV; + } + return (int)retval; +} + + +static int +processor_get_pstate ( + u32 *value) +{ + u64 pstate_index = 0; + s64 retval; + + pr_debug("processor_get_pstate\n"); + + retval = ia64_pal_get_pstate(&pstate_index, + PAL_GET_PSTATE_TYPE_INSTANT); + *value = (u32) pstate_index; + + if (retval) + pr_debug("Failed to get current freq with " + "error 0x%lx, idx 0x%x\n", retval, *value); + + return (int)retval; +} + + +/* To be used only after data->acpi_data is initialized */ +static unsigned +extract_clock ( + struct cpufreq_acpi_io *data, + unsigned value, + unsigned int cpu) +{ + unsigned long i; + + pr_debug("extract_clock\n"); + + for (i = 0; i < data->acpi_data.state_count; i++) { + if (value == data->acpi_data.states[i].status) + return data->acpi_data.states[i].core_frequency; + } + return data->acpi_data.states[i-1].core_frequency; +} + + +static unsigned int +processor_get_freq ( + struct cpufreq_acpi_io *data, + unsigned int cpu) +{ + int ret = 0; + u32 value = 0; + cpumask_t saved_mask; + unsigned long clock_freq; + + pr_debug("processor_get_freq\n"); + + saved_mask = current->cpus_allowed; + set_cpus_allowed_ptr(current, cpumask_of(cpu)); + if (smp_processor_id() != cpu) + goto migrate_end; + + /* processor_get_pstate gets the instantaneous frequency */ + ret = processor_get_pstate(&value); + + if (ret) { + set_cpus_allowed_ptr(current, &saved_mask); + printk(KERN_WARNING "get performance failed with error %d\n", + ret); + ret = 0; + goto migrate_end; + } + clock_freq = extract_clock(data, value, cpu); + ret = (clock_freq*1000); + +migrate_end: + set_cpus_allowed_ptr(current, &saved_mask); + return ret; +} + + +static int +processor_set_freq ( + struct cpufreq_acpi_io *data, + struct cpufreq_policy *policy, + int state) +{ + int ret = 0; + u32 value = 0; + struct cpufreq_freqs cpufreq_freqs; + cpumask_t saved_mask; + int retval; + + pr_debug("processor_set_freq\n"); + + saved_mask = current->cpus_allowed; + set_cpus_allowed_ptr(current, cpumask_of(policy->cpu)); + if (smp_processor_id() != policy->cpu) { + retval = -EAGAIN; + goto migrate_end; + } + + if (state == data->acpi_data.state) { + if (unlikely(data->resume)) { + pr_debug("Called after resume, resetting to P%d\n", state); + data->resume = 0; + } else { + pr_debug("Already at target state (P%d)\n", state); + retval = 0; + goto migrate_end; + } + } + + pr_debug("Transitioning from P%d to P%d\n", + data->acpi_data.state, state); + + /* cpufreq frequency struct */ + cpufreq_freqs.old = data->freq_table[data->acpi_data.state].frequency; + cpufreq_freqs.new = data->freq_table[state].frequency; + + /* notify cpufreq */ + cpufreq_notify_transition(policy, &cpufreq_freqs, CPUFREQ_PRECHANGE); + + /* + * First we write the target state's 'control' value to the + * control_register. + */ + + value = (u32) data->acpi_data.states[state].control; + + pr_debug("Transitioning to state: 0x%08x\n", value); + + ret = processor_set_pstate(value); + if (ret) { + unsigned int tmp = cpufreq_freqs.new; + cpufreq_notify_transition(policy, &cpufreq_freqs, + CPUFREQ_POSTCHANGE); + cpufreq_freqs.new = cpufreq_freqs.old; + cpufreq_freqs.old = tmp; + cpufreq_notify_transition(policy, &cpufreq_freqs, + CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &cpufreq_freqs, + CPUFREQ_POSTCHANGE); + printk(KERN_WARNING "Transition failed with error %d\n", ret); + retval = -ENODEV; + goto migrate_end; + } + + cpufreq_notify_transition(policy, &cpufreq_freqs, CPUFREQ_POSTCHANGE); + + data->acpi_data.state = state; + + retval = 0; + +migrate_end: + set_cpus_allowed_ptr(current, &saved_mask); + return (retval); +} + + +static unsigned int +acpi_cpufreq_get ( + unsigned int cpu) +{ + struct cpufreq_acpi_io *data = acpi_io_data[cpu]; + + pr_debug("acpi_cpufreq_get\n"); + + return processor_get_freq(data, cpu); +} + + +static int +acpi_cpufreq_target ( + struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct cpufreq_acpi_io *data = acpi_io_data[policy->cpu]; + unsigned int next_state = 0; + unsigned int result = 0; + + pr_debug("acpi_cpufreq_setpolicy\n"); + + result = cpufreq_frequency_table_target(policy, + data->freq_table, target_freq, relation, &next_state); + if (result) + return (result); + + result = processor_set_freq(data, policy, next_state); + + return (result); +} + + +static int +acpi_cpufreq_verify ( + struct cpufreq_policy *policy) +{ + unsigned int result = 0; + struct cpufreq_acpi_io *data = acpi_io_data[policy->cpu]; + + pr_debug("acpi_cpufreq_verify\n"); + + result = cpufreq_frequency_table_verify(policy, + data->freq_table); + + return (result); +} + + +static int +acpi_cpufreq_cpu_init ( + struct cpufreq_policy *policy) +{ + unsigned int i; + unsigned int cpu = policy->cpu; + struct cpufreq_acpi_io *data; + unsigned int result = 0; + + pr_debug("acpi_cpufreq_cpu_init\n"); + + data = kzalloc(sizeof(struct cpufreq_acpi_io), GFP_KERNEL); + if (!data) + return (-ENOMEM); + + acpi_io_data[cpu] = data; + + result = acpi_processor_register_performance(&data->acpi_data, cpu); + + if (result) + goto err_free; + + /* capability check */ + if (data->acpi_data.state_count <= 1) { + pr_debug("No P-States\n"); + result = -ENODEV; + goto err_unreg; + } + + if ((data->acpi_data.control_register.space_id != + ACPI_ADR_SPACE_FIXED_HARDWARE) || + (data->acpi_data.status_register.space_id != + ACPI_ADR_SPACE_FIXED_HARDWARE)) { + pr_debug("Unsupported address space [%d, %d]\n", + (u32) (data->acpi_data.control_register.space_id), + (u32) (data->acpi_data.status_register.space_id)); + result = -ENODEV; + goto err_unreg; + } + + /* alloc freq_table */ + data->freq_table = kmalloc(sizeof(struct cpufreq_frequency_table) * + (data->acpi_data.state_count + 1), + GFP_KERNEL); + if (!data->freq_table) { + result = -ENOMEM; + goto err_unreg; + } + + /* detect transition latency */ + policy->cpuinfo.transition_latency = 0; + for (i=0; i<data->acpi_data.state_count; i++) { + if ((data->acpi_data.states[i].transition_latency * 1000) > + policy->cpuinfo.transition_latency) { + policy->cpuinfo.transition_latency = + data->acpi_data.states[i].transition_latency * 1000; + } + } + policy->cur = processor_get_freq(data, policy->cpu); + + /* table init */ + for (i = 0; i <= data->acpi_data.state_count; i++) + { + data->freq_table[i].index = i; + if (i < data->acpi_data.state_count) { + data->freq_table[i].frequency = + data->acpi_data.states[i].core_frequency * 1000; + } else { + data->freq_table[i].frequency = CPUFREQ_TABLE_END; + } + } + + result = cpufreq_frequency_table_cpuinfo(policy, data->freq_table); + if (result) { + goto err_freqfree; + } + + /* notify BIOS that we exist */ + acpi_processor_notify_smm(THIS_MODULE); + + printk(KERN_INFO "acpi-cpufreq: CPU%u - ACPI performance management " + "activated.\n", cpu); + + for (i = 0; i < data->acpi_data.state_count; i++) + pr_debug(" %cP%d: %d MHz, %d mW, %d uS, %d uS, 0x%x 0x%x\n", + (i == data->acpi_data.state?'*':' '), i, + (u32) data->acpi_data.states[i].core_frequency, + (u32) data->acpi_data.states[i].power, + (u32) data->acpi_data.states[i].transition_latency, + (u32) data->acpi_data.states[i].bus_master_latency, + (u32) data->acpi_data.states[i].status, + (u32) data->acpi_data.states[i].control); + + cpufreq_frequency_table_get_attr(data->freq_table, policy->cpu); + + /* the first call to ->target() should result in us actually + * writing something to the appropriate registers. */ + data->resume = 1; + + return (result); + + err_freqfree: + kfree(data->freq_table); + err_unreg: + acpi_processor_unregister_performance(&data->acpi_data, cpu); + err_free: + kfree(data); + acpi_io_data[cpu] = NULL; + + return (result); +} + + +static int +acpi_cpufreq_cpu_exit ( + struct cpufreq_policy *policy) +{ + struct cpufreq_acpi_io *data = acpi_io_data[policy->cpu]; + + pr_debug("acpi_cpufreq_cpu_exit\n"); + + if (data) { + cpufreq_frequency_table_put_attr(policy->cpu); + acpi_io_data[policy->cpu] = NULL; + acpi_processor_unregister_performance(&data->acpi_data, + policy->cpu); + kfree(data); + } + + return (0); +} + + +static struct freq_attr* acpi_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + + +static struct cpufreq_driver acpi_cpufreq_driver = { + .verify = acpi_cpufreq_verify, + .target = acpi_cpufreq_target, + .get = acpi_cpufreq_get, + .init = acpi_cpufreq_cpu_init, + .exit = acpi_cpufreq_cpu_exit, + .name = "acpi-cpufreq", + .owner = THIS_MODULE, + .attr = acpi_cpufreq_attr, +}; + + +static int __init +acpi_cpufreq_init (void) +{ + pr_debug("acpi_cpufreq_init\n"); + + return cpufreq_register_driver(&acpi_cpufreq_driver); +} + + +static void __exit +acpi_cpufreq_exit (void) +{ + pr_debug("acpi_cpufreq_exit\n"); + + cpufreq_unregister_driver(&acpi_cpufreq_driver); + return; +} + + +late_initcall(acpi_cpufreq_init); +module_exit(acpi_cpufreq_exit); + diff --git a/drivers/cpufreq/imx6q-cpufreq.c b/drivers/cpufreq/imx6q-cpufreq.c index 54e336de373..b78bc35973b 100644 --- a/drivers/cpufreq/imx6q-cpufreq.c +++ b/drivers/cpufreq/imx6q-cpufreq.c @@ -50,7 +50,7 @@ static int imx6q_set_target(struct cpufreq_policy *policy, struct cpufreq_freqs freqs; struct opp *opp; unsigned long freq_hz, volt, volt_old; - unsigned int index, cpu; + unsigned int index; int ret; ret = cpufreq_frequency_table_target(policy, freq_table, target_freq, @@ -68,10 +68,7 @@ static int imx6q_set_target(struct cpufreq_policy *policy, if (freqs.old == freqs.new) return 0; - for_each_online_cpu(cpu) { - freqs.cpu = cpu; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); rcu_read_lock(); opp = opp_find_freq_ceil(cpu_dev, &freq_hz); @@ -166,10 +163,7 @@ static int imx6q_set_target(struct cpufreq_policy *policy, } } - for_each_online_cpu(cpu) { - freqs.cpu = cpu; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return 0; } diff --git a/drivers/cpufreq/integrator-cpufreq.c b/drivers/cpufreq/integrator-cpufreq.c new file mode 100644 index 00000000000..f7c99df0880 --- /dev/null +++ b/drivers/cpufreq/integrator-cpufreq.c @@ -0,0 +1,220 @@ +/* + * Copyright (C) 2001-2002 Deep Blue Solutions Ltd. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * CPU support functions + */ +#include <linux/module.h> +#include <linux/types.h> +#include <linux/kernel.h> +#include <linux/cpufreq.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/init.h> +#include <linux/io.h> + +#include <mach/hardware.h> +#include <mach/platform.h> +#include <asm/mach-types.h> +#include <asm/hardware/icst.h> + +static struct cpufreq_driver integrator_driver; + +#define CM_ID __io_address(INTEGRATOR_HDR_ID) +#define CM_OSC __io_address(INTEGRATOR_HDR_OSC) +#define CM_STAT __io_address(INTEGRATOR_HDR_STAT) +#define CM_LOCK __io_address(INTEGRATOR_HDR_LOCK) + +static const struct icst_params lclk_params = { + .ref = 24000000, + .vco_max = ICST525_VCO_MAX_5V, + .vco_min = ICST525_VCO_MIN, + .vd_min = 8, + .vd_max = 132, + .rd_min = 24, + .rd_max = 24, + .s2div = icst525_s2div, + .idx2s = icst525_idx2s, +}; + +static const struct icst_params cclk_params = { + .ref = 24000000, + .vco_max = ICST525_VCO_MAX_5V, + .vco_min = ICST525_VCO_MIN, + .vd_min = 12, + .vd_max = 160, + .rd_min = 24, + .rd_max = 24, + .s2div = icst525_s2div, + .idx2s = icst525_idx2s, +}; + +/* + * Validate the speed policy. + */ +static int integrator_verify_policy(struct cpufreq_policy *policy) +{ + struct icst_vco vco; + + cpufreq_verify_within_limits(policy, + policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + + vco = icst_hz_to_vco(&cclk_params, policy->max * 1000); + policy->max = icst_hz(&cclk_params, vco) / 1000; + + vco = icst_hz_to_vco(&cclk_params, policy->min * 1000); + policy->min = icst_hz(&cclk_params, vco) / 1000; + + cpufreq_verify_within_limits(policy, + policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + + return 0; +} + + +static int integrator_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + cpumask_t cpus_allowed; + int cpu = policy->cpu; + struct icst_vco vco; + struct cpufreq_freqs freqs; + u_int cm_osc; + + /* + * Save this threads cpus_allowed mask. + */ + cpus_allowed = current->cpus_allowed; + + /* + * Bind to the specified CPU. When this call returns, + * we should be running on the right CPU. + */ + set_cpus_allowed(current, cpumask_of_cpu(cpu)); + BUG_ON(cpu != smp_processor_id()); + + /* get current setting */ + cm_osc = __raw_readl(CM_OSC); + + if (machine_is_integrator()) { + vco.s = (cm_osc >> 8) & 7; + } else if (machine_is_cintegrator()) { + vco.s = 1; + } + vco.v = cm_osc & 255; + vco.r = 22; + freqs.old = icst_hz(&cclk_params, vco) / 1000; + + /* icst_hz_to_vco rounds down -- so we need the next + * larger freq in case of CPUFREQ_RELATION_L. + */ + if (relation == CPUFREQ_RELATION_L) + target_freq += 999; + if (target_freq > policy->max) + target_freq = policy->max; + vco = icst_hz_to_vco(&cclk_params, target_freq * 1000); + freqs.new = icst_hz(&cclk_params, vco) / 1000; + + if (freqs.old == freqs.new) { + set_cpus_allowed(current, cpus_allowed); + return 0; + } + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + cm_osc = __raw_readl(CM_OSC); + + if (machine_is_integrator()) { + cm_osc &= 0xfffff800; + cm_osc |= vco.s << 8; + } else if (machine_is_cintegrator()) { + cm_osc &= 0xffffff00; + } + cm_osc |= vco.v; + + __raw_writel(0xa05f, CM_LOCK); + __raw_writel(cm_osc, CM_OSC); + __raw_writel(0, CM_LOCK); + + /* + * Restore the CPUs allowed mask. + */ + set_cpus_allowed(current, cpus_allowed); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static unsigned int integrator_get(unsigned int cpu) +{ + cpumask_t cpus_allowed; + unsigned int current_freq; + u_int cm_osc; + struct icst_vco vco; + + cpus_allowed = current->cpus_allowed; + + set_cpus_allowed(current, cpumask_of_cpu(cpu)); + BUG_ON(cpu != smp_processor_id()); + + /* detect memory etc. */ + cm_osc = __raw_readl(CM_OSC); + + if (machine_is_integrator()) { + vco.s = (cm_osc >> 8) & 7; + } else { + vco.s = 1; + } + vco.v = cm_osc & 255; + vco.r = 22; + + current_freq = icst_hz(&cclk_params, vco) / 1000; /* current freq */ + + set_cpus_allowed(current, cpus_allowed); + + return current_freq; +} + +static int integrator_cpufreq_init(struct cpufreq_policy *policy) +{ + + /* set default policy and cpuinfo */ + policy->cpuinfo.max_freq = 160000; + policy->cpuinfo.min_freq = 12000; + policy->cpuinfo.transition_latency = 1000000; /* 1 ms, assumed */ + policy->cur = policy->min = policy->max = integrator_get(policy->cpu); + + return 0; +} + +static struct cpufreq_driver integrator_driver = { + .verify = integrator_verify_policy, + .target = integrator_set_target, + .get = integrator_get, + .init = integrator_cpufreq_init, + .name = "integrator", +}; + +static int __init integrator_cpu_init(void) +{ + return cpufreq_register_driver(&integrator_driver); +} + +static void __exit integrator_cpu_exit(void) +{ + cpufreq_unregister_driver(&integrator_driver); +} + +MODULE_AUTHOR ("Russell M. King"); +MODULE_DESCRIPTION ("cpufreq driver for ARM Integrator CPUs"); +MODULE_LICENSE ("GPL"); + +module_init(integrator_cpu_init); +module_exit(integrator_cpu_exit); diff --git a/drivers/cpufreq/intel_pstate.c b/drivers/cpufreq/intel_pstate.c index 6133ef5cf67..cc3a8e6c92b 100644 --- a/drivers/cpufreq/intel_pstate.c +++ b/drivers/cpufreq/intel_pstate.c @@ -1,5 +1,5 @@ /* - * cpufreq_snb.c: Native P state management for Intel processors + * intel_pstate.c: Native P state management for Intel processors * * (C) Copyright 2012 Intel Corporation * Author: Dirk Brandewie <dirk.j.brandewie@intel.com> @@ -657,30 +657,27 @@ static unsigned int intel_pstate_get(unsigned int cpu_num) static int intel_pstate_set_policy(struct cpufreq_policy *policy) { struct cpudata *cpu; - int min, max; cpu = all_cpu_data[policy->cpu]; if (!policy->cpuinfo.max_freq) return -ENODEV; - intel_pstate_get_min_max(cpu, &min, &max); - - limits.min_perf_pct = (policy->min * 100) / policy->cpuinfo.max_freq; - limits.min_perf_pct = clamp_t(int, limits.min_perf_pct, 0 , 100); - limits.min_perf = div_fp(int_tofp(limits.min_perf_pct), int_tofp(100)); - - limits.max_perf_pct = policy->max * 100 / policy->cpuinfo.max_freq; - limits.max_perf_pct = clamp_t(int, limits.max_perf_pct, 0 , 100); - limits.max_perf = div_fp(int_tofp(limits.max_perf_pct), int_tofp(100)); - if (policy->policy == CPUFREQ_POLICY_PERFORMANCE) { limits.min_perf_pct = 100; limits.min_perf = int_tofp(1); limits.max_perf_pct = 100; limits.max_perf = int_tofp(1); limits.no_turbo = 0; + return 0; } + limits.min_perf_pct = (policy->min * 100) / policy->cpuinfo.max_freq; + limits.min_perf_pct = clamp_t(int, limits.min_perf_pct, 0 , 100); + limits.min_perf = div_fp(int_tofp(limits.min_perf_pct), int_tofp(100)); + + limits.max_perf_pct = policy->max * 100 / policy->cpuinfo.max_freq; + limits.max_perf_pct = clamp_t(int, limits.max_perf_pct, 0 , 100); + limits.max_perf = div_fp(int_tofp(limits.max_perf_pct), int_tofp(100)); return 0; } diff --git a/drivers/cpufreq/kirkwood-cpufreq.c b/drivers/cpufreq/kirkwood-cpufreq.c index 0e83e3c24f5..d36ea8dc96e 100644 --- a/drivers/cpufreq/kirkwood-cpufreq.c +++ b/drivers/cpufreq/kirkwood-cpufreq.c @@ -55,7 +55,8 @@ static unsigned int kirkwood_cpufreq_get_cpu_frequency(unsigned int cpu) return kirkwood_freq_table[0].frequency; } -static void kirkwood_cpufreq_set_cpu_state(unsigned int index) +static void kirkwood_cpufreq_set_cpu_state(struct cpufreq_policy *policy, + unsigned int index) { struct cpufreq_freqs freqs; unsigned int state = kirkwood_freq_table[index].index; @@ -63,9 +64,8 @@ static void kirkwood_cpufreq_set_cpu_state(unsigned int index) freqs.old = kirkwood_cpufreq_get_cpu_frequency(0); freqs.new = kirkwood_freq_table[index].frequency; - freqs.cpu = 0; /* Kirkwood is UP */ - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); dev_dbg(priv.dev, "Attempting to set frequency to %i KHz\n", kirkwood_freq_table[index].frequency); @@ -99,7 +99,7 @@ static void kirkwood_cpufreq_set_cpu_state(unsigned int index) local_irq_enable(); } - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); }; static int kirkwood_cpufreq_verify(struct cpufreq_policy *policy) @@ -117,7 +117,7 @@ static int kirkwood_cpufreq_target(struct cpufreq_policy *policy, target_freq, relation, &index)) return -EINVAL; - kirkwood_cpufreq_set_cpu_state(index); + kirkwood_cpufreq_set_cpu_state(policy, index); return 0; } @@ -175,11 +175,9 @@ static int kirkwood_cpufreq_probe(struct platform_device *pdev) dev_err(&pdev->dev, "Cannot get memory resource\n"); return -ENODEV; } - priv.base = devm_request_and_ioremap(&pdev->dev, res); - if (!priv.base) { - dev_err(&pdev->dev, "Cannot ioremap\n"); - return -EADDRNOTAVAIL; - } + priv.base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(priv.base)) + return PTR_ERR(priv.base); np = of_find_node_by_path("/cpus/cpu@0"); if (!np) diff --git a/drivers/cpufreq/longhaul.c b/drivers/cpufreq/longhaul.c index 1180d536d1e..b448638e34d 100644 --- a/drivers/cpufreq/longhaul.c +++ b/drivers/cpufreq/longhaul.c @@ -242,7 +242,8 @@ static void do_powersaver(int cx_address, unsigned int mults_index, * Sets a new clock ratio. */ -static void longhaul_setstate(unsigned int table_index) +static void longhaul_setstate(struct cpufreq_policy *policy, + unsigned int table_index) { unsigned int mults_index; int speed, mult; @@ -267,9 +268,8 @@ static void longhaul_setstate(unsigned int table_index) freqs.old = calc_speed(longhaul_get_cpu_mult()); freqs.new = speed; - freqs.cpu = 0; /* longhaul.c is UP only driver */ - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); pr_debug("Setting to FSB:%dMHz Mult:%d.%dx (%s)\n", fsb, mult/10, mult%10, print_speed(speed/1000)); @@ -386,7 +386,7 @@ retry_loop: } } /* Report true CPU frequency */ - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); if (!bm_timeout) printk(KERN_INFO PFX "Warning: Timeout while waiting for " @@ -648,7 +648,7 @@ static int longhaul_target(struct cpufreq_policy *policy, return 0; if (!can_scale_voltage) - longhaul_setstate(table_index); + longhaul_setstate(policy, table_index); else { /* On test system voltage transitions exceeding single * step up or down were turning motherboard off. Both @@ -663,7 +663,7 @@ static int longhaul_target(struct cpufreq_policy *policy, while (i != table_index) { vid = (longhaul_table[i].index >> 8) & 0x1f; if (vid != current_vid) { - longhaul_setstate(i); + longhaul_setstate(policy, i); current_vid = vid; msleep(200); } @@ -672,7 +672,7 @@ static int longhaul_target(struct cpufreq_policy *policy, else i--; } - longhaul_setstate(table_index); + longhaul_setstate(policy, table_index); } longhaul_index = table_index; return 0; @@ -998,15 +998,17 @@ static int __init longhaul_init(void) static void __exit longhaul_exit(void) { + struct cpufreq_policy *policy = cpufreq_cpu_get(0); int i; for (i = 0; i < numscales; i++) { if (mults[i] == maxmult) { - longhaul_setstate(i); + longhaul_setstate(policy, i); break; } } + cpufreq_cpu_put(policy); cpufreq_unregister_driver(&longhaul_driver); kfree(longhaul_table); } diff --git a/drivers/cpufreq/loongson2_cpufreq.c b/drivers/cpufreq/loongson2_cpufreq.c new file mode 100644 index 00000000000..84889573b56 --- /dev/null +++ b/drivers/cpufreq/loongson2_cpufreq.c @@ -0,0 +1,248 @@ +/* + * Cpufreq driver for the loongson-2 processors + * + * The 2E revision of loongson processor not support this feature. + * + * Copyright (C) 2006 - 2008 Lemote Inc. & Insititute of Computing Technology + * Author: Yanhua, yanh@lemote.com + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#include <linux/cpufreq.h> +#include <linux/module.h> +#include <linux/err.h> +#include <linux/sched.h> /* set_cpus_allowed() */ +#include <linux/delay.h> +#include <linux/platform_device.h> + +#include <asm/clock.h> + +#include <asm/mach-loongson/loongson.h> + +static uint nowait; + +static struct clk *cpuclk; + +static void (*saved_cpu_wait) (void); + +static int loongson2_cpu_freq_notifier(struct notifier_block *nb, + unsigned long val, void *data); + +static struct notifier_block loongson2_cpufreq_notifier_block = { + .notifier_call = loongson2_cpu_freq_notifier +}; + +static int loongson2_cpu_freq_notifier(struct notifier_block *nb, + unsigned long val, void *data) +{ + if (val == CPUFREQ_POSTCHANGE) + current_cpu_data.udelay_val = loops_per_jiffy; + + return 0; +} + +static unsigned int loongson2_cpufreq_get(unsigned int cpu) +{ + return clk_get_rate(cpuclk); +} + +/* + * Here we notify other drivers of the proposed change and the final change. + */ +static int loongson2_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int cpu = policy->cpu; + unsigned int newstate = 0; + cpumask_t cpus_allowed; + struct cpufreq_freqs freqs; + unsigned int freq; + + cpus_allowed = current->cpus_allowed; + set_cpus_allowed_ptr(current, cpumask_of(cpu)); + + if (cpufreq_frequency_table_target + (policy, &loongson2_clockmod_table[0], target_freq, relation, + &newstate)) + return -EINVAL; + + freq = + ((cpu_clock_freq / 1000) * + loongson2_clockmod_table[newstate].index) / 8; + if (freq < policy->min || freq > policy->max) + return -EINVAL; + + pr_debug("cpufreq: requested frequency %u Hz\n", target_freq * 1000); + + freqs.old = loongson2_cpufreq_get(cpu); + freqs.new = freq; + freqs.flags = 0; + + if (freqs.new == freqs.old) + return 0; + + /* notifiers */ + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + set_cpus_allowed_ptr(current, &cpus_allowed); + + /* setting the cpu frequency */ + clk_set_rate(cpuclk, freq); + + /* notifiers */ + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + pr_debug("cpufreq: set frequency %u kHz\n", freq); + + return 0; +} + +static int loongson2_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + int i; + unsigned long rate; + int ret; + + cpuclk = clk_get(NULL, "cpu_clk"); + if (IS_ERR(cpuclk)) { + printk(KERN_ERR "cpufreq: couldn't get CPU clk\n"); + return PTR_ERR(cpuclk); + } + + rate = cpu_clock_freq / 1000; + if (!rate) { + clk_put(cpuclk); + return -EINVAL; + } + ret = clk_set_rate(cpuclk, rate); + if (ret) { + clk_put(cpuclk); + return ret; + } + + /* clock table init */ + for (i = 2; + (loongson2_clockmod_table[i].frequency != CPUFREQ_TABLE_END); + i++) + loongson2_clockmod_table[i].frequency = (rate * i) / 8; + + policy->cur = loongson2_cpufreq_get(policy->cpu); + + cpufreq_frequency_table_get_attr(&loongson2_clockmod_table[0], + policy->cpu); + + return cpufreq_frequency_table_cpuinfo(policy, + &loongson2_clockmod_table[0]); +} + +static int loongson2_cpufreq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, + &loongson2_clockmod_table[0]); +} + +static int loongson2_cpufreq_exit(struct cpufreq_policy *policy) +{ + clk_put(cpuclk); + return 0; +} + +static struct freq_attr *loongson2_table_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver loongson2_cpufreq_driver = { + .owner = THIS_MODULE, + .name = "loongson2", + .init = loongson2_cpufreq_cpu_init, + .verify = loongson2_cpufreq_verify, + .target = loongson2_cpufreq_target, + .get = loongson2_cpufreq_get, + .exit = loongson2_cpufreq_exit, + .attr = loongson2_table_attr, +}; + +static struct platform_device_id platform_device_ids[] = { + { + .name = "loongson2_cpufreq", + }, + {} +}; + +MODULE_DEVICE_TABLE(platform, platform_device_ids); + +static struct platform_driver platform_driver = { + .driver = { + .name = "loongson2_cpufreq", + .owner = THIS_MODULE, + }, + .id_table = platform_device_ids, +}; + +/* + * This is the simple version of Loongson-2 wait, Maybe we need do this in + * interrupt disabled context. + */ + +static DEFINE_SPINLOCK(loongson2_wait_lock); + +static void loongson2_cpu_wait(void) +{ + unsigned long flags; + u32 cpu_freq; + + spin_lock_irqsave(&loongson2_wait_lock, flags); + cpu_freq = LOONGSON_CHIPCFG0; + LOONGSON_CHIPCFG0 &= ~0x7; /* Put CPU into wait mode */ + LOONGSON_CHIPCFG0 = cpu_freq; /* Restore CPU state */ + spin_unlock_irqrestore(&loongson2_wait_lock, flags); +} + +static int __init cpufreq_init(void) +{ + int ret; + + /* Register platform stuff */ + ret = platform_driver_register(&platform_driver); + if (ret) + return ret; + + pr_info("cpufreq: Loongson-2F CPU frequency driver.\n"); + + cpufreq_register_notifier(&loongson2_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + ret = cpufreq_register_driver(&loongson2_cpufreq_driver); + + if (!ret && !nowait) { + saved_cpu_wait = cpu_wait; + cpu_wait = loongson2_cpu_wait; + } + + return ret; +} + +static void __exit cpufreq_exit(void) +{ + if (!nowait && saved_cpu_wait) + cpu_wait = saved_cpu_wait; + cpufreq_unregister_driver(&loongson2_cpufreq_driver); + cpufreq_unregister_notifier(&loongson2_cpufreq_notifier_block, + CPUFREQ_TRANSITION_NOTIFIER); + + platform_driver_unregister(&platform_driver); +} + +module_init(cpufreq_init); +module_exit(cpufreq_exit); + +module_param(nowait, uint, 0644); +MODULE_PARM_DESC(nowait, "Disable Loongson-2F specific wait"); + +MODULE_AUTHOR("Yanhua <yanh@lemote.com>"); +MODULE_DESCRIPTION("cpufreq driver for Loongson2F"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/maple-cpufreq.c b/drivers/cpufreq/maple-cpufreq.c index d4c4989823d..cdd62915efa 100644 --- a/drivers/cpufreq/maple-cpufreq.c +++ b/drivers/cpufreq/maple-cpufreq.c @@ -158,11 +158,10 @@ static int maple_cpufreq_target(struct cpufreq_policy *policy, freqs.old = maple_cpu_freqs[maple_pmode_cur].frequency; freqs.new = maple_cpu_freqs[newstate].frequency; - freqs.cpu = 0; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); rc = maple_scom_switch_freq(newstate); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); mutex_unlock(&maple_switch_mutex); diff --git a/drivers/cpufreq/omap-cpufreq.c b/drivers/cpufreq/omap-cpufreq.c index 9128c07bafb..0279d18a57f 100644 --- a/drivers/cpufreq/omap-cpufreq.c +++ b/drivers/cpufreq/omap-cpufreq.c @@ -25,6 +25,7 @@ #include <linux/opp.h> #include <linux/cpu.h> #include <linux/module.h> +#include <linux/platform_device.h> #include <linux/regulator/consumer.h> #include <asm/smp_plat.h> @@ -88,16 +89,12 @@ static int omap_target(struct cpufreq_policy *policy, } freqs.old = omap_getspeed(policy->cpu); - freqs.cpu = policy->cpu; if (freqs.old == freqs.new && policy->cur == freqs.new) return ret; /* notifiers */ - for_each_cpu(i, policy->cpus) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); freq = freqs.new * 1000; ret = clk_round_rate(mpu_clk, freq); @@ -157,10 +154,7 @@ static int omap_target(struct cpufreq_policy *policy, done: /* notifiers */ - for_each_cpu(i, policy->cpus) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return ret; } @@ -184,7 +178,7 @@ static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy) goto fail_ck; } - policy->cur = policy->min = policy->max = omap_getspeed(policy->cpu); + policy->cur = omap_getspeed(policy->cpu); if (!freq_table) result = opp_init_cpufreq_table(mpu_dev, &freq_table); @@ -203,8 +197,6 @@ static int __cpuinit omap_cpu_init(struct cpufreq_policy *policy) cpufreq_frequency_table_get_attr(freq_table, policy->cpu); - policy->min = policy->cpuinfo.min_freq; - policy->max = policy->cpuinfo.max_freq; policy->cur = omap_getspeed(policy->cpu); /* @@ -252,7 +244,7 @@ static struct cpufreq_driver omap_driver = { .attr = omap_cpufreq_attr, }; -static int __init omap_cpufreq_init(void) +static int omap_cpufreq_probe(struct platform_device *pdev) { mpu_dev = get_cpu_device(0); if (!mpu_dev) { @@ -280,12 +272,20 @@ static int __init omap_cpufreq_init(void) return cpufreq_register_driver(&omap_driver); } -static void __exit omap_cpufreq_exit(void) +static int omap_cpufreq_remove(struct platform_device *pdev) { - cpufreq_unregister_driver(&omap_driver); + return cpufreq_unregister_driver(&omap_driver); } +static struct platform_driver omap_cpufreq_platdrv = { + .driver = { + .name = "omap-cpufreq", + .owner = THIS_MODULE, + }, + .probe = omap_cpufreq_probe, + .remove = omap_cpufreq_remove, +}; +module_platform_driver(omap_cpufreq_platdrv); + MODULE_DESCRIPTION("cpufreq driver for OMAP SoCs"); MODULE_LICENSE("GPL"); -module_init(omap_cpufreq_init); -module_exit(omap_cpufreq_exit); diff --git a/drivers/cpufreq/p4-clockmod.c b/drivers/cpufreq/p4-clockmod.c index 827629c9aad..421ef37d0bb 100644 --- a/drivers/cpufreq/p4-clockmod.c +++ b/drivers/cpufreq/p4-clockmod.c @@ -58,8 +58,7 @@ static int cpufreq_p4_setdc(unsigned int cpu, unsigned int newstate) { u32 l, h; - if (!cpu_online(cpu) || - (newstate > DC_DISABLE) || (newstate == DC_RESV)) + if ((newstate > DC_DISABLE) || (newstate == DC_RESV)) return -EINVAL; rdmsr_on_cpu(cpu, MSR_IA32_THERM_STATUS, &l, &h); @@ -125,10 +124,7 @@ static int cpufreq_p4_target(struct cpufreq_policy *policy, return 0; /* notifiers */ - for_each_cpu(i, policy->cpus) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); /* run on each logical CPU, * see section 13.15.3 of IA32 Intel Architecture Software @@ -138,10 +134,7 @@ static int cpufreq_p4_target(struct cpufreq_policy *policy, cpufreq_p4_setdc(i, p4clockmod_table[newstate].index); /* notifiers */ - for_each_cpu(i, policy->cpus) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return 0; } diff --git a/drivers/cpufreq/pcc-cpufreq.c b/drivers/cpufreq/pcc-cpufreq.c index 503996a94a6..0de00081a81 100644 --- a/drivers/cpufreq/pcc-cpufreq.c +++ b/drivers/cpufreq/pcc-cpufreq.c @@ -215,8 +215,7 @@ static int pcc_cpufreq_target(struct cpufreq_policy *policy, (pcch_virt_addr + pcc_cpu_data->input_offset)); freqs.new = target_freq; - freqs.cpu = cpu; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); input_buffer = 0x1 | (((target_freq * 100) / (ioread32(&pcch_hdr->nominal) * 1000)) << 8); @@ -237,7 +236,7 @@ static int pcc_cpufreq_target(struct cpufreq_policy *policy, } iowrite16(0, &pcch_hdr->status); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); pr_debug("target: was SUCCESSFUL for cpu %d\n", cpu); spin_unlock(&pcc_lock); diff --git a/drivers/cpufreq/powernow-k6.c b/drivers/cpufreq/powernow-k6.c index af23e0b9ec9..ea0222a45b7 100644 --- a/drivers/cpufreq/powernow-k6.c +++ b/drivers/cpufreq/powernow-k6.c @@ -68,7 +68,8 @@ static int powernow_k6_get_cpu_multiplier(void) * * Tries to change the PowerNow! multiplier */ -static void powernow_k6_set_state(unsigned int best_i) +static void powernow_k6_set_state(struct cpufreq_policy *policy, + unsigned int best_i) { unsigned long outvalue = 0, invalue = 0; unsigned long msrval; @@ -81,9 +82,8 @@ static void powernow_k6_set_state(unsigned int best_i) freqs.old = busfreq * powernow_k6_get_cpu_multiplier(); freqs.new = busfreq * clock_ratio[best_i].index; - freqs.cpu = 0; /* powernow-k6.c is UP only driver */ - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); /* we now need to transform best_i to the BVC format, see AMD#23446 */ @@ -98,7 +98,7 @@ static void powernow_k6_set_state(unsigned int best_i) msrval = POWERNOW_IOPORT + 0x0; wrmsr(MSR_K6_EPMR, msrval, 0); /* disable it again */ - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return; } @@ -136,7 +136,7 @@ static int powernow_k6_target(struct cpufreq_policy *policy, target_freq, relation, &newstate)) return -EINVAL; - powernow_k6_set_state(newstate); + powernow_k6_set_state(policy, newstate); return 0; } @@ -182,7 +182,7 @@ static int powernow_k6_cpu_exit(struct cpufreq_policy *policy) unsigned int i; for (i = 0; i < 8; i++) { if (i == max_multiplier) - powernow_k6_set_state(i); + powernow_k6_set_state(policy, i); } cpufreq_frequency_table_put_attr(policy->cpu); return 0; diff --git a/drivers/cpufreq/powernow-k7.c b/drivers/cpufreq/powernow-k7.c index 334cc2f1e9f..53888dacbe5 100644 --- a/drivers/cpufreq/powernow-k7.c +++ b/drivers/cpufreq/powernow-k7.c @@ -248,7 +248,7 @@ static void change_VID(int vid) } -static void change_speed(unsigned int index) +static void change_speed(struct cpufreq_policy *policy, unsigned int index) { u8 fid, vid; struct cpufreq_freqs freqs; @@ -263,15 +263,13 @@ static void change_speed(unsigned int index) fid = powernow_table[index].index & 0xFF; vid = (powernow_table[index].index & 0xFF00) >> 8; - freqs.cpu = 0; - rdmsrl(MSR_K7_FID_VID_STATUS, fidvidstatus.val); cfid = fidvidstatus.bits.CFID; freqs.old = fsb * fid_codes[cfid] / 10; freqs.new = powernow_table[index].frequency; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); /* Now do the magic poking into the MSRs. */ @@ -292,7 +290,7 @@ static void change_speed(unsigned int index) if (have_a0 == 1) local_irq_enable(); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); } @@ -546,7 +544,7 @@ static int powernow_target(struct cpufreq_policy *policy, relation, &newstate)) return -EINVAL; - change_speed(newstate); + change_speed(policy, newstate); return 0; } diff --git a/drivers/cpufreq/powernow-k8.c b/drivers/cpufreq/powernow-k8.c index d13a13678b5..b828efe4b2f 100644 --- a/drivers/cpufreq/powernow-k8.c +++ b/drivers/cpufreq/powernow-k8.c @@ -928,9 +928,10 @@ static int get_transition_latency(struct powernow_k8_data *data) static int transition_frequency_fidvid(struct powernow_k8_data *data, unsigned int index) { + struct cpufreq_policy *policy; u32 fid = 0; u32 vid = 0; - int res, i; + int res; struct cpufreq_freqs freqs; pr_debug("cpu %d transition to index %u\n", smp_processor_id(), index); @@ -959,10 +960,10 @@ static int transition_frequency_fidvid(struct powernow_k8_data *data, freqs.old = find_khz_freq_from_fid(data->currfid); freqs.new = find_khz_freq_from_fid(fid); - for_each_cpu(i, data->available_cores) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - } + policy = cpufreq_cpu_get(smp_processor_id()); + cpufreq_cpu_put(policy); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); res = transition_fid_vid(data, fid, vid); if (res) @@ -970,10 +971,7 @@ static int transition_frequency_fidvid(struct powernow_k8_data *data, freqs.new = find_khz_freq_from_fid(data->currfid); - for_each_cpu(i, data->available_cores) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return res; } @@ -1104,9 +1102,6 @@ static int __cpuinit powernowk8_cpu_init(struct cpufreq_policy *pol) struct init_on_cpu init_on_cpu; int rc; - if (!cpu_online(pol->cpu)) - return -ENODEV; - smp_call_function_single(pol->cpu, check_supported_cpu, &rc, 1); if (rc) return -ENODEV; diff --git a/drivers/cpufreq/ppc_cbe_cpufreq.c b/drivers/cpufreq/ppc_cbe_cpufreq.c new file mode 100644 index 00000000000..e577a1dbbfc --- /dev/null +++ b/drivers/cpufreq/ppc_cbe_cpufreq.c @@ -0,0 +1,209 @@ +/* + * cpufreq driver for the cell processor + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005-2007 + * + * Author: Christian Krafft <krafft@de.ibm.com> + * + * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/cpufreq.h> +#include <linux/module.h> +#include <linux/of_platform.h> + +#include <asm/machdep.h> +#include <asm/prom.h> +#include <asm/cell-regs.h> + +#include "ppc_cbe_cpufreq.h" + +static DEFINE_MUTEX(cbe_switch_mutex); + + +/* the CBE supports an 8 step frequency scaling */ +static struct cpufreq_frequency_table cbe_freqs[] = { + {1, 0}, + {2, 0}, + {3, 0}, + {4, 0}, + {5, 0}, + {6, 0}, + {8, 0}, + {10, 0}, + {0, CPUFREQ_TABLE_END}, +}; + +/* + * hardware specific functions + */ + +static int set_pmode(unsigned int cpu, unsigned int slow_mode) +{ + int rc; + + if (cbe_cpufreq_has_pmi) + rc = cbe_cpufreq_set_pmode_pmi(cpu, slow_mode); + else + rc = cbe_cpufreq_set_pmode(cpu, slow_mode); + + pr_debug("register contains slow mode %d\n", cbe_cpufreq_get_pmode(cpu)); + + return rc; +} + +/* + * cpufreq functions + */ + +static int cbe_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + const u32 *max_freqp; + u32 max_freq; + int i, cur_pmode; + struct device_node *cpu; + + cpu = of_get_cpu_node(policy->cpu, NULL); + + if (!cpu) + return -ENODEV; + + pr_debug("init cpufreq on CPU %d\n", policy->cpu); + + /* + * Let's check we can actually get to the CELL regs + */ + if (!cbe_get_cpu_pmd_regs(policy->cpu) || + !cbe_get_cpu_mic_tm_regs(policy->cpu)) { + pr_info("invalid CBE regs pointers for cpufreq\n"); + return -EINVAL; + } + + max_freqp = of_get_property(cpu, "clock-frequency", NULL); + + of_node_put(cpu); + + if (!max_freqp) + return -EINVAL; + + /* we need the freq in kHz */ + max_freq = *max_freqp / 1000; + + pr_debug("max clock-frequency is at %u kHz\n", max_freq); + pr_debug("initializing frequency table\n"); + + /* initialize frequency table */ + for (i=0; cbe_freqs[i].frequency!=CPUFREQ_TABLE_END; i++) { + cbe_freqs[i].frequency = max_freq / cbe_freqs[i].index; + pr_debug("%d: %d\n", i, cbe_freqs[i].frequency); + } + + /* if DEBUG is enabled set_pmode() measures the latency + * of a transition */ + policy->cpuinfo.transition_latency = 25000; + + cur_pmode = cbe_cpufreq_get_pmode(policy->cpu); + pr_debug("current pmode is at %d\n",cur_pmode); + + policy->cur = cbe_freqs[cur_pmode].frequency; + +#ifdef CONFIG_SMP + cpumask_copy(policy->cpus, cpu_sibling_mask(policy->cpu)); +#endif + + cpufreq_frequency_table_get_attr(cbe_freqs, policy->cpu); + + /* this ensures that policy->cpuinfo_min + * and policy->cpuinfo_max are set correctly */ + return cpufreq_frequency_table_cpuinfo(policy, cbe_freqs); +} + +static int cbe_cpufreq_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_put_attr(policy->cpu); + return 0; +} + +static int cbe_cpufreq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, cbe_freqs); +} + +static int cbe_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + int rc; + struct cpufreq_freqs freqs; + unsigned int cbe_pmode_new; + + cpufreq_frequency_table_target(policy, + cbe_freqs, + target_freq, + relation, + &cbe_pmode_new); + + freqs.old = policy->cur; + freqs.new = cbe_freqs[cbe_pmode_new].frequency; + + mutex_lock(&cbe_switch_mutex); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + pr_debug("setting frequency for cpu %d to %d kHz, " \ + "1/%d of max frequency\n", + policy->cpu, + cbe_freqs[cbe_pmode_new].frequency, + cbe_freqs[cbe_pmode_new].index); + + rc = set_pmode(policy->cpu, cbe_pmode_new); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + mutex_unlock(&cbe_switch_mutex); + + return rc; +} + +static struct cpufreq_driver cbe_cpufreq_driver = { + .verify = cbe_cpufreq_verify, + .target = cbe_cpufreq_target, + .init = cbe_cpufreq_cpu_init, + .exit = cbe_cpufreq_cpu_exit, + .name = "cbe-cpufreq", + .owner = THIS_MODULE, + .flags = CPUFREQ_CONST_LOOPS, +}; + +/* + * module init and destoy + */ + +static int __init cbe_cpufreq_init(void) +{ + if (!machine_is(cell)) + return -ENODEV; + + return cpufreq_register_driver(&cbe_cpufreq_driver); +} + +static void __exit cbe_cpufreq_exit(void) +{ + cpufreq_unregister_driver(&cbe_cpufreq_driver); +} + +module_init(cbe_cpufreq_init); +module_exit(cbe_cpufreq_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Christian Krafft <krafft@de.ibm.com>"); diff --git a/drivers/cpufreq/ppc_cbe_cpufreq.h b/drivers/cpufreq/ppc_cbe_cpufreq.h new file mode 100644 index 00000000000..b4c00a5a6a5 --- /dev/null +++ b/drivers/cpufreq/ppc_cbe_cpufreq.h @@ -0,0 +1,24 @@ +/* + * ppc_cbe_cpufreq.h + * + * This file contains the definitions used by the cbe_cpufreq driver. + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005-2007 + * + * Author: Christian Krafft <krafft@de.ibm.com> + * + */ + +#include <linux/cpufreq.h> +#include <linux/types.h> + +int cbe_cpufreq_set_pmode(int cpu, unsigned int pmode); +int cbe_cpufreq_get_pmode(int cpu); + +int cbe_cpufreq_set_pmode_pmi(int cpu, unsigned int pmode); + +#if defined(CONFIG_CPU_FREQ_CBE_PMI) || defined(CONFIG_CPU_FREQ_CBE_PMI_MODULE) +extern bool cbe_cpufreq_has_pmi; +#else +#define cbe_cpufreq_has_pmi (0) +#endif diff --git a/drivers/cpufreq/ppc_cbe_cpufreq_pervasive.c b/drivers/cpufreq/ppc_cbe_cpufreq_pervasive.c new file mode 100644 index 00000000000..84d2f2cf5ba --- /dev/null +++ b/drivers/cpufreq/ppc_cbe_cpufreq_pervasive.c @@ -0,0 +1,115 @@ +/* + * pervasive backend for the cbe_cpufreq driver + * + * This driver makes use of the pervasive unit to + * engage the desired frequency. + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005-2007 + * + * Author: Christian Krafft <krafft@de.ibm.com> + * + * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/time.h> +#include <asm/machdep.h> +#include <asm/hw_irq.h> +#include <asm/cell-regs.h> + +#include "ppc_cbe_cpufreq.h" + +/* to write to MIC register */ +static u64 MIC_Slow_Fast_Timer_table[] = { + [0 ... 7] = 0x007fc00000000000ull, +}; + +/* more values for the MIC */ +static u64 MIC_Slow_Next_Timer_table[] = { + 0x0000240000000000ull, + 0x0000268000000000ull, + 0x000029C000000000ull, + 0x00002D0000000000ull, + 0x0000300000000000ull, + 0x0000334000000000ull, + 0x000039C000000000ull, + 0x00003FC000000000ull, +}; + + +int cbe_cpufreq_set_pmode(int cpu, unsigned int pmode) +{ + struct cbe_pmd_regs __iomem *pmd_regs; + struct cbe_mic_tm_regs __iomem *mic_tm_regs; + unsigned long flags; + u64 value; +#ifdef DEBUG + long time; +#endif + + local_irq_save(flags); + + mic_tm_regs = cbe_get_cpu_mic_tm_regs(cpu); + pmd_regs = cbe_get_cpu_pmd_regs(cpu); + +#ifdef DEBUG + time = jiffies; +#endif + + out_be64(&mic_tm_regs->slow_fast_timer_0, MIC_Slow_Fast_Timer_table[pmode]); + out_be64(&mic_tm_regs->slow_fast_timer_1, MIC_Slow_Fast_Timer_table[pmode]); + + out_be64(&mic_tm_regs->slow_next_timer_0, MIC_Slow_Next_Timer_table[pmode]); + out_be64(&mic_tm_regs->slow_next_timer_1, MIC_Slow_Next_Timer_table[pmode]); + + value = in_be64(&pmd_regs->pmcr); + /* set bits to zero */ + value &= 0xFFFFFFFFFFFFFFF8ull; + /* set bits to next pmode */ + value |= pmode; + + out_be64(&pmd_regs->pmcr, value); + +#ifdef DEBUG + /* wait until new pmode appears in status register */ + value = in_be64(&pmd_regs->pmsr) & 0x07; + while (value != pmode) { + cpu_relax(); + value = in_be64(&pmd_regs->pmsr) & 0x07; + } + + time = jiffies - time; + time = jiffies_to_msecs(time); + pr_debug("had to wait %lu ms for a transition using " \ + "pervasive unit\n", time); +#endif + local_irq_restore(flags); + + return 0; +} + + +int cbe_cpufreq_get_pmode(int cpu) +{ + int ret; + struct cbe_pmd_regs __iomem *pmd_regs; + + pmd_regs = cbe_get_cpu_pmd_regs(cpu); + ret = in_be64(&pmd_regs->pmsr) & 0x07; + + return ret; +} + diff --git a/drivers/cpufreq/ppc_cbe_cpufreq_pmi.c b/drivers/cpufreq/ppc_cbe_cpufreq_pmi.c new file mode 100644 index 00000000000..d29e8da396a --- /dev/null +++ b/drivers/cpufreq/ppc_cbe_cpufreq_pmi.c @@ -0,0 +1,156 @@ +/* + * pmi backend for the cbe_cpufreq driver + * + * (C) Copyright IBM Deutschland Entwicklung GmbH 2005-2007 + * + * Author: Christian Krafft <krafft@de.ibm.com> + * + * 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, 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/timer.h> +#include <linux/module.h> +#include <linux/of_platform.h> + +#include <asm/processor.h> +#include <asm/prom.h> +#include <asm/pmi.h> +#include <asm/cell-regs.h> + +#ifdef DEBUG +#include <asm/time.h> +#endif + +#include "ppc_cbe_cpufreq.h" + +static u8 pmi_slow_mode_limit[MAX_CBE]; + +bool cbe_cpufreq_has_pmi = false; +EXPORT_SYMBOL_GPL(cbe_cpufreq_has_pmi); + +/* + * hardware specific functions + */ + +int cbe_cpufreq_set_pmode_pmi(int cpu, unsigned int pmode) +{ + int ret; + pmi_message_t pmi_msg; +#ifdef DEBUG + long time; +#endif + pmi_msg.type = PMI_TYPE_FREQ_CHANGE; + pmi_msg.data1 = cbe_cpu_to_node(cpu); + pmi_msg.data2 = pmode; + +#ifdef DEBUG + time = jiffies; +#endif + pmi_send_message(pmi_msg); + +#ifdef DEBUG + time = jiffies - time; + time = jiffies_to_msecs(time); + pr_debug("had to wait %lu ms for a transition using " \ + "PMI\n", time); +#endif + ret = pmi_msg.data2; + pr_debug("PMI returned slow mode %d\n", ret); + + return ret; +} +EXPORT_SYMBOL_GPL(cbe_cpufreq_set_pmode_pmi); + + +static void cbe_cpufreq_handle_pmi(pmi_message_t pmi_msg) +{ + u8 node, slow_mode; + + BUG_ON(pmi_msg.type != PMI_TYPE_FREQ_CHANGE); + + node = pmi_msg.data1; + slow_mode = pmi_msg.data2; + + pmi_slow_mode_limit[node] = slow_mode; + + pr_debug("cbe_handle_pmi: node: %d max_freq: %d\n", node, slow_mode); +} + +static int pmi_notifier(struct notifier_block *nb, + unsigned long event, void *data) +{ + struct cpufreq_policy *policy = data; + struct cpufreq_frequency_table *cbe_freqs; + u8 node; + + /* Should this really be called for CPUFREQ_ADJUST, CPUFREQ_INCOMPATIBLE + * and CPUFREQ_NOTIFY policy events?) + */ + if (event == CPUFREQ_START) + return 0; + + cbe_freqs = cpufreq_frequency_get_table(policy->cpu); + node = cbe_cpu_to_node(policy->cpu); + + pr_debug("got notified, event=%lu, node=%u\n", event, node); + + if (pmi_slow_mode_limit[node] != 0) { + pr_debug("limiting node %d to slow mode %d\n", + node, pmi_slow_mode_limit[node]); + + cpufreq_verify_within_limits(policy, 0, + + cbe_freqs[pmi_slow_mode_limit[node]].frequency); + } + + return 0; +} + +static struct notifier_block pmi_notifier_block = { + .notifier_call = pmi_notifier, +}; + +static struct pmi_handler cbe_pmi_handler = { + .type = PMI_TYPE_FREQ_CHANGE, + .handle_pmi_message = cbe_cpufreq_handle_pmi, +}; + + + +static int __init cbe_cpufreq_pmi_init(void) +{ + cbe_cpufreq_has_pmi = pmi_register_handler(&cbe_pmi_handler) == 0; + + if (!cbe_cpufreq_has_pmi) + return -ENODEV; + + cpufreq_register_notifier(&pmi_notifier_block, CPUFREQ_POLICY_NOTIFIER); + + return 0; +} + +static void __exit cbe_cpufreq_pmi_exit(void) +{ + cpufreq_unregister_notifier(&pmi_notifier_block, CPUFREQ_POLICY_NOTIFIER); + pmi_unregister_handler(&cbe_pmi_handler); +} + +module_init(cbe_cpufreq_pmi_init); +module_exit(cbe_cpufreq_pmi_exit); + +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Christian Krafft <krafft@de.ibm.com>"); diff --git a/drivers/cpufreq/pxa2xx-cpufreq.c b/drivers/cpufreq/pxa2xx-cpufreq.c new file mode 100644 index 00000000000..9e5bc8e388a --- /dev/null +++ b/drivers/cpufreq/pxa2xx-cpufreq.c @@ -0,0 +1,492 @@ +/* + * Copyright (C) 2002,2003 Intrinsyc Software + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * History: + * 31-Jul-2002 : Initial version [FB] + * 29-Jan-2003 : added PXA255 support [FB] + * 20-Apr-2003 : ported to v2.5 (Dustin McIntire, Sensoria Corp.) + * + * Note: + * This driver may change the memory bus clock rate, but will not do any + * platform specific access timing changes... for example if you have flash + * memory connected to CS0, you will need to register a platform specific + * notifier which will adjust the memory access strobes to maintain a + * minimum strobe width. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/err.h> +#include <linux/regulator/consumer.h> +#include <linux/io.h> + +#include <mach/pxa2xx-regs.h> +#include <mach/smemc.h> + +#ifdef DEBUG +static unsigned int freq_debug; +module_param(freq_debug, uint, 0); +MODULE_PARM_DESC(freq_debug, "Set the debug messages to on=1/off=0"); +#else +#define freq_debug 0 +#endif + +static struct regulator *vcc_core; + +static unsigned int pxa27x_maxfreq; +module_param(pxa27x_maxfreq, uint, 0); +MODULE_PARM_DESC(pxa27x_maxfreq, "Set the pxa27x maxfreq in MHz" + "(typically 624=>pxa270, 416=>pxa271, 520=>pxa272)"); + +typedef struct { + unsigned int khz; + unsigned int membus; + unsigned int cccr; + unsigned int div2; + unsigned int cclkcfg; + int vmin; + int vmax; +} pxa_freqs_t; + +/* Define the refresh period in mSec for the SDRAM and the number of rows */ +#define SDRAM_TREF 64 /* standard 64ms SDRAM */ +static unsigned int sdram_rows; + +#define CCLKCFG_TURBO 0x1 +#define CCLKCFG_FCS 0x2 +#define CCLKCFG_HALFTURBO 0x4 +#define CCLKCFG_FASTBUS 0x8 +#define MDREFR_DB2_MASK (MDREFR_K2DB2 | MDREFR_K1DB2) +#define MDREFR_DRI_MASK 0xFFF + +#define MDCNFG_DRAC2(mdcnfg) (((mdcnfg) >> 21) & 0x3) +#define MDCNFG_DRAC0(mdcnfg) (((mdcnfg) >> 5) & 0x3) + +/* + * PXA255 definitions + */ +/* Use the run mode frequencies for the CPUFREQ_POLICY_PERFORMANCE policy */ +#define CCLKCFG CCLKCFG_TURBO | CCLKCFG_FCS + +static pxa_freqs_t pxa255_run_freqs[] = +{ + /* CPU MEMBUS CCCR DIV2 CCLKCFG run turbo PXbus SDRAM */ + { 99500, 99500, 0x121, 1, CCLKCFG, -1, -1}, /* 99, 99, 50, 50 */ + {132700, 132700, 0x123, 1, CCLKCFG, -1, -1}, /* 133, 133, 66, 66 */ + {199100, 99500, 0x141, 0, CCLKCFG, -1, -1}, /* 199, 199, 99, 99 */ + {265400, 132700, 0x143, 1, CCLKCFG, -1, -1}, /* 265, 265, 133, 66 */ + {331800, 165900, 0x145, 1, CCLKCFG, -1, -1}, /* 331, 331, 166, 83 */ + {398100, 99500, 0x161, 0, CCLKCFG, -1, -1}, /* 398, 398, 196, 99 */ +}; + +/* Use the turbo mode frequencies for the CPUFREQ_POLICY_POWERSAVE policy */ +static pxa_freqs_t pxa255_turbo_freqs[] = +{ + /* CPU MEMBUS CCCR DIV2 CCLKCFG run turbo PXbus SDRAM */ + { 99500, 99500, 0x121, 1, CCLKCFG, -1, -1}, /* 99, 99, 50, 50 */ + {199100, 99500, 0x221, 0, CCLKCFG, -1, -1}, /* 99, 199, 50, 99 */ + {298500, 99500, 0x321, 0, CCLKCFG, -1, -1}, /* 99, 287, 50, 99 */ + {298600, 99500, 0x1c1, 0, CCLKCFG, -1, -1}, /* 199, 287, 99, 99 */ + {398100, 99500, 0x241, 0, CCLKCFG, -1, -1}, /* 199, 398, 99, 99 */ +}; + +#define NUM_PXA25x_RUN_FREQS ARRAY_SIZE(pxa255_run_freqs) +#define NUM_PXA25x_TURBO_FREQS ARRAY_SIZE(pxa255_turbo_freqs) + +static struct cpufreq_frequency_table + pxa255_run_freq_table[NUM_PXA25x_RUN_FREQS+1]; +static struct cpufreq_frequency_table + pxa255_turbo_freq_table[NUM_PXA25x_TURBO_FREQS+1]; + +static unsigned int pxa255_turbo_table; +module_param(pxa255_turbo_table, uint, 0); +MODULE_PARM_DESC(pxa255_turbo_table, "Selects the frequency table (0 = run table, !0 = turbo table)"); + +/* + * PXA270 definitions + * + * For the PXA27x: + * Control variables are A, L, 2N for CCCR; B, HT, T for CLKCFG. + * + * A = 0 => memory controller clock from table 3-7, + * A = 1 => memory controller clock = system bus clock + * Run mode frequency = 13 MHz * L + * Turbo mode frequency = 13 MHz * L * N + * System bus frequency = 13 MHz * L / (B + 1) + * + * In CCCR: + * A = 1 + * L = 16 oscillator to run mode ratio + * 2N = 6 2 * (turbo mode to run mode ratio) + * + * In CCLKCFG: + * B = 1 Fast bus mode + * HT = 0 Half-Turbo mode + * T = 1 Turbo mode + * + * For now, just support some of the combinations in table 3-7 of + * PXA27x Processor Family Developer's Manual to simplify frequency + * change sequences. + */ +#define PXA27x_CCCR(A, L, N2) (A << 25 | N2 << 7 | L) +#define CCLKCFG2(B, HT, T) \ + (CCLKCFG_FCS | \ + ((B) ? CCLKCFG_FASTBUS : 0) | \ + ((HT) ? CCLKCFG_HALFTURBO : 0) | \ + ((T) ? CCLKCFG_TURBO : 0)) + +static pxa_freqs_t pxa27x_freqs[] = { + {104000, 104000, PXA27x_CCCR(1, 8, 2), 0, CCLKCFG2(1, 0, 1), 900000, 1705000 }, + {156000, 104000, PXA27x_CCCR(1, 8, 3), 0, CCLKCFG2(1, 0, 1), 1000000, 1705000 }, + {208000, 208000, PXA27x_CCCR(0, 16, 2), 1, CCLKCFG2(0, 0, 1), 1180000, 1705000 }, + {312000, 208000, PXA27x_CCCR(1, 16, 3), 1, CCLKCFG2(1, 0, 1), 1250000, 1705000 }, + {416000, 208000, PXA27x_CCCR(1, 16, 4), 1, CCLKCFG2(1, 0, 1), 1350000, 1705000 }, + {520000, 208000, PXA27x_CCCR(1, 16, 5), 1, CCLKCFG2(1, 0, 1), 1450000, 1705000 }, + {624000, 208000, PXA27x_CCCR(1, 16, 6), 1, CCLKCFG2(1, 0, 1), 1550000, 1705000 } +}; + +#define NUM_PXA27x_FREQS ARRAY_SIZE(pxa27x_freqs) +static struct cpufreq_frequency_table + pxa27x_freq_table[NUM_PXA27x_FREQS+1]; + +extern unsigned get_clk_frequency_khz(int info); + +#ifdef CONFIG_REGULATOR + +static int pxa_cpufreq_change_voltage(pxa_freqs_t *pxa_freq) +{ + int ret = 0; + int vmin, vmax; + + if (!cpu_is_pxa27x()) + return 0; + + vmin = pxa_freq->vmin; + vmax = pxa_freq->vmax; + if ((vmin == -1) || (vmax == -1)) + return 0; + + ret = regulator_set_voltage(vcc_core, vmin, vmax); + if (ret) + pr_err("cpufreq: Failed to set vcc_core in [%dmV..%dmV]\n", + vmin, vmax); + return ret; +} + +static __init void pxa_cpufreq_init_voltages(void) +{ + vcc_core = regulator_get(NULL, "vcc_core"); + if (IS_ERR(vcc_core)) { + pr_info("cpufreq: Didn't find vcc_core regulator\n"); + vcc_core = NULL; + } else { + pr_info("cpufreq: Found vcc_core regulator\n"); + } +} +#else +static int pxa_cpufreq_change_voltage(pxa_freqs_t *pxa_freq) +{ + return 0; +} + +static __init void pxa_cpufreq_init_voltages(void) { } +#endif + +static void find_freq_tables(struct cpufreq_frequency_table **freq_table, + pxa_freqs_t **pxa_freqs) +{ + if (cpu_is_pxa25x()) { + if (!pxa255_turbo_table) { + *pxa_freqs = pxa255_run_freqs; + *freq_table = pxa255_run_freq_table; + } else { + *pxa_freqs = pxa255_turbo_freqs; + *freq_table = pxa255_turbo_freq_table; + } + } else if (cpu_is_pxa27x()) { + *pxa_freqs = pxa27x_freqs; + *freq_table = pxa27x_freq_table; + } else { + BUG(); + } +} + +static void pxa27x_guess_max_freq(void) +{ + if (!pxa27x_maxfreq) { + pxa27x_maxfreq = 416000; + printk(KERN_INFO "PXA CPU 27x max frequency not defined " + "(pxa27x_maxfreq), assuming pxa271 with %dkHz maxfreq\n", + pxa27x_maxfreq); + } else { + pxa27x_maxfreq *= 1000; + } +} + +static void init_sdram_rows(void) +{ + uint32_t mdcnfg = __raw_readl(MDCNFG); + unsigned int drac2 = 0, drac0 = 0; + + if (mdcnfg & (MDCNFG_DE2 | MDCNFG_DE3)) + drac2 = MDCNFG_DRAC2(mdcnfg); + + if (mdcnfg & (MDCNFG_DE0 | MDCNFG_DE1)) + drac0 = MDCNFG_DRAC0(mdcnfg); + + sdram_rows = 1 << (11 + max(drac0, drac2)); +} + +static u32 mdrefr_dri(unsigned int freq) +{ + u32 interval = freq * SDRAM_TREF / sdram_rows; + + return (interval - (cpu_is_pxa27x() ? 31 : 0)) / 32; +} + +/* find a valid frequency point */ +static int pxa_verify_policy(struct cpufreq_policy *policy) +{ + struct cpufreq_frequency_table *pxa_freqs_table; + pxa_freqs_t *pxa_freqs; + int ret; + + find_freq_tables(&pxa_freqs_table, &pxa_freqs); + ret = cpufreq_frequency_table_verify(policy, pxa_freqs_table); + + if (freq_debug) + pr_debug("Verified CPU policy: %dKhz min to %dKhz max\n", + policy->min, policy->max); + + return ret; +} + +static unsigned int pxa_cpufreq_get(unsigned int cpu) +{ + return get_clk_frequency_khz(0); +} + +static int pxa_set_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct cpufreq_frequency_table *pxa_freqs_table; + pxa_freqs_t *pxa_freq_settings; + struct cpufreq_freqs freqs; + unsigned int idx; + unsigned long flags; + unsigned int new_freq_cpu, new_freq_mem; + unsigned int unused, preset_mdrefr, postset_mdrefr, cclkcfg; + int ret = 0; + + /* Get the current policy */ + find_freq_tables(&pxa_freqs_table, &pxa_freq_settings); + + /* Lookup the next frequency */ + if (cpufreq_frequency_table_target(policy, pxa_freqs_table, + target_freq, relation, &idx)) { + return -EINVAL; + } + + new_freq_cpu = pxa_freq_settings[idx].khz; + new_freq_mem = pxa_freq_settings[idx].membus; + freqs.old = policy->cur; + freqs.new = new_freq_cpu; + + if (freq_debug) + pr_debug("Changing CPU frequency to %d Mhz, (SDRAM %d Mhz)\n", + freqs.new / 1000, (pxa_freq_settings[idx].div2) ? + (new_freq_mem / 2000) : (new_freq_mem / 1000)); + + if (vcc_core && freqs.new > freqs.old) + ret = pxa_cpufreq_change_voltage(&pxa_freq_settings[idx]); + if (ret) + return ret; + /* + * Tell everyone what we're about to do... + * you should add a notify client with any platform specific + * Vcc changing capability + */ + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + /* Calculate the next MDREFR. If we're slowing down the SDRAM clock + * we need to preset the smaller DRI before the change. If we're + * speeding up we need to set the larger DRI value after the change. + */ + preset_mdrefr = postset_mdrefr = __raw_readl(MDREFR); + if ((preset_mdrefr & MDREFR_DRI_MASK) > mdrefr_dri(new_freq_mem)) { + preset_mdrefr = (preset_mdrefr & ~MDREFR_DRI_MASK); + preset_mdrefr |= mdrefr_dri(new_freq_mem); + } + postset_mdrefr = + (postset_mdrefr & ~MDREFR_DRI_MASK) | mdrefr_dri(new_freq_mem); + + /* If we're dividing the memory clock by two for the SDRAM clock, this + * must be set prior to the change. Clearing the divide must be done + * after the change. + */ + if (pxa_freq_settings[idx].div2) { + preset_mdrefr |= MDREFR_DB2_MASK; + postset_mdrefr |= MDREFR_DB2_MASK; + } else { + postset_mdrefr &= ~MDREFR_DB2_MASK; + } + + local_irq_save(flags); + + /* Set new the CCCR and prepare CCLKCFG */ + CCCR = pxa_freq_settings[idx].cccr; + cclkcfg = pxa_freq_settings[idx].cclkcfg; + + asm volatile(" \n\ + ldr r4, [%1] /* load MDREFR */ \n\ + b 2f \n\ + .align 5 \n\ +1: \n\ + str %3, [%1] /* preset the MDREFR */ \n\ + mcr p14, 0, %2, c6, c0, 0 /* set CCLKCFG[FCS] */ \n\ + str %4, [%1] /* postset the MDREFR */ \n\ + \n\ + b 3f \n\ +2: b 1b \n\ +3: nop \n\ + " + : "=&r" (unused) + : "r" (MDREFR), "r" (cclkcfg), + "r" (preset_mdrefr), "r" (postset_mdrefr) + : "r4", "r5"); + local_irq_restore(flags); + + /* + * Tell everyone what we've just done... + * you should add a notify client with any platform specific + * SDRAM refresh timer adjustments + */ + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + /* + * Even if voltage setting fails, we don't report it, as the frequency + * change succeeded. The voltage reduction is not a critical failure, + * only power savings will suffer from this. + * + * Note: if the voltage change fails, and a return value is returned, a + * bug is triggered (seems a deadlock). Should anybody find out where, + * the "return 0" should become a "return ret". + */ + if (vcc_core && freqs.new < freqs.old) + ret = pxa_cpufreq_change_voltage(&pxa_freq_settings[idx]); + + return 0; +} + +static int pxa_cpufreq_init(struct cpufreq_policy *policy) +{ + int i; + unsigned int freq; + struct cpufreq_frequency_table *pxa255_freq_table; + pxa_freqs_t *pxa255_freqs; + + /* try to guess pxa27x cpu */ + if (cpu_is_pxa27x()) + pxa27x_guess_max_freq(); + + pxa_cpufreq_init_voltages(); + + init_sdram_rows(); + + /* set default policy and cpuinfo */ + policy->cpuinfo.transition_latency = 1000; /* FIXME: 1 ms, assumed */ + policy->cur = get_clk_frequency_khz(0); /* current freq */ + policy->min = policy->max = policy->cur; + + /* Generate pxa25x the run cpufreq_frequency_table struct */ + for (i = 0; i < NUM_PXA25x_RUN_FREQS; i++) { + pxa255_run_freq_table[i].frequency = pxa255_run_freqs[i].khz; + pxa255_run_freq_table[i].index = i; + } + pxa255_run_freq_table[i].frequency = CPUFREQ_TABLE_END; + + /* Generate pxa25x the turbo cpufreq_frequency_table struct */ + for (i = 0; i < NUM_PXA25x_TURBO_FREQS; i++) { + pxa255_turbo_freq_table[i].frequency = + pxa255_turbo_freqs[i].khz; + pxa255_turbo_freq_table[i].index = i; + } + pxa255_turbo_freq_table[i].frequency = CPUFREQ_TABLE_END; + + pxa255_turbo_table = !!pxa255_turbo_table; + + /* Generate the pxa27x cpufreq_frequency_table struct */ + for (i = 0; i < NUM_PXA27x_FREQS; i++) { + freq = pxa27x_freqs[i].khz; + if (freq > pxa27x_maxfreq) + break; + pxa27x_freq_table[i].frequency = freq; + pxa27x_freq_table[i].index = i; + } + pxa27x_freq_table[i].index = i; + pxa27x_freq_table[i].frequency = CPUFREQ_TABLE_END; + + /* + * Set the policy's minimum and maximum frequencies from the tables + * just constructed. This sets cpuinfo.mxx_freq, min and max. + */ + if (cpu_is_pxa25x()) { + find_freq_tables(&pxa255_freq_table, &pxa255_freqs); + pr_info("PXA255 cpufreq using %s frequency table\n", + pxa255_turbo_table ? "turbo" : "run"); + cpufreq_frequency_table_cpuinfo(policy, pxa255_freq_table); + } + else if (cpu_is_pxa27x()) + cpufreq_frequency_table_cpuinfo(policy, pxa27x_freq_table); + + printk(KERN_INFO "PXA CPU frequency change support initialized\n"); + + return 0; +} + +static struct cpufreq_driver pxa_cpufreq_driver = { + .verify = pxa_verify_policy, + .target = pxa_set_target, + .init = pxa_cpufreq_init, + .get = pxa_cpufreq_get, + .name = "PXA2xx", +}; + +static int __init pxa_cpu_init(void) +{ + int ret = -ENODEV; + if (cpu_is_pxa25x() || cpu_is_pxa27x()) + ret = cpufreq_register_driver(&pxa_cpufreq_driver); + return ret; +} + +static void __exit pxa_cpu_exit(void) +{ + cpufreq_unregister_driver(&pxa_cpufreq_driver); +} + + +MODULE_AUTHOR("Intrinsyc Software Inc."); +MODULE_DESCRIPTION("CPU frequency changing driver for the PXA architecture"); +MODULE_LICENSE("GPL"); +module_init(pxa_cpu_init); +module_exit(pxa_cpu_exit); diff --git a/drivers/cpufreq/pxa3xx-cpufreq.c b/drivers/cpufreq/pxa3xx-cpufreq.c new file mode 100644 index 00000000000..15d60f857ad --- /dev/null +++ b/drivers/cpufreq/pxa3xx-cpufreq.c @@ -0,0 +1,254 @@ +/* + * Copyright (C) 2008 Marvell International Ltd. + * + * 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. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/slab.h> +#include <linux/io.h> + +#include <mach/generic.h> +#include <mach/pxa3xx-regs.h> + +#define HSS_104M (0) +#define HSS_156M (1) +#define HSS_208M (2) +#define HSS_312M (3) + +#define SMCFS_78M (0) +#define SMCFS_104M (2) +#define SMCFS_208M (5) + +#define SFLFS_104M (0) +#define SFLFS_156M (1) +#define SFLFS_208M (2) +#define SFLFS_312M (3) + +#define XSPCLK_156M (0) +#define XSPCLK_NONE (3) + +#define DMCFS_26M (0) +#define DMCFS_260M (3) + +struct pxa3xx_freq_info { + unsigned int cpufreq_mhz; + unsigned int core_xl : 5; + unsigned int core_xn : 3; + unsigned int hss : 2; + unsigned int dmcfs : 2; + unsigned int smcfs : 3; + unsigned int sflfs : 2; + unsigned int df_clkdiv : 3; + + int vcc_core; /* in mV */ + int vcc_sram; /* in mV */ +}; + +#define OP(cpufreq, _xl, _xn, _hss, _dmc, _smc, _sfl, _dfi, vcore, vsram) \ +{ \ + .cpufreq_mhz = cpufreq, \ + .core_xl = _xl, \ + .core_xn = _xn, \ + .hss = HSS_##_hss##M, \ + .dmcfs = DMCFS_##_dmc##M, \ + .smcfs = SMCFS_##_smc##M, \ + .sflfs = SFLFS_##_sfl##M, \ + .df_clkdiv = _dfi, \ + .vcc_core = vcore, \ + .vcc_sram = vsram, \ +} + +static struct pxa3xx_freq_info pxa300_freqs[] = { + /* CPU XL XN HSS DMEM SMEM SRAM DFI VCC_CORE VCC_SRAM */ + OP(104, 8, 1, 104, 260, 78, 104, 3, 1000, 1100), /* 104MHz */ + OP(208, 16, 1, 104, 260, 104, 156, 2, 1000, 1100), /* 208MHz */ + OP(416, 16, 2, 156, 260, 104, 208, 2, 1100, 1200), /* 416MHz */ + OP(624, 24, 2, 208, 260, 208, 312, 3, 1375, 1400), /* 624MHz */ +}; + +static struct pxa3xx_freq_info pxa320_freqs[] = { + /* CPU XL XN HSS DMEM SMEM SRAM DFI VCC_CORE VCC_SRAM */ + OP(104, 8, 1, 104, 260, 78, 104, 3, 1000, 1100), /* 104MHz */ + OP(208, 16, 1, 104, 260, 104, 156, 2, 1000, 1100), /* 208MHz */ + OP(416, 16, 2, 156, 260, 104, 208, 2, 1100, 1200), /* 416MHz */ + OP(624, 24, 2, 208, 260, 208, 312, 3, 1375, 1400), /* 624MHz */ + OP(806, 31, 2, 208, 260, 208, 312, 3, 1400, 1400), /* 806MHz */ +}; + +static unsigned int pxa3xx_freqs_num; +static struct pxa3xx_freq_info *pxa3xx_freqs; +static struct cpufreq_frequency_table *pxa3xx_freqs_table; + +static int setup_freqs_table(struct cpufreq_policy *policy, + struct pxa3xx_freq_info *freqs, int num) +{ + struct cpufreq_frequency_table *table; + int i; + + table = kzalloc((num + 1) * sizeof(*table), GFP_KERNEL); + if (table == NULL) + return -ENOMEM; + + for (i = 0; i < num; i++) { + table[i].index = i; + table[i].frequency = freqs[i].cpufreq_mhz * 1000; + } + table[num].index = i; + table[num].frequency = CPUFREQ_TABLE_END; + + pxa3xx_freqs = freqs; + pxa3xx_freqs_num = num; + pxa3xx_freqs_table = table; + + return cpufreq_frequency_table_cpuinfo(policy, table); +} + +static void __update_core_freq(struct pxa3xx_freq_info *info) +{ + uint32_t mask = ACCR_XN_MASK | ACCR_XL_MASK; + uint32_t accr = ACCR; + uint32_t xclkcfg; + + accr &= ~(ACCR_XN_MASK | ACCR_XL_MASK | ACCR_XSPCLK_MASK); + accr |= ACCR_XN(info->core_xn) | ACCR_XL(info->core_xl); + + /* No clock until core PLL is re-locked */ + accr |= ACCR_XSPCLK(XSPCLK_NONE); + + xclkcfg = (info->core_xn == 2) ? 0x3 : 0x2; /* turbo bit */ + + ACCR = accr; + __asm__("mcr p14, 0, %0, c6, c0, 0\n" : : "r"(xclkcfg)); + + while ((ACSR & mask) != (accr & mask)) + cpu_relax(); +} + +static void __update_bus_freq(struct pxa3xx_freq_info *info) +{ + uint32_t mask; + uint32_t accr = ACCR; + + mask = ACCR_SMCFS_MASK | ACCR_SFLFS_MASK | ACCR_HSS_MASK | + ACCR_DMCFS_MASK; + + accr &= ~mask; + accr |= ACCR_SMCFS(info->smcfs) | ACCR_SFLFS(info->sflfs) | + ACCR_HSS(info->hss) | ACCR_DMCFS(info->dmcfs); + + ACCR = accr; + + while ((ACSR & mask) != (accr & mask)) + cpu_relax(); +} + +static int pxa3xx_cpufreq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, pxa3xx_freqs_table); +} + +static unsigned int pxa3xx_cpufreq_get(unsigned int cpu) +{ + return pxa3xx_get_clk_frequency_khz(0); +} + +static int pxa3xx_cpufreq_set(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct pxa3xx_freq_info *next; + struct cpufreq_freqs freqs; + unsigned long flags; + int idx; + + if (policy->cpu != 0) + return -EINVAL; + + /* Lookup the next frequency */ + if (cpufreq_frequency_table_target(policy, pxa3xx_freqs_table, + target_freq, relation, &idx)) + return -EINVAL; + + next = &pxa3xx_freqs[idx]; + + freqs.old = policy->cur; + freqs.new = next->cpufreq_mhz * 1000; + + pr_debug("CPU frequency from %d MHz to %d MHz%s\n", + freqs.old / 1000, freqs.new / 1000, + (freqs.old == freqs.new) ? " (skipped)" : ""); + + if (freqs.old == target_freq) + return 0; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + local_irq_save(flags); + __update_core_freq(next); + __update_bus_freq(next); + local_irq_restore(flags); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static int pxa3xx_cpufreq_init(struct cpufreq_policy *policy) +{ + int ret = -EINVAL; + + /* set default policy and cpuinfo */ + policy->cpuinfo.min_freq = 104000; + policy->cpuinfo.max_freq = (cpu_is_pxa320()) ? 806000 : 624000; + policy->cpuinfo.transition_latency = 1000; /* FIXME: 1 ms, assumed */ + policy->max = pxa3xx_get_clk_frequency_khz(0); + policy->cur = policy->min = policy->max; + + if (cpu_is_pxa300() || cpu_is_pxa310()) + ret = setup_freqs_table(policy, ARRAY_AND_SIZE(pxa300_freqs)); + + if (cpu_is_pxa320()) + ret = setup_freqs_table(policy, ARRAY_AND_SIZE(pxa320_freqs)); + + if (ret) { + pr_err("failed to setup frequency table\n"); + return ret; + } + + pr_info("CPUFREQ support for PXA3xx initialized\n"); + return 0; +} + +static struct cpufreq_driver pxa3xx_cpufreq_driver = { + .verify = pxa3xx_cpufreq_verify, + .target = pxa3xx_cpufreq_set, + .init = pxa3xx_cpufreq_init, + .get = pxa3xx_cpufreq_get, + .name = "pxa3xx-cpufreq", +}; + +static int __init cpufreq_init(void) +{ + if (cpu_is_pxa3xx()) + return cpufreq_register_driver(&pxa3xx_cpufreq_driver); + + return 0; +} +module_init(cpufreq_init); + +static void __exit cpufreq_exit(void) +{ + cpufreq_unregister_driver(&pxa3xx_cpufreq_driver); +} +module_exit(cpufreq_exit); + +MODULE_DESCRIPTION("CPU frequency scaling driver for PXA3xx"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/s3c2416-cpufreq.c b/drivers/cpufreq/s3c2416-cpufreq.c index bcc053bc02c..4f1881eee3f 100644 --- a/drivers/cpufreq/s3c2416-cpufreq.c +++ b/drivers/cpufreq/s3c2416-cpufreq.c @@ -256,7 +256,6 @@ static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy, goto out; } - freqs.cpu = 0; freqs.flags = 0; freqs.old = s3c_freq->is_dvs ? FREQ_DVS : clk_get_rate(s3c_freq->armclk) / 1000; @@ -274,7 +273,7 @@ static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy, if (!to_dvs && freqs.old == freqs.new) goto out; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); if (to_dvs) { pr_debug("cpufreq: enter dvs\n"); @@ -287,7 +286,7 @@ static int s3c2416_cpufreq_set_target(struct cpufreq_policy *policy, ret = s3c2416_cpufreq_set_armdiv(s3c_freq, freqs.new); } - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); out: mutex_unlock(&cpufreq_lock); diff --git a/drivers/cpufreq/s3c64xx-cpufreq.c b/drivers/cpufreq/s3c64xx-cpufreq.c index 6f9490b3c35..27cacb52479 100644 --- a/drivers/cpufreq/s3c64xx-cpufreq.c +++ b/drivers/cpufreq/s3c64xx-cpufreq.c @@ -84,7 +84,6 @@ static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, if (ret != 0) return ret; - freqs.cpu = 0; freqs.old = clk_get_rate(armclk) / 1000; freqs.new = s3c64xx_freq_table[i].frequency; freqs.flags = 0; @@ -95,7 +94,7 @@ static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, pr_debug("Transition %d-%dkHz\n", freqs.old, freqs.new); - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); #ifdef CONFIG_REGULATOR if (vddarm && freqs.new > freqs.old) { @@ -117,7 +116,7 @@ static int s3c64xx_cpufreq_set_target(struct cpufreq_policy *policy, goto err; } - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); #ifdef CONFIG_REGULATOR if (vddarm && freqs.new < freqs.old) { @@ -141,7 +140,7 @@ err_clk: if (clk_set_rate(armclk, freqs.old * 1000) < 0) pr_err("Failed to restore original clock rate\n"); err: - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return ret; } diff --git a/drivers/cpufreq/s5pv210-cpufreq.c b/drivers/cpufreq/s5pv210-cpufreq.c index a484aaea980..5c775707379 100644 --- a/drivers/cpufreq/s5pv210-cpufreq.c +++ b/drivers/cpufreq/s5pv210-cpufreq.c @@ -229,7 +229,6 @@ static int s5pv210_target(struct cpufreq_policy *policy, } freqs.new = s5pv210_freq_table[index].frequency; - freqs.cpu = 0; if (freqs.new == freqs.old) goto exit; @@ -256,7 +255,7 @@ static int s5pv210_target(struct cpufreq_policy *policy, goto exit; } - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); /* Check if there need to change PLL */ if ((index == L0) || (priv_index == L0)) @@ -468,7 +467,7 @@ static int s5pv210_target(struct cpufreq_policy *policy, } } - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); if (freqs.new < freqs.old) { regulator_set_voltage(int_regulator, diff --git a/drivers/cpufreq/sa1100-cpufreq.c b/drivers/cpufreq/sa1100-cpufreq.c new file mode 100644 index 00000000000..cff18e87ca5 --- /dev/null +++ b/drivers/cpufreq/sa1100-cpufreq.c @@ -0,0 +1,247 @@ +/* + * cpu-sa1100.c: clock scaling for the SA1100 + * + * Copyright (C) 2000 2001, The Delft University of Technology + * + * Authors: + * - Johan Pouwelse (J.A.Pouwelse@its.tudelft.nl): initial version + * - Erik Mouw (J.A.K.Mouw@its.tudelft.nl): + * - major rewrite for linux-2.3.99 + * - rewritten for the more generic power management scheme in + * linux-2.4.5-rmk1 + * + * This software has been developed while working on the LART + * computing board (http://www.lartmaker.nl/), which is + * sponsored by the Mobile Multi-media Communications + * (http://www.mobimedia.org/) and Ubiquitous Communications + * (http://www.ubicom.tudelft.nl/) projects. + * + * The authors can be reached at: + * + * Erik Mouw + * Information and Communication Theory Group + * Faculty of Information Technology and Systems + * Delft University of Technology + * P.O. Box 5031 + * 2600 GA Delft + * The Netherlands + * + * + * 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + * + * Theory of operations + * ==================== + * + * Clock scaling can be used to lower the power consumption of the CPU + * core. This will give you a somewhat longer running time. + * + * The SA-1100 has a single register to change the core clock speed: + * + * PPCR 0x90020014 PLL config + * + * However, the DRAM timings are closely related to the core clock + * speed, so we need to change these, too. The used registers are: + * + * MDCNFG 0xA0000000 DRAM config + * MDCAS0 0xA0000004 Access waveform + * MDCAS1 0xA0000008 Access waveform + * MDCAS2 0xA000000C Access waveform + * + * Care must be taken to change the DRAM parameters the correct way, + * because otherwise the DRAM becomes unusable and the kernel will + * crash. + * + * The simple solution to avoid a kernel crash is to put the actual + * clock change in ROM and jump to that code from the kernel. The main + * disadvantage is that the ROM has to be modified, which is not + * possible on all SA-1100 platforms. Another disadvantage is that + * jumping to ROM makes clock switching unnecessary complicated. + * + * The idea behind this driver is that the memory configuration can be + * changed while running from DRAM (even with interrupts turned on!) + * as long as all re-configuration steps yield a valid DRAM + * configuration. The advantages are clear: it will run on all SA-1100 + * platforms, and the code is very simple. + * + * If you really want to understand what is going on in + * sa1100_update_dram_timings(), you'll have to read sections 8.2, + * 9.5.7.3, and 10.2 from the "Intel StrongARM SA-1100 Microprocessor + * Developers Manual" (available for free from Intel). + * + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/cpufreq.h> +#include <linux/io.h> + +#include <asm/cputype.h> + +#include <mach/generic.h> +#include <mach/hardware.h> + +struct sa1100_dram_regs { + int speed; + u32 mdcnfg; + u32 mdcas0; + u32 mdcas1; + u32 mdcas2; +}; + + +static struct cpufreq_driver sa1100_driver; + +static struct sa1100_dram_regs sa1100_dram_settings[] = { + /*speed, mdcnfg, mdcas0, mdcas1, mdcas2, clock freq */ + { 59000, 0x00dc88a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 59.0 MHz */ + { 73700, 0x011490a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 73.7 MHz */ + { 88500, 0x014e90a3, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 88.5 MHz */ + {103200, 0x01889923, 0xcccccccf, 0xfffffffc, 0xffffffff},/* 103.2 MHz */ + {118000, 0x01c29923, 0x9999998f, 0xfffffff9, 0xffffffff},/* 118.0 MHz */ + {132700, 0x01fb2123, 0x9999998f, 0xfffffff9, 0xffffffff},/* 132.7 MHz */ + {147500, 0x02352123, 0x3333330f, 0xfffffff3, 0xffffffff},/* 147.5 MHz */ + {162200, 0x026b29a3, 0x38e38e1f, 0xfff8e38e, 0xffffffff},/* 162.2 MHz */ + {176900, 0x02a329a3, 0x71c71c1f, 0xfff1c71c, 0xffffffff},/* 176.9 MHz */ + {191700, 0x02dd31a3, 0xe38e383f, 0xffe38e38, 0xffffffff},/* 191.7 MHz */ + {206400, 0x03153223, 0xc71c703f, 0xffc71c71, 0xffffffff},/* 206.4 MHz */ + {221200, 0x034fba23, 0xc71c703f, 0xffc71c71, 0xffffffff},/* 221.2 MHz */ + {235900, 0x03853a23, 0xe1e1e07f, 0xe1e1e1e1, 0xffffffe1},/* 235.9 MHz */ + {250700, 0x03bf3aa3, 0xc3c3c07f, 0xc3c3c3c3, 0xffffffc3},/* 250.7 MHz */ + {265400, 0x03f7c2a3, 0xc3c3c07f, 0xc3c3c3c3, 0xffffffc3},/* 265.4 MHz */ + {280200, 0x0431c2a3, 0x878780ff, 0x87878787, 0xffffff87},/* 280.2 MHz */ + { 0, 0, 0, 0, 0 } /* last entry */ +}; + +static void sa1100_update_dram_timings(int current_speed, int new_speed) +{ + struct sa1100_dram_regs *settings = sa1100_dram_settings; + + /* find speed */ + while (settings->speed != 0) { + if (new_speed == settings->speed) + break; + + settings++; + } + + if (settings->speed == 0) { + panic("%s: couldn't find dram setting for speed %d\n", + __func__, new_speed); + } + + /* No risk, no fun: run with interrupts on! */ + if (new_speed > current_speed) { + /* We're going FASTER, so first relax the memory + * timings before changing the core frequency + */ + + /* Half the memory access clock */ + MDCNFG |= MDCNFG_CDB2; + + /* The order of these statements IS important, keep 8 + * pulses!! + */ + MDCAS2 = settings->mdcas2; + MDCAS1 = settings->mdcas1; + MDCAS0 = settings->mdcas0; + MDCNFG = settings->mdcnfg; + } else { + /* We're going SLOWER: first decrease the core + * frequency and then tighten the memory settings. + */ + + /* Half the memory access clock */ + MDCNFG |= MDCNFG_CDB2; + + /* The order of these statements IS important, keep 8 + * pulses!! + */ + MDCAS0 = settings->mdcas0; + MDCAS1 = settings->mdcas1; + MDCAS2 = settings->mdcas2; + MDCNFG = settings->mdcnfg; + } +} + +static int sa1100_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int cur = sa11x0_getspeed(0); + unsigned int new_ppcr; + struct cpufreq_freqs freqs; + + new_ppcr = sa11x0_freq_to_ppcr(target_freq); + switch (relation) { + case CPUFREQ_RELATION_L: + if (sa11x0_ppcr_to_freq(new_ppcr) > policy->max) + new_ppcr--; + break; + case CPUFREQ_RELATION_H: + if ((sa11x0_ppcr_to_freq(new_ppcr) > target_freq) && + (sa11x0_ppcr_to_freq(new_ppcr - 1) >= policy->min)) + new_ppcr--; + break; + } + + freqs.old = cur; + freqs.new = sa11x0_ppcr_to_freq(new_ppcr); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + if (freqs.new > cur) + sa1100_update_dram_timings(cur, freqs.new); + + PPCR = new_ppcr; + + if (freqs.new < cur) + sa1100_update_dram_timings(cur, freqs.new); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static int __init sa1100_cpu_init(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + policy->cur = policy->min = policy->max = sa11x0_getspeed(0); + policy->cpuinfo.min_freq = 59000; + policy->cpuinfo.max_freq = 287000; + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + return 0; +} + +static struct cpufreq_driver sa1100_driver __refdata = { + .flags = CPUFREQ_STICKY, + .verify = sa11x0_verify_speed, + .target = sa1100_target, + .get = sa11x0_getspeed, + .init = sa1100_cpu_init, + .name = "sa1100", +}; + +static int __init sa1100_dram_init(void) +{ + if (cpu_is_sa1100()) + return cpufreq_register_driver(&sa1100_driver); + else + return -ENODEV; +} + +arch_initcall(sa1100_dram_init); diff --git a/drivers/cpufreq/sa1110-cpufreq.c b/drivers/cpufreq/sa1110-cpufreq.c new file mode 100644 index 00000000000..39c90b6f428 --- /dev/null +++ b/drivers/cpufreq/sa1110-cpufreq.c @@ -0,0 +1,406 @@ +/* + * linux/arch/arm/mach-sa1100/cpu-sa1110.c + * + * Copyright (C) 2001 Russell King + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * Note: there are two erratas that apply to the SA1110 here: + * 7 - SDRAM auto-power-up failure (rev A0) + * 13 - Corruption of internal register reads/writes following + * SDRAM reads (rev A0, B0, B1) + * + * We ignore rev. A0 and B0 devices; I don't think they're worth supporting. + * + * The SDRAM type can be passed on the command line as cpu_sa1110.sdram=type + */ +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/io.h> +#include <linux/kernel.h> +#include <linux/moduleparam.h> +#include <linux/types.h> + +#include <asm/cputype.h> +#include <asm/mach-types.h> + +#include <mach/generic.h> +#include <mach/hardware.h> + +#undef DEBUG + +struct sdram_params { + const char name[20]; + u_char rows; /* bits */ + u_char cas_latency; /* cycles */ + u_char tck; /* clock cycle time (ns) */ + u_char trcd; /* activate to r/w (ns) */ + u_char trp; /* precharge to activate (ns) */ + u_char twr; /* write recovery time (ns) */ + u_short refresh; /* refresh time for array (us) */ +}; + +struct sdram_info { + u_int mdcnfg; + u_int mdrefr; + u_int mdcas[3]; +}; + +static struct sdram_params sdram_tbl[] __initdata = { + { /* Toshiba TC59SM716 CL2 */ + .name = "TC59SM716-CL2", + .rows = 12, + .tck = 10, + .trcd = 20, + .trp = 20, + .twr = 10, + .refresh = 64000, + .cas_latency = 2, + }, { /* Toshiba TC59SM716 CL3 */ + .name = "TC59SM716-CL3", + .rows = 12, + .tck = 8, + .trcd = 20, + .trp = 20, + .twr = 8, + .refresh = 64000, + .cas_latency = 3, + }, { /* Samsung K4S641632D TC75 */ + .name = "K4S641632D", + .rows = 14, + .tck = 9, + .trcd = 27, + .trp = 20, + .twr = 9, + .refresh = 64000, + .cas_latency = 3, + }, { /* Samsung K4S281632B-1H */ + .name = "K4S281632B-1H", + .rows = 12, + .tck = 10, + .trp = 20, + .twr = 10, + .refresh = 64000, + .cas_latency = 3, + }, { /* Samsung KM416S4030CT */ + .name = "KM416S4030CT", + .rows = 13, + .tck = 8, + .trcd = 24, /* 3 CLKs */ + .trp = 24, /* 3 CLKs */ + .twr = 16, /* Trdl: 2 CLKs */ + .refresh = 64000, + .cas_latency = 3, + }, { /* Winbond W982516AH75L CL3 */ + .name = "W982516AH75L", + .rows = 16, + .tck = 8, + .trcd = 20, + .trp = 20, + .twr = 8, + .refresh = 64000, + .cas_latency = 3, + }, { /* Micron MT48LC8M16A2TG-75 */ + .name = "MT48LC8M16A2TG-75", + .rows = 12, + .tck = 8, + .trcd = 20, + .trp = 20, + .twr = 8, + .refresh = 64000, + .cas_latency = 3, + }, +}; + +static struct sdram_params sdram_params; + +/* + * Given a period in ns and frequency in khz, calculate the number of + * cycles of frequency in period. Note that we round up to the next + * cycle, even if we are only slightly over. + */ +static inline u_int ns_to_cycles(u_int ns, u_int khz) +{ + return (ns * khz + 999999) / 1000000; +} + +/* + * Create the MDCAS register bit pattern. + */ +static inline void set_mdcas(u_int *mdcas, int delayed, u_int rcd) +{ + u_int shift; + + rcd = 2 * rcd - 1; + shift = delayed + 1 + rcd; + + mdcas[0] = (1 << rcd) - 1; + mdcas[0] |= 0x55555555 << shift; + mdcas[1] = mdcas[2] = 0x55555555 << (shift & 1); +} + +static void +sdram_calculate_timing(struct sdram_info *sd, u_int cpu_khz, + struct sdram_params *sdram) +{ + u_int mem_khz, sd_khz, trp, twr; + + mem_khz = cpu_khz / 2; + sd_khz = mem_khz; + + /* + * If SDCLK would invalidate the SDRAM timings, + * run SDCLK at half speed. + * + * CPU steppings prior to B2 must either run the memory at + * half speed or use delayed read latching (errata 13). + */ + if ((ns_to_cycles(sdram->tck, sd_khz) > 1) || + (CPU_REVISION < CPU_SA1110_B2 && sd_khz < 62000)) + sd_khz /= 2; + + sd->mdcnfg = MDCNFG & 0x007f007f; + + twr = ns_to_cycles(sdram->twr, mem_khz); + + /* trp should always be >1 */ + trp = ns_to_cycles(sdram->trp, mem_khz) - 1; + if (trp < 1) + trp = 1; + + sd->mdcnfg |= trp << 8; + sd->mdcnfg |= trp << 24; + sd->mdcnfg |= sdram->cas_latency << 12; + sd->mdcnfg |= sdram->cas_latency << 28; + sd->mdcnfg |= twr << 14; + sd->mdcnfg |= twr << 30; + + sd->mdrefr = MDREFR & 0xffbffff0; + sd->mdrefr |= 7; + + if (sd_khz != mem_khz) + sd->mdrefr |= MDREFR_K1DB2; + + /* initial number of '1's in MDCAS + 1 */ + set_mdcas(sd->mdcas, sd_khz >= 62000, + ns_to_cycles(sdram->trcd, mem_khz)); + +#ifdef DEBUG + printk(KERN_DEBUG "MDCNFG: %08x MDREFR: %08x MDCAS0: %08x MDCAS1: %08x MDCAS2: %08x\n", + sd->mdcnfg, sd->mdrefr, sd->mdcas[0], sd->mdcas[1], + sd->mdcas[2]); +#endif +} + +/* + * Set the SDRAM refresh rate. + */ +static inline void sdram_set_refresh(u_int dri) +{ + MDREFR = (MDREFR & 0xffff000f) | (dri << 4); + (void) MDREFR; +} + +/* + * Update the refresh period. We do this such that we always refresh + * the SDRAMs within their permissible period. The refresh period is + * always a multiple of the memory clock (fixed at cpu_clock / 2). + * + * FIXME: we don't currently take account of burst accesses here, + * but neither do Intels DM nor Angel. + */ +static void +sdram_update_refresh(u_int cpu_khz, struct sdram_params *sdram) +{ + u_int ns_row = (sdram->refresh * 1000) >> sdram->rows; + u_int dri = ns_to_cycles(ns_row, cpu_khz / 2) / 32; + +#ifdef DEBUG + mdelay(250); + printk(KERN_DEBUG "new dri value = %d\n", dri); +#endif + + sdram_set_refresh(dri); +} + +/* + * Ok, set the CPU frequency. + */ +static int sa1110_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + struct sdram_params *sdram = &sdram_params; + struct cpufreq_freqs freqs; + struct sdram_info sd; + unsigned long flags; + unsigned int ppcr, unused; + + switch (relation) { + case CPUFREQ_RELATION_L: + ppcr = sa11x0_freq_to_ppcr(target_freq); + if (sa11x0_ppcr_to_freq(ppcr) > policy->max) + ppcr--; + break; + case CPUFREQ_RELATION_H: + ppcr = sa11x0_freq_to_ppcr(target_freq); + if (ppcr && (sa11x0_ppcr_to_freq(ppcr) > target_freq) && + (sa11x0_ppcr_to_freq(ppcr-1) >= policy->min)) + ppcr--; + break; + default: + return -EINVAL; + } + + freqs.old = sa11x0_getspeed(0); + freqs.new = sa11x0_ppcr_to_freq(ppcr); + + sdram_calculate_timing(&sd, freqs.new, sdram); + +#if 0 + /* + * These values are wrong according to the SA1110 documentation + * and errata, but they seem to work. Need to get a storage + * scope on to the SDRAM signals to work out why. + */ + if (policy->max < 147500) { + sd.mdrefr |= MDREFR_K1DB2; + sd.mdcas[0] = 0xaaaaaa7f; + } else { + sd.mdrefr &= ~MDREFR_K1DB2; + sd.mdcas[0] = 0xaaaaaa9f; + } + sd.mdcas[1] = 0xaaaaaaaa; + sd.mdcas[2] = 0xaaaaaaaa; +#endif + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + /* + * The clock could be going away for some time. Set the SDRAMs + * to refresh rapidly (every 64 memory clock cycles). To get + * through the whole array, we need to wait 262144 mclk cycles. + * We wait 20ms to be safe. + */ + sdram_set_refresh(2); + if (!irqs_disabled()) + msleep(20); + else + mdelay(20); + + /* + * Reprogram the DRAM timings with interrupts disabled, and + * ensure that we are doing this within a complete cache line. + * This means that we won't access SDRAM for the duration of + * the programming. + */ + local_irq_save(flags); + asm("mcr p15, 0, %0, c7, c10, 4" : : "r" (0)); + udelay(10); + __asm__ __volatile__("\n\ + b 2f \n\ + .align 5 \n\ +1: str %3, [%1, #0] @ MDCNFG \n\ + str %4, [%1, #28] @ MDREFR \n\ + str %5, [%1, #4] @ MDCAS0 \n\ + str %6, [%1, #8] @ MDCAS1 \n\ + str %7, [%1, #12] @ MDCAS2 \n\ + str %8, [%2, #0] @ PPCR \n\ + ldr %0, [%1, #0] \n\ + b 3f \n\ +2: b 1b \n\ +3: nop \n\ + nop" + : "=&r" (unused) + : "r" (&MDCNFG), "r" (&PPCR), "0" (sd.mdcnfg), + "r" (sd.mdrefr), "r" (sd.mdcas[0]), + "r" (sd.mdcas[1]), "r" (sd.mdcas[2]), "r" (ppcr)); + local_irq_restore(flags); + + /* + * Now, return the SDRAM refresh back to normal. + */ + sdram_update_refresh(freqs.new, sdram); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static int __init sa1110_cpu_init(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + policy->cur = policy->min = policy->max = sa11x0_getspeed(0); + policy->cpuinfo.min_freq = 59000; + policy->cpuinfo.max_freq = 287000; + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + return 0; +} + +/* sa1110_driver needs __refdata because it must remain after init registers + * it with cpufreq_register_driver() */ +static struct cpufreq_driver sa1110_driver __refdata = { + .flags = CPUFREQ_STICKY, + .verify = sa11x0_verify_speed, + .target = sa1110_target, + .get = sa11x0_getspeed, + .init = sa1110_cpu_init, + .name = "sa1110", +}; + +static struct sdram_params *sa1110_find_sdram(const char *name) +{ + struct sdram_params *sdram; + + for (sdram = sdram_tbl; sdram < sdram_tbl + ARRAY_SIZE(sdram_tbl); + sdram++) + if (strcmp(name, sdram->name) == 0) + return sdram; + + return NULL; +} + +static char sdram_name[16]; + +static int __init sa1110_clk_init(void) +{ + struct sdram_params *sdram; + const char *name = sdram_name; + + if (!cpu_is_sa1110()) + return -ENODEV; + + if (!name[0]) { + if (machine_is_assabet()) + name = "TC59SM716-CL3"; + if (machine_is_pt_system3()) + name = "K4S641632D"; + if (machine_is_h3100()) + name = "KM416S4030CT"; + if (machine_is_jornada720()) + name = "K4S281632B-1H"; + if (machine_is_nanoengine()) + name = "MT48LC8M16A2TG-75"; + } + + sdram = sa1110_find_sdram(name); + if (sdram) { + printk(KERN_DEBUG "SDRAM: tck: %d trcd: %d trp: %d" + " twr: %d refresh: %d cas_latency: %d\n", + sdram->tck, sdram->trcd, sdram->trp, + sdram->twr, sdram->refresh, sdram->cas_latency); + + memcpy(&sdram_params, sdram, sizeof(sdram_params)); + + return cpufreq_register_driver(&sa1110_driver); + } + + return 0; +} + +module_param_string(sdram, sdram_name, sizeof(sdram_name), 0); +arch_initcall(sa1110_clk_init); diff --git a/drivers/cpufreq/sc520_freq.c b/drivers/cpufreq/sc520_freq.c index e42e073cd9b..f740b134d27 100644 --- a/drivers/cpufreq/sc520_freq.c +++ b/drivers/cpufreq/sc520_freq.c @@ -53,7 +53,8 @@ static unsigned int sc520_freq_get_cpu_frequency(unsigned int cpu) } } -static void sc520_freq_set_cpu_state(unsigned int state) +static void sc520_freq_set_cpu_state(struct cpufreq_policy *policy, + unsigned int state) { struct cpufreq_freqs freqs; @@ -61,9 +62,8 @@ static void sc520_freq_set_cpu_state(unsigned int state) freqs.old = sc520_freq_get_cpu_frequency(0); freqs.new = sc520_freq_table[state].frequency; - freqs.cpu = 0; /* AMD Elan is UP */ - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); pr_debug("attempting to set frequency to %i kHz\n", sc520_freq_table[state].frequency); @@ -75,7 +75,7 @@ static void sc520_freq_set_cpu_state(unsigned int state) local_irq_enable(); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); }; static int sc520_freq_verify(struct cpufreq_policy *policy) @@ -93,7 +93,7 @@ static int sc520_freq_target(struct cpufreq_policy *policy, target_freq, relation, &newstate)) return -EINVAL; - sc520_freq_set_cpu_state(newstate); + sc520_freq_set_cpu_state(policy, newstate); return 0; } diff --git a/drivers/cpufreq/sh-cpufreq.c b/drivers/cpufreq/sh-cpufreq.c new file mode 100644 index 00000000000..73adb64651e --- /dev/null +++ b/drivers/cpufreq/sh-cpufreq.c @@ -0,0 +1,189 @@ +/* + * cpufreq driver for the SuperH processors. + * + * Copyright (C) 2002 - 2012 Paul Mundt + * Copyright (C) 2002 M. R. Brown + * + * Clock framework bits from arch/avr32/mach-at32ap/cpufreq.c + * + * Copyright (C) 2004-2007 Atmel Corporation + * + * This file is subject to the terms and conditions of the GNU General Public + * License. See the file "COPYING" in the main directory of this archive + * for more details. + */ +#define pr_fmt(fmt) "cpufreq: " fmt + +#include <linux/types.h> +#include <linux/cpufreq.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/cpumask.h> +#include <linux/cpu.h> +#include <linux/smp.h> +#include <linux/sched.h> /* set_cpus_allowed() */ +#include <linux/clk.h> +#include <linux/percpu.h> +#include <linux/sh_clk.h> + +static DEFINE_PER_CPU(struct clk, sh_cpuclk); + +static unsigned int sh_cpufreq_get(unsigned int cpu) +{ + return (clk_get_rate(&per_cpu(sh_cpuclk, cpu)) + 500) / 1000; +} + +/* + * Here we notify other drivers of the proposed change and the final change. + */ +static int sh_cpufreq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int cpu = policy->cpu; + struct clk *cpuclk = &per_cpu(sh_cpuclk, cpu); + cpumask_t cpus_allowed; + struct cpufreq_freqs freqs; + struct device *dev; + long freq; + + cpus_allowed = current->cpus_allowed; + set_cpus_allowed_ptr(current, cpumask_of(cpu)); + + BUG_ON(smp_processor_id() != cpu); + + dev = get_cpu_device(cpu); + + /* Convert target_freq from kHz to Hz */ + freq = clk_round_rate(cpuclk, target_freq * 1000); + + if (freq < (policy->min * 1000) || freq > (policy->max * 1000)) + return -EINVAL; + + dev_dbg(dev, "requested frequency %u Hz\n", target_freq * 1000); + + freqs.old = sh_cpufreq_get(cpu); + freqs.new = (freq + 500) / 1000; + freqs.flags = 0; + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + set_cpus_allowed_ptr(current, &cpus_allowed); + clk_set_rate(cpuclk, freq); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + dev_dbg(dev, "set frequency %lu Hz\n", freq); + + return 0; +} + +static int sh_cpufreq_verify(struct cpufreq_policy *policy) +{ + struct clk *cpuclk = &per_cpu(sh_cpuclk, policy->cpu); + struct cpufreq_frequency_table *freq_table; + + freq_table = cpuclk->nr_freqs ? cpuclk->freq_table : NULL; + if (freq_table) + return cpufreq_frequency_table_verify(policy, freq_table); + + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + + policy->min = (clk_round_rate(cpuclk, 1) + 500) / 1000; + policy->max = (clk_round_rate(cpuclk, ~0UL) + 500) / 1000; + + cpufreq_verify_within_limits(policy, policy->cpuinfo.min_freq, + policy->cpuinfo.max_freq); + + return 0; +} + +static int sh_cpufreq_cpu_init(struct cpufreq_policy *policy) +{ + unsigned int cpu = policy->cpu; + struct clk *cpuclk = &per_cpu(sh_cpuclk, cpu); + struct cpufreq_frequency_table *freq_table; + struct device *dev; + + dev = get_cpu_device(cpu); + + cpuclk = clk_get(dev, "cpu_clk"); + if (IS_ERR(cpuclk)) { + dev_err(dev, "couldn't get CPU clk\n"); + return PTR_ERR(cpuclk); + } + + policy->cur = sh_cpufreq_get(cpu); + + freq_table = cpuclk->nr_freqs ? cpuclk->freq_table : NULL; + if (freq_table) { + int result; + + result = cpufreq_frequency_table_cpuinfo(policy, freq_table); + if (!result) + cpufreq_frequency_table_get_attr(freq_table, cpu); + } else { + dev_notice(dev, "no frequency table found, falling back " + "to rate rounding.\n"); + + policy->min = policy->cpuinfo.min_freq = + (clk_round_rate(cpuclk, 1) + 500) / 1000; + policy->max = policy->cpuinfo.max_freq = + (clk_round_rate(cpuclk, ~0UL) + 500) / 1000; + } + + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + + dev_info(dev, "CPU Frequencies - Minimum %u.%03u MHz, " + "Maximum %u.%03u MHz.\n", + policy->min / 1000, policy->min % 1000, + policy->max / 1000, policy->max % 1000); + + return 0; +} + +static int sh_cpufreq_cpu_exit(struct cpufreq_policy *policy) +{ + unsigned int cpu = policy->cpu; + struct clk *cpuclk = &per_cpu(sh_cpuclk, cpu); + + cpufreq_frequency_table_put_attr(cpu); + clk_put(cpuclk); + + return 0; +} + +static struct freq_attr *sh_freq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver sh_cpufreq_driver = { + .owner = THIS_MODULE, + .name = "sh", + .get = sh_cpufreq_get, + .target = sh_cpufreq_target, + .verify = sh_cpufreq_verify, + .init = sh_cpufreq_cpu_init, + .exit = sh_cpufreq_cpu_exit, + .attr = sh_freq_attr, +}; + +static int __init sh_cpufreq_module_init(void) +{ + pr_notice("SuperH CPU frequency driver.\n"); + return cpufreq_register_driver(&sh_cpufreq_driver); +} + +static void __exit sh_cpufreq_module_exit(void) +{ + cpufreq_unregister_driver(&sh_cpufreq_driver); +} + +module_init(sh_cpufreq_module_init); +module_exit(sh_cpufreq_module_exit); + +MODULE_AUTHOR("Paul Mundt <lethal@linux-sh.org>"); +MODULE_DESCRIPTION("cpufreq driver for SuperH"); +MODULE_LICENSE("GPL"); diff --git a/drivers/cpufreq/sparc-us2e-cpufreq.c b/drivers/cpufreq/sparc-us2e-cpufreq.c new file mode 100644 index 00000000000..306ae462bba --- /dev/null +++ b/drivers/cpufreq/sparc-us2e-cpufreq.c @@ -0,0 +1,408 @@ +/* us2e_cpufreq.c: UltraSPARC-IIe cpu frequency support + * + * Copyright (C) 2003 David S. Miller (davem@redhat.com) + * + * Many thanks to Dominik Brodowski for fixing up the cpufreq + * infrastructure in order to make this driver easier to implement. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/cpufreq.h> +#include <linux/threads.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/init.h> + +#include <asm/asi.h> +#include <asm/timer.h> + +static struct cpufreq_driver *cpufreq_us2e_driver; + +struct us2e_freq_percpu_info { + struct cpufreq_frequency_table table[6]; +}; + +/* Indexed by cpu number. */ +static struct us2e_freq_percpu_info *us2e_freq_table; + +#define HBIRD_MEM_CNTL0_ADDR 0x1fe0000f010UL +#define HBIRD_ESTAR_MODE_ADDR 0x1fe0000f080UL + +/* UltraSPARC-IIe has five dividers: 1, 2, 4, 6, and 8. These are controlled + * in the ESTAR mode control register. + */ +#define ESTAR_MODE_DIV_1 0x0000000000000000UL +#define ESTAR_MODE_DIV_2 0x0000000000000001UL +#define ESTAR_MODE_DIV_4 0x0000000000000003UL +#define ESTAR_MODE_DIV_6 0x0000000000000002UL +#define ESTAR_MODE_DIV_8 0x0000000000000004UL +#define ESTAR_MODE_DIV_MASK 0x0000000000000007UL + +#define MCTRL0_SREFRESH_ENAB 0x0000000000010000UL +#define MCTRL0_REFR_COUNT_MASK 0x0000000000007f00UL +#define MCTRL0_REFR_COUNT_SHIFT 8 +#define MCTRL0_REFR_INTERVAL 7800 +#define MCTRL0_REFR_CLKS_P_CNT 64 + +static unsigned long read_hbreg(unsigned long addr) +{ + unsigned long ret; + + __asm__ __volatile__("ldxa [%1] %2, %0" + : "=&r" (ret) + : "r" (addr), "i" (ASI_PHYS_BYPASS_EC_E)); + return ret; +} + +static void write_hbreg(unsigned long addr, unsigned long val) +{ + __asm__ __volatile__("stxa %0, [%1] %2\n\t" + "membar #Sync" + : /* no outputs */ + : "r" (val), "r" (addr), "i" (ASI_PHYS_BYPASS_EC_E) + : "memory"); + if (addr == HBIRD_ESTAR_MODE_ADDR) { + /* Need to wait 16 clock cycles for the PLL to lock. */ + udelay(1); + } +} + +static void self_refresh_ctl(int enable) +{ + unsigned long mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR); + + if (enable) + mctrl |= MCTRL0_SREFRESH_ENAB; + else + mctrl &= ~MCTRL0_SREFRESH_ENAB; + write_hbreg(HBIRD_MEM_CNTL0_ADDR, mctrl); + (void) read_hbreg(HBIRD_MEM_CNTL0_ADDR); +} + +static void frob_mem_refresh(int cpu_slowing_down, + unsigned long clock_tick, + unsigned long old_divisor, unsigned long divisor) +{ + unsigned long old_refr_count, refr_count, mctrl; + + refr_count = (clock_tick * MCTRL0_REFR_INTERVAL); + refr_count /= (MCTRL0_REFR_CLKS_P_CNT * divisor * 1000000000UL); + + mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR); + old_refr_count = (mctrl & MCTRL0_REFR_COUNT_MASK) + >> MCTRL0_REFR_COUNT_SHIFT; + + mctrl &= ~MCTRL0_REFR_COUNT_MASK; + mctrl |= refr_count << MCTRL0_REFR_COUNT_SHIFT; + write_hbreg(HBIRD_MEM_CNTL0_ADDR, mctrl); + mctrl = read_hbreg(HBIRD_MEM_CNTL0_ADDR); + + if (cpu_slowing_down && !(mctrl & MCTRL0_SREFRESH_ENAB)) { + unsigned long usecs; + + /* We have to wait for both refresh counts (old + * and new) to go to zero. + */ + usecs = (MCTRL0_REFR_CLKS_P_CNT * + (refr_count + old_refr_count) * + 1000000UL * + old_divisor) / clock_tick; + udelay(usecs + 1UL); + } +} + +static void us2e_transition(unsigned long estar, unsigned long new_bits, + unsigned long clock_tick, + unsigned long old_divisor, unsigned long divisor) +{ + unsigned long flags; + + local_irq_save(flags); + + estar &= ~ESTAR_MODE_DIV_MASK; + + /* This is based upon the state transition diagram in the IIe manual. */ + if (old_divisor == 2 && divisor == 1) { + self_refresh_ctl(0); + write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits); + frob_mem_refresh(0, clock_tick, old_divisor, divisor); + } else if (old_divisor == 1 && divisor == 2) { + frob_mem_refresh(1, clock_tick, old_divisor, divisor); + write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits); + self_refresh_ctl(1); + } else if (old_divisor == 1 && divisor > 2) { + us2e_transition(estar, ESTAR_MODE_DIV_2, clock_tick, + 1, 2); + us2e_transition(estar, new_bits, clock_tick, + 2, divisor); + } else if (old_divisor > 2 && divisor == 1) { + us2e_transition(estar, ESTAR_MODE_DIV_2, clock_tick, + old_divisor, 2); + us2e_transition(estar, new_bits, clock_tick, + 2, divisor); + } else if (old_divisor < divisor) { + frob_mem_refresh(0, clock_tick, old_divisor, divisor); + write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits); + } else if (old_divisor > divisor) { + write_hbreg(HBIRD_ESTAR_MODE_ADDR, estar | new_bits); + frob_mem_refresh(1, clock_tick, old_divisor, divisor); + } else { + BUG(); + } + + local_irq_restore(flags); +} + +static unsigned long index_to_estar_mode(unsigned int index) +{ + switch (index) { + case 0: + return ESTAR_MODE_DIV_1; + + case 1: + return ESTAR_MODE_DIV_2; + + case 2: + return ESTAR_MODE_DIV_4; + + case 3: + return ESTAR_MODE_DIV_6; + + case 4: + return ESTAR_MODE_DIV_8; + + default: + BUG(); + } +} + +static unsigned long index_to_divisor(unsigned int index) +{ + switch (index) { + case 0: + return 1; + + case 1: + return 2; + + case 2: + return 4; + + case 3: + return 6; + + case 4: + return 8; + + default: + BUG(); + } +} + +static unsigned long estar_to_divisor(unsigned long estar) +{ + unsigned long ret; + + switch (estar & ESTAR_MODE_DIV_MASK) { + case ESTAR_MODE_DIV_1: + ret = 1; + break; + case ESTAR_MODE_DIV_2: + ret = 2; + break; + case ESTAR_MODE_DIV_4: + ret = 4; + break; + case ESTAR_MODE_DIV_6: + ret = 6; + break; + case ESTAR_MODE_DIV_8: + ret = 8; + break; + default: + BUG(); + } + + return ret; +} + +static unsigned int us2e_freq_get(unsigned int cpu) +{ + cpumask_t cpus_allowed; + unsigned long clock_tick, estar; + + cpumask_copy(&cpus_allowed, tsk_cpus_allowed(current)); + set_cpus_allowed_ptr(current, cpumask_of(cpu)); + + clock_tick = sparc64_get_clock_tick(cpu) / 1000; + estar = read_hbreg(HBIRD_ESTAR_MODE_ADDR); + + set_cpus_allowed_ptr(current, &cpus_allowed); + + return clock_tick / estar_to_divisor(estar); +} + +static void us2e_set_cpu_divider_index(struct cpufreq_policy *policy, + unsigned int index) +{ + unsigned int cpu = policy->cpu; + unsigned long new_bits, new_freq; + unsigned long clock_tick, divisor, old_divisor, estar; + cpumask_t cpus_allowed; + struct cpufreq_freqs freqs; + + cpumask_copy(&cpus_allowed, tsk_cpus_allowed(current)); + set_cpus_allowed_ptr(current, cpumask_of(cpu)); + + new_freq = clock_tick = sparc64_get_clock_tick(cpu) / 1000; + new_bits = index_to_estar_mode(index); + divisor = index_to_divisor(index); + new_freq /= divisor; + + estar = read_hbreg(HBIRD_ESTAR_MODE_ADDR); + + old_divisor = estar_to_divisor(estar); + + freqs.old = clock_tick / old_divisor; + freqs.new = new_freq; + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + if (old_divisor != divisor) + us2e_transition(estar, new_bits, clock_tick * 1000, + old_divisor, divisor); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + set_cpus_allowed_ptr(current, &cpus_allowed); +} + +static int us2e_freq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int new_index = 0; + + if (cpufreq_frequency_table_target(policy, + &us2e_freq_table[policy->cpu].table[0], + target_freq, relation, &new_index)) + return -EINVAL; + + us2e_set_cpu_divider_index(policy, new_index); + + return 0; +} + +static int us2e_freq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, + &us2e_freq_table[policy->cpu].table[0]); +} + +static int __init us2e_freq_cpu_init(struct cpufreq_policy *policy) +{ + unsigned int cpu = policy->cpu; + unsigned long clock_tick = sparc64_get_clock_tick(cpu) / 1000; + struct cpufreq_frequency_table *table = + &us2e_freq_table[cpu].table[0]; + + table[0].index = 0; + table[0].frequency = clock_tick / 1; + table[1].index = 1; + table[1].frequency = clock_tick / 2; + table[2].index = 2; + table[2].frequency = clock_tick / 4; + table[2].index = 3; + table[2].frequency = clock_tick / 6; + table[2].index = 4; + table[2].frequency = clock_tick / 8; + table[2].index = 5; + table[3].frequency = CPUFREQ_TABLE_END; + + policy->cpuinfo.transition_latency = 0; + policy->cur = clock_tick; + + return cpufreq_frequency_table_cpuinfo(policy, table); +} + +static int us2e_freq_cpu_exit(struct cpufreq_policy *policy) +{ + if (cpufreq_us2e_driver) + us2e_set_cpu_divider_index(policy, 0); + + return 0; +} + +static int __init us2e_freq_init(void) +{ + unsigned long manuf, impl, ver; + int ret; + + if (tlb_type != spitfire) + return -ENODEV; + + __asm__("rdpr %%ver, %0" : "=r" (ver)); + manuf = ((ver >> 48) & 0xffff); + impl = ((ver >> 32) & 0xffff); + + if (manuf == 0x17 && impl == 0x13) { + struct cpufreq_driver *driver; + + ret = -ENOMEM; + driver = kzalloc(sizeof(struct cpufreq_driver), GFP_KERNEL); + if (!driver) + goto err_out; + + us2e_freq_table = kzalloc( + (NR_CPUS * sizeof(struct us2e_freq_percpu_info)), + GFP_KERNEL); + if (!us2e_freq_table) + goto err_out; + + driver->init = us2e_freq_cpu_init; + driver->verify = us2e_freq_verify; + driver->target = us2e_freq_target; + driver->get = us2e_freq_get; + driver->exit = us2e_freq_cpu_exit; + driver->owner = THIS_MODULE, + strcpy(driver->name, "UltraSPARC-IIe"); + + cpufreq_us2e_driver = driver; + ret = cpufreq_register_driver(driver); + if (ret) + goto err_out; + + return 0; + +err_out: + if (driver) { + kfree(driver); + cpufreq_us2e_driver = NULL; + } + kfree(us2e_freq_table); + us2e_freq_table = NULL; + return ret; + } + + return -ENODEV; +} + +static void __exit us2e_freq_exit(void) +{ + if (cpufreq_us2e_driver) { + cpufreq_unregister_driver(cpufreq_us2e_driver); + kfree(cpufreq_us2e_driver); + cpufreq_us2e_driver = NULL; + kfree(us2e_freq_table); + us2e_freq_table = NULL; + } +} + +MODULE_AUTHOR("David S. Miller <davem@redhat.com>"); +MODULE_DESCRIPTION("cpufreq driver for UltraSPARC-IIe"); +MODULE_LICENSE("GPL"); + +module_init(us2e_freq_init); +module_exit(us2e_freq_exit); diff --git a/drivers/cpufreq/sparc-us3-cpufreq.c b/drivers/cpufreq/sparc-us3-cpufreq.c new file mode 100644 index 00000000000..c71ee142347 --- /dev/null +++ b/drivers/cpufreq/sparc-us3-cpufreq.c @@ -0,0 +1,269 @@ +/* us3_cpufreq.c: UltraSPARC-III cpu frequency support + * + * Copyright (C) 2003 David S. Miller (davem@redhat.com) + * + * Many thanks to Dominik Brodowski for fixing up the cpufreq + * infrastructure in order to make this driver easier to implement. + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/sched.h> +#include <linux/smp.h> +#include <linux/cpufreq.h> +#include <linux/threads.h> +#include <linux/slab.h> +#include <linux/init.h> + +#include <asm/head.h> +#include <asm/timer.h> + +static struct cpufreq_driver *cpufreq_us3_driver; + +struct us3_freq_percpu_info { + struct cpufreq_frequency_table table[4]; +}; + +/* Indexed by cpu number. */ +static struct us3_freq_percpu_info *us3_freq_table; + +/* UltraSPARC-III has three dividers: 1, 2, and 32. These are controlled + * in the Safari config register. + */ +#define SAFARI_CFG_DIV_1 0x0000000000000000UL +#define SAFARI_CFG_DIV_2 0x0000000040000000UL +#define SAFARI_CFG_DIV_32 0x0000000080000000UL +#define SAFARI_CFG_DIV_MASK 0x00000000C0000000UL + +static unsigned long read_safari_cfg(void) +{ + unsigned long ret; + + __asm__ __volatile__("ldxa [%%g0] %1, %0" + : "=&r" (ret) + : "i" (ASI_SAFARI_CONFIG)); + return ret; +} + +static void write_safari_cfg(unsigned long val) +{ + __asm__ __volatile__("stxa %0, [%%g0] %1\n\t" + "membar #Sync" + : /* no outputs */ + : "r" (val), "i" (ASI_SAFARI_CONFIG) + : "memory"); +} + +static unsigned long get_current_freq(unsigned int cpu, unsigned long safari_cfg) +{ + unsigned long clock_tick = sparc64_get_clock_tick(cpu) / 1000; + unsigned long ret; + + switch (safari_cfg & SAFARI_CFG_DIV_MASK) { + case SAFARI_CFG_DIV_1: + ret = clock_tick / 1; + break; + case SAFARI_CFG_DIV_2: + ret = clock_tick / 2; + break; + case SAFARI_CFG_DIV_32: + ret = clock_tick / 32; + break; + default: + BUG(); + } + + return ret; +} + +static unsigned int us3_freq_get(unsigned int cpu) +{ + cpumask_t cpus_allowed; + unsigned long reg; + unsigned int ret; + + cpumask_copy(&cpus_allowed, tsk_cpus_allowed(current)); + set_cpus_allowed_ptr(current, cpumask_of(cpu)); + + reg = read_safari_cfg(); + ret = get_current_freq(cpu, reg); + + set_cpus_allowed_ptr(current, &cpus_allowed); + + return ret; +} + +static void us3_set_cpu_divider_index(struct cpufreq_policy *policy, + unsigned int index) +{ + unsigned int cpu = policy->cpu; + unsigned long new_bits, new_freq, reg; + cpumask_t cpus_allowed; + struct cpufreq_freqs freqs; + + cpumask_copy(&cpus_allowed, tsk_cpus_allowed(current)); + set_cpus_allowed_ptr(current, cpumask_of(cpu)); + + new_freq = sparc64_get_clock_tick(cpu) / 1000; + switch (index) { + case 0: + new_bits = SAFARI_CFG_DIV_1; + new_freq /= 1; + break; + case 1: + new_bits = SAFARI_CFG_DIV_2; + new_freq /= 2; + break; + case 2: + new_bits = SAFARI_CFG_DIV_32; + new_freq /= 32; + break; + + default: + BUG(); + } + + reg = read_safari_cfg(); + + freqs.old = get_current_freq(cpu, reg); + freqs.new = new_freq; + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + reg &= ~SAFARI_CFG_DIV_MASK; + reg |= new_bits; + write_safari_cfg(reg); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + set_cpus_allowed_ptr(current, &cpus_allowed); +} + +static int us3_freq_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int new_index = 0; + + if (cpufreq_frequency_table_target(policy, + &us3_freq_table[policy->cpu].table[0], + target_freq, + relation, + &new_index)) + return -EINVAL; + + us3_set_cpu_divider_index(policy, new_index); + + return 0; +} + +static int us3_freq_verify(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, + &us3_freq_table[policy->cpu].table[0]); +} + +static int __init us3_freq_cpu_init(struct cpufreq_policy *policy) +{ + unsigned int cpu = policy->cpu; + unsigned long clock_tick = sparc64_get_clock_tick(cpu) / 1000; + struct cpufreq_frequency_table *table = + &us3_freq_table[cpu].table[0]; + + table[0].index = 0; + table[0].frequency = clock_tick / 1; + table[1].index = 1; + table[1].frequency = clock_tick / 2; + table[2].index = 2; + table[2].frequency = clock_tick / 32; + table[3].index = 0; + table[3].frequency = CPUFREQ_TABLE_END; + + policy->cpuinfo.transition_latency = 0; + policy->cur = clock_tick; + + return cpufreq_frequency_table_cpuinfo(policy, table); +} + +static int us3_freq_cpu_exit(struct cpufreq_policy *policy) +{ + if (cpufreq_us3_driver) + us3_set_cpu_divider_index(policy, 0); + + return 0; +} + +static int __init us3_freq_init(void) +{ + unsigned long manuf, impl, ver; + int ret; + + if (tlb_type != cheetah && tlb_type != cheetah_plus) + return -ENODEV; + + __asm__("rdpr %%ver, %0" : "=r" (ver)); + manuf = ((ver >> 48) & 0xffff); + impl = ((ver >> 32) & 0xffff); + + if (manuf == CHEETAH_MANUF && + (impl == CHEETAH_IMPL || + impl == CHEETAH_PLUS_IMPL || + impl == JAGUAR_IMPL || + impl == PANTHER_IMPL)) { + struct cpufreq_driver *driver; + + ret = -ENOMEM; + driver = kzalloc(sizeof(struct cpufreq_driver), GFP_KERNEL); + if (!driver) + goto err_out; + + us3_freq_table = kzalloc( + (NR_CPUS * sizeof(struct us3_freq_percpu_info)), + GFP_KERNEL); + if (!us3_freq_table) + goto err_out; + + driver->init = us3_freq_cpu_init; + driver->verify = us3_freq_verify; + driver->target = us3_freq_target; + driver->get = us3_freq_get; + driver->exit = us3_freq_cpu_exit; + driver->owner = THIS_MODULE, + strcpy(driver->name, "UltraSPARC-III"); + + cpufreq_us3_driver = driver; + ret = cpufreq_register_driver(driver); + if (ret) + goto err_out; + + return 0; + +err_out: + if (driver) { + kfree(driver); + cpufreq_us3_driver = NULL; + } + kfree(us3_freq_table); + us3_freq_table = NULL; + return ret; + } + + return -ENODEV; +} + +static void __exit us3_freq_exit(void) +{ + if (cpufreq_us3_driver) { + cpufreq_unregister_driver(cpufreq_us3_driver); + kfree(cpufreq_us3_driver); + cpufreq_us3_driver = NULL; + kfree(us3_freq_table); + us3_freq_table = NULL; + } +} + +MODULE_AUTHOR("David S. Miller <davem@redhat.com>"); +MODULE_DESCRIPTION("cpufreq driver for UltraSPARC-III"); +MODULE_LICENSE("GPL"); + +module_init(us3_freq_init); +module_exit(us3_freq_exit); diff --git a/drivers/cpufreq/spear-cpufreq.c b/drivers/cpufreq/spear-cpufreq.c index 7e4d7732795..156829f4576 100644 --- a/drivers/cpufreq/spear-cpufreq.c +++ b/drivers/cpufreq/spear-cpufreq.c @@ -121,7 +121,6 @@ static int spear_cpufreq_target(struct cpufreq_policy *policy, target_freq, relation, &index)) return -EINVAL; - freqs.cpu = policy->cpu; freqs.old = spear_cpufreq_get(0); newfreq = spear_cpufreq.freq_tbl[index].frequency * 1000; @@ -158,8 +157,7 @@ static int spear_cpufreq_target(struct cpufreq_policy *policy, freqs.new = newfreq / 1000; freqs.new /= mult; - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); if (mult == 2) ret = spear1340_set_cpu_rate(srcclk, newfreq); @@ -172,8 +170,7 @@ static int spear_cpufreq_target(struct cpufreq_policy *policy, freqs.new = clk_get_rate(spear_cpufreq.clk) / 1000; } - for_each_cpu(freqs.cpu, policy->cpus) - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return ret; } diff --git a/drivers/cpufreq/speedstep-centrino.c b/drivers/cpufreq/speedstep-centrino.c index 3a953d519f4..618e6f417b1 100644 --- a/drivers/cpufreq/speedstep-centrino.c +++ b/drivers/cpufreq/speedstep-centrino.c @@ -457,7 +457,7 @@ static int centrino_target (struct cpufreq_policy *policy, unsigned int msr, oldmsr = 0, h = 0, cpu = policy->cpu; struct cpufreq_freqs freqs; int retval = 0; - unsigned int j, k, first_cpu, tmp; + unsigned int j, first_cpu, tmp; cpumask_var_t covered_cpus; if (unlikely(!zalloc_cpumask_var(&covered_cpus, GFP_KERNEL))) @@ -481,10 +481,6 @@ static int centrino_target (struct cpufreq_policy *policy, for_each_cpu(j, policy->cpus) { int good_cpu; - /* cpufreq holds the hotplug lock, so we are safe here */ - if (!cpu_online(j)) - continue; - /* * Support for SMP systems. * Make sure we are running on CPU that wants to change freq @@ -522,13 +518,8 @@ static int centrino_target (struct cpufreq_policy *policy, pr_debug("target=%dkHz old=%d new=%d msr=%04x\n", target_freq, freqs.old, freqs.new, msr); - for_each_cpu(k, policy->cpus) { - if (!cpu_online(k)) - continue; - freqs.cpu = k; - cpufreq_notify_transition(&freqs, + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); - } first_cpu = 0; /* all but 16 LSB are reserved, treat them with care */ @@ -544,12 +535,7 @@ static int centrino_target (struct cpufreq_policy *policy, cpumask_set_cpu(j, covered_cpus); } - for_each_cpu(k, policy->cpus) { - if (!cpu_online(k)) - continue; - freqs.cpu = k; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); if (unlikely(retval)) { /* @@ -565,12 +551,8 @@ static int centrino_target (struct cpufreq_policy *policy, tmp = freqs.new; freqs.new = freqs.old; freqs.old = tmp; - for_each_cpu(j, policy->cpus) { - if (!cpu_online(j)) - continue; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); } retval = 0; diff --git a/drivers/cpufreq/speedstep-ich.c b/drivers/cpufreq/speedstep-ich.c index e29b59aa68a..e2e5aa97145 100644 --- a/drivers/cpufreq/speedstep-ich.c +++ b/drivers/cpufreq/speedstep-ich.c @@ -263,7 +263,6 @@ static int speedstep_target(struct cpufreq_policy *policy, { unsigned int newstate = 0, policy_cpu; struct cpufreq_freqs freqs; - int i; if (cpufreq_frequency_table_target(policy, &speedstep_freqs[0], target_freq, relation, &newstate)) @@ -272,7 +271,6 @@ static int speedstep_target(struct cpufreq_policy *policy, policy_cpu = cpumask_any_and(policy->cpus, cpu_online_mask); freqs.old = speedstep_get(policy_cpu); freqs.new = speedstep_freqs[newstate].frequency; - freqs.cpu = policy->cpu; pr_debug("transiting from %u to %u kHz\n", freqs.old, freqs.new); @@ -280,18 +278,12 @@ static int speedstep_target(struct cpufreq_policy *policy, if (freqs.old == freqs.new) return 0; - for_each_cpu(i, policy->cpus) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); smp_call_function_single(policy_cpu, _speedstep_set_state, &newstate, true); - for_each_cpu(i, policy->cpus) { - freqs.cpu = i; - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); - } + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return 0; } diff --git a/drivers/cpufreq/speedstep-smi.c b/drivers/cpufreq/speedstep-smi.c index 6a457fcaaad..f5a6b70ee6c 100644 --- a/drivers/cpufreq/speedstep-smi.c +++ b/drivers/cpufreq/speedstep-smi.c @@ -252,14 +252,13 @@ static int speedstep_target(struct cpufreq_policy *policy, freqs.old = speedstep_freqs[speedstep_get_state()].frequency; freqs.new = speedstep_freqs[newstate].frequency; - freqs.cpu = 0; /* speedstep.c is UP only driver */ if (freqs.old == freqs.new) return 0; - cpufreq_notify_transition(&freqs, CPUFREQ_PRECHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); speedstep_set_state(newstate); - cpufreq_notify_transition(&freqs, CPUFREQ_POSTCHANGE); + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); return 0; } diff --git a/drivers/cpufreq/tegra-cpufreq.c b/drivers/cpufreq/tegra-cpufreq.c new file mode 100644 index 00000000000..c74c0e130ef --- /dev/null +++ b/drivers/cpufreq/tegra-cpufreq.c @@ -0,0 +1,292 @@ +/* + * Copyright (C) 2010 Google, Inc. + * + * Author: + * Colin Cross <ccross@google.com> + * Based on arch/arm/plat-omap/cpu-omap.c, (C) 2005 Nokia Corporation + * + * This software is licensed under the terms of the GNU General Public + * License version 2, as published by the Free Software Foundation, and + * may be copied, distributed, and modified under those terms. + * + * 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. + * + */ + +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/types.h> +#include <linux/sched.h> +#include <linux/cpufreq.h> +#include <linux/delay.h> +#include <linux/init.h> +#include <linux/err.h> +#include <linux/clk.h> +#include <linux/io.h> +#include <linux/suspend.h> + +/* Frequency table index must be sequential starting at 0 */ +static struct cpufreq_frequency_table freq_table[] = { + { 0, 216000 }, + { 1, 312000 }, + { 2, 456000 }, + { 3, 608000 }, + { 4, 760000 }, + { 5, 816000 }, + { 6, 912000 }, + { 7, 1000000 }, + { 8, CPUFREQ_TABLE_END }, +}; + +#define NUM_CPUS 2 + +static struct clk *cpu_clk; +static struct clk *pll_x_clk; +static struct clk *pll_p_clk; +static struct clk *emc_clk; + +static unsigned long target_cpu_speed[NUM_CPUS]; +static DEFINE_MUTEX(tegra_cpu_lock); +static bool is_suspended; + +static int tegra_verify_speed(struct cpufreq_policy *policy) +{ + return cpufreq_frequency_table_verify(policy, freq_table); +} + +static unsigned int tegra_getspeed(unsigned int cpu) +{ + unsigned long rate; + + if (cpu >= NUM_CPUS) + return 0; + + rate = clk_get_rate(cpu_clk) / 1000; + return rate; +} + +static int tegra_cpu_clk_set_rate(unsigned long rate) +{ + int ret; + + /* + * Take an extra reference to the main pll so it doesn't turn + * off when we move the cpu off of it + */ + clk_prepare_enable(pll_x_clk); + + ret = clk_set_parent(cpu_clk, pll_p_clk); + if (ret) { + pr_err("Failed to switch cpu to clock pll_p\n"); + goto out; + } + + if (rate == clk_get_rate(pll_p_clk)) + goto out; + + ret = clk_set_rate(pll_x_clk, rate); + if (ret) { + pr_err("Failed to change pll_x to %lu\n", rate); + goto out; + } + + ret = clk_set_parent(cpu_clk, pll_x_clk); + if (ret) { + pr_err("Failed to switch cpu to clock pll_x\n"); + goto out; + } + +out: + clk_disable_unprepare(pll_x_clk); + return ret; +} + +static int tegra_update_cpu_speed(struct cpufreq_policy *policy, + unsigned long rate) +{ + int ret = 0; + struct cpufreq_freqs freqs; + + freqs.old = tegra_getspeed(0); + freqs.new = rate; + + if (freqs.old == freqs.new) + return ret; + + /* + * Vote on memory bus frequency based on cpu frequency + * This sets the minimum frequency, display or avp may request higher + */ + if (rate >= 816000) + clk_set_rate(emc_clk, 600000000); /* cpu 816 MHz, emc max */ + else if (rate >= 456000) + clk_set_rate(emc_clk, 300000000); /* cpu 456 MHz, emc 150Mhz */ + else + clk_set_rate(emc_clk, 100000000); /* emc 50Mhz */ + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + +#ifdef CONFIG_CPU_FREQ_DEBUG + printk(KERN_DEBUG "cpufreq-tegra: transition: %u --> %u\n", + freqs.old, freqs.new); +#endif + + ret = tegra_cpu_clk_set_rate(freqs.new * 1000); + if (ret) { + pr_err("cpu-tegra: Failed to set cpu frequency to %d kHz\n", + freqs.new); + return ret; + } + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static unsigned long tegra_cpu_highest_speed(void) +{ + unsigned long rate = 0; + int i; + + for_each_online_cpu(i) + rate = max(rate, target_cpu_speed[i]); + return rate; +} + +static int tegra_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int idx; + unsigned int freq; + int ret = 0; + + mutex_lock(&tegra_cpu_lock); + + if (is_suspended) { + ret = -EBUSY; + goto out; + } + + cpufreq_frequency_table_target(policy, freq_table, target_freq, + relation, &idx); + + freq = freq_table[idx].frequency; + + target_cpu_speed[policy->cpu] = freq; + + ret = tegra_update_cpu_speed(policy, tegra_cpu_highest_speed()); + +out: + mutex_unlock(&tegra_cpu_lock); + return ret; +} + +static int tegra_pm_notify(struct notifier_block *nb, unsigned long event, + void *dummy) +{ + mutex_lock(&tegra_cpu_lock); + if (event == PM_SUSPEND_PREPARE) { + struct cpufreq_policy *policy = cpufreq_cpu_get(0); + is_suspended = true; + pr_info("Tegra cpufreq suspend: setting frequency to %d kHz\n", + freq_table[0].frequency); + tegra_update_cpu_speed(policy, freq_table[0].frequency); + cpufreq_cpu_put(policy); + } else if (event == PM_POST_SUSPEND) { + is_suspended = false; + } + mutex_unlock(&tegra_cpu_lock); + + return NOTIFY_OK; +} + +static struct notifier_block tegra_cpu_pm_notifier = { + .notifier_call = tegra_pm_notify, +}; + +static int tegra_cpu_init(struct cpufreq_policy *policy) +{ + if (policy->cpu >= NUM_CPUS) + return -EINVAL; + + clk_prepare_enable(emc_clk); + clk_prepare_enable(cpu_clk); + + cpufreq_frequency_table_cpuinfo(policy, freq_table); + cpufreq_frequency_table_get_attr(freq_table, policy->cpu); + policy->cur = tegra_getspeed(policy->cpu); + target_cpu_speed[policy->cpu] = policy->cur; + + /* FIXME: what's the actual transition time? */ + policy->cpuinfo.transition_latency = 300 * 1000; + + cpumask_copy(policy->cpus, cpu_possible_mask); + + if (policy->cpu == 0) + register_pm_notifier(&tegra_cpu_pm_notifier); + + return 0; +} + +static int tegra_cpu_exit(struct cpufreq_policy *policy) +{ + cpufreq_frequency_table_cpuinfo(policy, freq_table); + clk_disable_unprepare(emc_clk); + return 0; +} + +static struct freq_attr *tegra_cpufreq_attr[] = { + &cpufreq_freq_attr_scaling_available_freqs, + NULL, +}; + +static struct cpufreq_driver tegra_cpufreq_driver = { + .verify = tegra_verify_speed, + .target = tegra_target, + .get = tegra_getspeed, + .init = tegra_cpu_init, + .exit = tegra_cpu_exit, + .name = "tegra", + .attr = tegra_cpufreq_attr, +}; + +static int __init tegra_cpufreq_init(void) +{ + cpu_clk = clk_get_sys(NULL, "cpu"); + if (IS_ERR(cpu_clk)) + return PTR_ERR(cpu_clk); + + pll_x_clk = clk_get_sys(NULL, "pll_x"); + if (IS_ERR(pll_x_clk)) + return PTR_ERR(pll_x_clk); + + pll_p_clk = clk_get_sys(NULL, "pll_p_cclk"); + if (IS_ERR(pll_p_clk)) + return PTR_ERR(pll_p_clk); + + emc_clk = clk_get_sys("cpu", "emc"); + if (IS_ERR(emc_clk)) { + clk_put(cpu_clk); + return PTR_ERR(emc_clk); + } + + return cpufreq_register_driver(&tegra_cpufreq_driver); +} + +static void __exit tegra_cpufreq_exit(void) +{ + cpufreq_unregister_driver(&tegra_cpufreq_driver); + clk_put(emc_clk); + clk_put(cpu_clk); +} + + +MODULE_AUTHOR("Colin Cross <ccross@android.com>"); +MODULE_DESCRIPTION("cpufreq driver for Nvidia Tegra2"); +MODULE_LICENSE("GPL"); +module_init(tegra_cpufreq_init); +module_exit(tegra_cpufreq_exit); diff --git a/drivers/cpufreq/unicore2-cpufreq.c b/drivers/cpufreq/unicore2-cpufreq.c new file mode 100644 index 00000000000..12fc904d7da --- /dev/null +++ b/drivers/cpufreq/unicore2-cpufreq.c @@ -0,0 +1,92 @@ +/* + * clock scaling for the UniCore-II + * + * Code specific to PKUnity SoC and UniCore ISA + * + * Maintained by GUAN Xue-tao <gxt@mprc.pku.edu.cn> + * Copyright (C) 2001-2010 Guan Xuetao + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + */ + +#include <linux/kernel.h> +#include <linux/types.h> +#include <linux/init.h> +#include <linux/clk.h> +#include <linux/cpufreq.h> + +#include <mach/hardware.h> + +static struct cpufreq_driver ucv2_driver; + +/* make sure that only the "userspace" governor is run + * -- anything else wouldn't make sense on this platform, anyway. + */ +int ucv2_verify_speed(struct cpufreq_policy *policy) +{ + if (policy->cpu) + return -EINVAL; + + cpufreq_verify_within_limits(policy, + policy->cpuinfo.min_freq, policy->cpuinfo.max_freq); + + return 0; +} + +static unsigned int ucv2_getspeed(unsigned int cpu) +{ + struct clk *mclk = clk_get(NULL, "MAIN_CLK"); + + if (cpu) + return 0; + return clk_get_rate(mclk)/1000; +} + +static int ucv2_target(struct cpufreq_policy *policy, + unsigned int target_freq, + unsigned int relation) +{ + unsigned int cur = ucv2_getspeed(0); + struct cpufreq_freqs freqs; + struct clk *mclk = clk_get(NULL, "MAIN_CLK"); + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_PRECHANGE); + + if (!clk_set_rate(mclk, target_freq * 1000)) { + freqs.old = cur; + freqs.new = target_freq; + } + + cpufreq_notify_transition(policy, &freqs, CPUFREQ_POSTCHANGE); + + return 0; +} + +static int __init ucv2_cpu_init(struct cpufreq_policy *policy) +{ + if (policy->cpu != 0) + return -EINVAL; + policy->cur = ucv2_getspeed(0); + policy->min = policy->cpuinfo.min_freq = 250000; + policy->max = policy->cpuinfo.max_freq = 1000000; + policy->cpuinfo.transition_latency = CPUFREQ_ETERNAL; + return 0; +} + +static struct cpufreq_driver ucv2_driver = { + .flags = CPUFREQ_STICKY, + .verify = ucv2_verify_speed, + .target = ucv2_target, + .get = ucv2_getspeed, + .init = ucv2_cpu_init, + .name = "UniCore-II", +}; + +static int __init ucv2_cpufreq_init(void) +{ + return cpufreq_register_driver(&ucv2_driver); +} + +arch_initcall(ucv2_cpufreq_init); diff --git a/drivers/cpuidle/Kconfig b/drivers/cpuidle/Kconfig index 071e2c3eec4..c4cc27e5c8a 100644 --- a/drivers/cpuidle/Kconfig +++ b/drivers/cpuidle/Kconfig @@ -39,10 +39,4 @@ config CPU_IDLE_CALXEDA help Select this to enable cpuidle on Calxeda processors. -config CPU_IDLE_KIRKWOOD - bool "CPU Idle Driver for Kirkwood processors" - depends on ARCH_KIRKWOOD - help - Select this to enable cpuidle on Kirkwood processors. - endif diff --git a/drivers/cpuidle/Makefile b/drivers/cpuidle/Makefile index 24c6e7d945e..0d8bd55e776 100644 --- a/drivers/cpuidle/Makefile +++ b/drivers/cpuidle/Makefile @@ -6,4 +6,4 @@ obj-y += cpuidle.o driver.o governor.o sysfs.o governors/ obj-$(CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED) += coupled.o obj-$(CONFIG_CPU_IDLE_CALXEDA) += cpuidle-calxeda.o -obj-$(CONFIG_CPU_IDLE_KIRKWOOD) += cpuidle-kirkwood.o +obj-$(CONFIG_ARCH_KIRKWOOD) += cpuidle-kirkwood.o diff --git a/drivers/cpuidle/cpuidle-calxeda.c b/drivers/cpuidle/cpuidle-calxeda.c index e1aab38c5a8..223379169cb 100644 --- a/drivers/cpuidle/cpuidle-calxeda.c +++ b/drivers/cpuidle/cpuidle-calxeda.c @@ -1,7 +1,7 @@ /* * Copyright 2012 Calxeda, Inc. * - * Based on arch/arm/plat-mxc/cpuidle.c: + * Based on arch/arm/plat-mxc/cpuidle.c: #v3.7 * Copyright 2012 Freescale Semiconductor, Inc. * Copyright 2012 Linaro Ltd. * @@ -16,6 +16,8 @@ * * You should have received a copy of the GNU General Public License along with * this program. If not, see <http://www.gnu.org/licenses/>. + * + * Maintainer: Rob Herring <rob.herring@calxeda.com> */ #include <linux/cpuidle.h> @@ -35,8 +37,6 @@ extern void highbank_set_cpu_jump(int cpu, void *jump_addr); extern void *scu_base_addr; -static struct cpuidle_device __percpu *calxeda_idle_cpuidle_devices; - static inline unsigned int get_auxcr(void) { unsigned int val; @@ -85,22 +85,8 @@ static int calxeda_pwrdown_idle(struct cpuidle_device *dev, return index; } -static void calxeda_idle_cpuidle_devices_uninit(void) -{ - int i; - struct cpuidle_device *dev; - - for_each_possible_cpu(i) { - dev = per_cpu_ptr(calxeda_idle_cpuidle_devices, i); - cpuidle_unregister_device(dev); - } - - free_percpu(calxeda_idle_cpuidle_devices); -} - static struct cpuidle_driver calxeda_idle_driver = { .name = "calxeda_idle", - .en_core_tk_irqen = 1, .states = { ARM_CPUIDLE_WFI_STATE, { @@ -118,44 +104,9 @@ static struct cpuidle_driver calxeda_idle_driver = { static int __init calxeda_cpuidle_init(void) { - int cpu_id; - int ret; - struct cpuidle_device *dev; - struct cpuidle_driver *drv = &calxeda_idle_driver; - if (!of_machine_is_compatible("calxeda,highbank")) return -ENODEV; - ret = cpuidle_register_driver(drv); - if (ret) - return ret; - - calxeda_idle_cpuidle_devices = alloc_percpu(struct cpuidle_device); - if (calxeda_idle_cpuidle_devices == NULL) { - ret = -ENOMEM; - goto unregister_drv; - } - - /* initialize state data for each cpuidle_device */ - for_each_possible_cpu(cpu_id) { - dev = per_cpu_ptr(calxeda_idle_cpuidle_devices, cpu_id); - dev->cpu = cpu_id; - dev->state_count = drv->state_count; - - ret = cpuidle_register_device(dev); - if (ret) { - pr_err("Failed to register cpu %u, error: %d\n", - cpu_id, ret); - goto uninit; - } - } - - return 0; - -uninit: - calxeda_idle_cpuidle_devices_uninit(); -unregister_drv: - cpuidle_unregister_driver(drv); - return ret; + return cpuidle_register(&calxeda_idle_driver, NULL); } module_init(calxeda_cpuidle_init); diff --git a/drivers/cpuidle/cpuidle-kirkwood.c b/drivers/cpuidle/cpuidle-kirkwood.c index 670aa1e55cd..521b0a7fdd8 100644 --- a/drivers/cpuidle/cpuidle-kirkwood.c +++ b/drivers/cpuidle/cpuidle-kirkwood.c @@ -1,6 +1,4 @@ /* - * arch/arm/mach-kirkwood/cpuidle.c - * * CPU idle Marvell Kirkwood SoCs * * This file is licensed under the terms of the GNU General Public @@ -11,6 +9,9 @@ * to implement two idle states - * #1 wait-for-interrupt * #2 wait-for-interrupt and DDR self refresh + * + * Maintainer: Jason Cooper <jason@lakedaemon.net> + * Maintainer: Andrew Lunn <andrew@lunn.ch> */ #include <linux/kernel.h> @@ -41,7 +42,6 @@ static int kirkwood_enter_idle(struct cpuidle_device *dev, static struct cpuidle_driver kirkwood_idle_driver = { .name = "kirkwood_idle", .owner = THIS_MODULE, - .en_core_tk_irqen = 1, .states[0] = ARM_CPUIDLE_WFI_STATE, .states[1] = { .enter = kirkwood_enter_idle, @@ -53,9 +53,6 @@ static struct cpuidle_driver kirkwood_idle_driver = { }, .state_count = KIRKWOOD_MAX_STATES, }; -static struct cpuidle_device *device; - -static DEFINE_PER_CPU(struct cpuidle_device, kirkwood_cpuidle_device); /* Initialize CPU idle by registering the idle states */ static int kirkwood_cpuidle_probe(struct platform_device *pdev) @@ -66,26 +63,16 @@ static int kirkwood_cpuidle_probe(struct platform_device *pdev) if (res == NULL) return -EINVAL; - ddr_operation_base = devm_request_and_ioremap(&pdev->dev, res); - if (!ddr_operation_base) - return -EADDRNOTAVAIL; + ddr_operation_base = devm_ioremap_resource(&pdev->dev, res); + if (IS_ERR(ddr_operation_base)) + return PTR_ERR(ddr_operation_base); - device = &per_cpu(kirkwood_cpuidle_device, smp_processor_id()); - device->state_count = KIRKWOOD_MAX_STATES; - - cpuidle_register_driver(&kirkwood_idle_driver); - if (cpuidle_register_device(device)) { - pr_err("kirkwood_init_cpuidle: Failed registering\n"); - return -EIO; - } - return 0; + return cpuidle_register(&kirkwood_idle_driver, NULL); } int kirkwood_cpuidle_remove(struct platform_device *pdev) { - cpuidle_unregister_device(device); - cpuidle_unregister_driver(&kirkwood_idle_driver); - + cpuidle_unregister(&kirkwood_idle_driver); return 0; } diff --git a/drivers/cpuidle/cpuidle.c b/drivers/cpuidle/cpuidle.c index eba69290e07..c3a93fece81 100644 --- a/drivers/cpuidle/cpuidle.c +++ b/drivers/cpuidle/cpuidle.c @@ -8,6 +8,7 @@ * This code is licenced under the GPL. */ +#include <linux/clockchips.h> #include <linux/kernel.h> #include <linux/mutex.h> #include <linux/sched.h> @@ -23,6 +24,7 @@ #include "cpuidle.h" DEFINE_PER_CPU(struct cpuidle_device *, cpuidle_devices); +DEFINE_PER_CPU(struct cpuidle_device, cpuidle_dev); DEFINE_MUTEX(cpuidle_lock); LIST_HEAD(cpuidle_detected_devices); @@ -42,24 +44,6 @@ void disable_cpuidle(void) static int __cpuidle_register_device(struct cpuidle_device *dev); -static inline int cpuidle_enter(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index) -{ - struct cpuidle_state *target_state = &drv->states[index]; - return target_state->enter(dev, drv, index); -} - -static inline int cpuidle_enter_tk(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index) -{ - return cpuidle_wrap_enter(dev, drv, index, cpuidle_enter); -} - -typedef int (*cpuidle_enter_t)(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index); - -static cpuidle_enter_t cpuidle_enter_ops; - /** * cpuidle_play_dead - cpu off-lining * @@ -89,11 +73,27 @@ int cpuidle_play_dead(void) * @next_state: index into drv->states of the state to enter */ int cpuidle_enter_state(struct cpuidle_device *dev, struct cpuidle_driver *drv, - int next_state) + int index) { int entered_state; - entered_state = cpuidle_enter_ops(dev, drv, next_state); + struct cpuidle_state *target_state = &drv->states[index]; + ktime_t time_start, time_end; + s64 diff; + + time_start = ktime_get(); + + entered_state = target_state->enter(dev, drv, index); + + time_end = ktime_get(); + + local_irq_enable(); + + diff = ktime_to_us(ktime_sub(time_end, time_start)); + if (diff > INT_MAX) + diff = INT_MAX; + + dev->last_residency = (int) diff; if (entered_state >= 0) { /* Update cpuidle counters */ @@ -146,12 +146,20 @@ int cpuidle_idle_call(void) trace_cpu_idle_rcuidle(next_state, dev->cpu); + if (drv->states[next_state].flags & CPUIDLE_FLAG_TIMER_STOP) + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, + &dev->cpu); + if (cpuidle_state_is_coupled(dev, drv, next_state)) entered_state = cpuidle_enter_state_coupled(dev, drv, next_state); else entered_state = cpuidle_enter_state(dev, drv, next_state); + if (drv->states[next_state].flags & CPUIDLE_FLAG_TIMER_STOP) + clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, + &dev->cpu); + trace_cpu_idle_rcuidle(PWR_EVENT_EXIT, dev->cpu); /* give the governor an opportunity to reflect on the outcome */ @@ -222,37 +230,6 @@ void cpuidle_resume(void) mutex_unlock(&cpuidle_lock); } -/** - * cpuidle_wrap_enter - performs timekeeping and irqen around enter function - * @dev: pointer to a valid cpuidle_device object - * @drv: pointer to a valid cpuidle_driver object - * @index: index of the target cpuidle state. - */ -int cpuidle_wrap_enter(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index, - int (*enter)(struct cpuidle_device *dev, - struct cpuidle_driver *drv, int index)) -{ - ktime_t time_start, time_end; - s64 diff; - - time_start = ktime_get(); - - index = enter(dev, drv, index); - - time_end = ktime_get(); - - local_irq_enable(); - - diff = ktime_to_us(ktime_sub(time_end, time_start)); - if (diff > INT_MAX) - diff = INT_MAX; - - dev->last_residency = (int) diff; - - return index; -} - #ifdef CONFIG_ARCH_HAS_CPU_RELAX static int poll_idle(struct cpuidle_device *dev, struct cpuidle_driver *drv, int index) @@ -324,9 +301,6 @@ int cpuidle_enable_device(struct cpuidle_device *dev) return ret; } - cpuidle_enter_ops = drv->en_core_tk_irqen ? - cpuidle_enter_tk : cpuidle_enter; - poll_idle_init(drv); ret = cpuidle_add_device_sysfs(dev); @@ -480,6 +454,77 @@ void cpuidle_unregister_device(struct cpuidle_device *dev) EXPORT_SYMBOL_GPL(cpuidle_unregister_device); +/** + * cpuidle_unregister: unregister a driver and the devices. This function + * can be used only if the driver has been previously registered through + * the cpuidle_register function. + * + * @drv: a valid pointer to a struct cpuidle_driver + */ +void cpuidle_unregister(struct cpuidle_driver *drv) +{ + int cpu; + struct cpuidle_device *device; + + for_each_possible_cpu(cpu) { + device = &per_cpu(cpuidle_dev, cpu); + cpuidle_unregister_device(device); + } + + cpuidle_unregister_driver(drv); +} +EXPORT_SYMBOL_GPL(cpuidle_unregister); + +/** + * cpuidle_register: registers the driver and the cpu devices with the + * coupled_cpus passed as parameter. This function is used for all common + * initialization pattern there are in the arch specific drivers. The + * devices is globally defined in this file. + * + * @drv : a valid pointer to a struct cpuidle_driver + * @coupled_cpus: a cpumask for the coupled states + * + * Returns 0 on success, < 0 otherwise + */ +int cpuidle_register(struct cpuidle_driver *drv, + const struct cpumask *const coupled_cpus) +{ + int ret, cpu; + struct cpuidle_device *device; + + ret = cpuidle_register_driver(drv); + if (ret) { + pr_err("failed to register cpuidle driver\n"); + return ret; + } + + for_each_possible_cpu(cpu) { + device = &per_cpu(cpuidle_dev, cpu); + device->cpu = cpu; + +#ifdef CONFIG_ARCH_NEEDS_CPU_IDLE_COUPLED + /* + * On multiplatform for ARM, the coupled idle states could + * enabled in the kernel even if the cpuidle driver does not + * use it. Note, coupled_cpus is a struct copy. + */ + if (coupled_cpus) + device->coupled_cpus = *coupled_cpus; +#endif + ret = cpuidle_register_device(device); + if (!ret) + continue; + + pr_err("Failed to register cpuidle device for cpu%d\n", cpu); + + cpuidle_unregister(drv); + break; + } + + return ret; +} +EXPORT_SYMBOL_GPL(cpuidle_register); + #ifdef CONFIG_SMP static void smp_callback(void *v) diff --git a/drivers/cpuidle/driver.c b/drivers/cpuidle/driver.c index 422c7b69ba7..8dfaaae9444 100644 --- a/drivers/cpuidle/driver.c +++ b/drivers/cpuidle/driver.c @@ -11,6 +11,8 @@ #include <linux/mutex.h> #include <linux/module.h> #include <linux/cpuidle.h> +#include <linux/cpumask.h> +#include <linux/clockchips.h> #include "cpuidle.h" @@ -19,9 +21,28 @@ DEFINE_SPINLOCK(cpuidle_driver_lock); static void __cpuidle_set_cpu_driver(struct cpuidle_driver *drv, int cpu); static struct cpuidle_driver * __cpuidle_get_cpu_driver(int cpu); -static void __cpuidle_driver_init(struct cpuidle_driver *drv) +static void cpuidle_setup_broadcast_timer(void *arg) { + int cpu = smp_processor_id(); + clockevents_notify((long)(arg), &cpu); +} + +static void __cpuidle_driver_init(struct cpuidle_driver *drv, int cpu) +{ + int i; + drv->refcnt = 0; + + for (i = drv->state_count - 1; i >= 0 ; i--) { + + if (!(drv->states[i].flags & CPUIDLE_FLAG_TIMER_STOP)) + continue; + + drv->bctimer = 1; + on_each_cpu_mask(get_cpu_mask(cpu), cpuidle_setup_broadcast_timer, + (void *)CLOCK_EVT_NOTIFY_BROADCAST_ON, 1); + break; + } } static int __cpuidle_register_driver(struct cpuidle_driver *drv, int cpu) @@ -35,7 +56,7 @@ static int __cpuidle_register_driver(struct cpuidle_driver *drv, int cpu) if (__cpuidle_get_cpu_driver(cpu)) return -EBUSY; - __cpuidle_driver_init(drv); + __cpuidle_driver_init(drv, cpu); __cpuidle_set_cpu_driver(drv, cpu); @@ -49,6 +70,12 @@ static void __cpuidle_unregister_driver(struct cpuidle_driver *drv, int cpu) if (!WARN_ON(drv->refcnt > 0)) __cpuidle_set_cpu_driver(NULL, cpu); + + if (drv->bctimer) { + drv->bctimer = 0; + on_each_cpu_mask(get_cpu_mask(cpu), cpuidle_setup_broadcast_timer, + (void *)CLOCK_EVT_NOTIFY_BROADCAST_OFF, 1); + } } #ifdef CONFIG_CPU_IDLE_MULTIPLE_DRIVERS diff --git a/drivers/gpu/drm/i915/intel_opregion.c b/drivers/gpu/drm/i915/intel_opregion.c index 4d338740f2c..a8117e61400 100644 --- a/drivers/gpu/drm/i915/intel_opregion.c +++ b/drivers/gpu/drm/i915/intel_opregion.c @@ -350,11 +350,11 @@ static void intel_didl_outputs(struct drm_device *dev) if (!handle || acpi_bus_get_device(handle, &acpi_dev)) return; - if (acpi_is_video_device(acpi_dev)) + if (acpi_is_video_device(handle)) acpi_video_bus = acpi_dev; else { list_for_each_entry(acpi_cdev, &acpi_dev->children, node) { - if (acpi_is_video_device(acpi_cdev)) { + if (acpi_is_video_device(acpi_cdev->handle)) { acpi_video_bus = acpi_cdev; break; } diff --git a/drivers/hwmon/Kconfig b/drivers/hwmon/Kconfig index f61d98a6296..9c333d47143 100644 --- a/drivers/hwmon/Kconfig +++ b/drivers/hwmon/Kconfig @@ -39,6 +39,19 @@ config HWMON_DEBUG_CHIP comment "Native drivers" +config SENSORS_AB8500 + tristate "AB8500 thermal monitoring" + depends on AB8500_GPADC && AB8500_BM + default n + help + If you say yes here you get support for the thermal sensor part + of the AB8500 chip. The driver includes thermal management for + AB8500 die and two GPADC channels. The GPADC channel are preferably + used to access sensors outside the AB8500 chip. + + This driver can also be built as a module. If so, the module + will be called abx500-temp. + config SENSORS_ABITUGURU tristate "Abit uGuru (rev 1 & 2)" depends on X86 && DMI diff --git a/drivers/hwmon/Makefile b/drivers/hwmon/Makefile index c51b0dc35dc..d17d3e64f9f 100644 --- a/drivers/hwmon/Makefile +++ b/drivers/hwmon/Makefile @@ -19,6 +19,7 @@ obj-$(CONFIG_SENSORS_W83795) += w83795.o obj-$(CONFIG_SENSORS_W83781D) += w83781d.o obj-$(CONFIG_SENSORS_W83791D) += w83791d.o +obj-$(CONFIG_SENSORS_AB8500) += abx500.o ab8500.o obj-$(CONFIG_SENSORS_ABITUGURU) += abituguru.o obj-$(CONFIG_SENSORS_ABITUGURU3)+= abituguru3.o obj-$(CONFIG_SENSORS_AD7314) += ad7314.o diff --git a/drivers/hwmon/ab8500.c b/drivers/hwmon/ab8500.c new file mode 100644 index 00000000000..d844dc80685 --- /dev/null +++ b/drivers/hwmon/ab8500.c @@ -0,0 +1,206 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * Author: Martin Persson <martin.persson@stericsson.com> + * Hongbo Zhang <hongbo.zhang@linaro.org> + * License Terms: GNU General Public License v2 + * + * When the AB8500 thermal warning temperature is reached (threshold cannot + * be changed by SW), an interrupt is set, and if no further action is taken + * within a certain time frame, pm_power off will be called. + * + * When AB8500 thermal shutdown temperature is reached a hardware shutdown of + * the AB8500 will occur. + */ + +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/mfd/abx500/ab8500-gpadc.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/power/ab8500.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include "abx500.h" + +#define DEFAULT_POWER_OFF_DELAY (HZ * 10) +#define THERMAL_VCC 1800 +#define PULL_UP_RESISTOR 47000 +/* Number of monitored sensors should not greater than NUM_SENSORS */ +#define NUM_MONITORED_SENSORS 4 + +struct ab8500_gpadc_cfg { + const struct abx500_res_to_temp *temp_tbl; + int tbl_sz; + int vcc; + int r_up; +}; + +struct ab8500_temp { + struct ab8500_gpadc *gpadc; + struct ab8500_btemp *btemp; + struct delayed_work power_off_work; + struct ab8500_gpadc_cfg cfg; + struct abx500_temp *abx500_data; +}; + +/* + * The hardware connection is like this: + * VCC----[ R_up ]-----[ NTC ]----GND + * where R_up is pull-up resistance, and GPADC measures voltage on NTC. + * and res_to_temp table is strictly sorted by falling resistance values. + */ +static int ab8500_voltage_to_temp(struct ab8500_gpadc_cfg *cfg, + int v_ntc, int *temp) +{ + int r_ntc, i = 0, tbl_sz = cfg->tbl_sz; + const struct abx500_res_to_temp *tbl = cfg->temp_tbl; + + if (cfg->vcc < 0 || v_ntc >= cfg->vcc) + return -EINVAL; + + r_ntc = v_ntc * cfg->r_up / (cfg->vcc - v_ntc); + if (r_ntc > tbl[0].resist || r_ntc < tbl[tbl_sz - 1].resist) + return -EINVAL; + + while (!(r_ntc <= tbl[i].resist && r_ntc > tbl[i + 1].resist) && + i < tbl_sz - 2) + i++; + + /* return milli-Celsius */ + *temp = tbl[i].temp * 1000 + ((tbl[i + 1].temp - tbl[i].temp) * 1000 * + (r_ntc - tbl[i].resist)) / (tbl[i + 1].resist - tbl[i].resist); + + return 0; +} + +static int ab8500_read_sensor(struct abx500_temp *data, u8 sensor, int *temp) +{ + int voltage, ret; + struct ab8500_temp *ab8500_data = data->plat_data; + + if (sensor == BAT_CTRL) { + *temp = ab8500_btemp_get_batctrl_temp(ab8500_data->btemp); + } else if (sensor == BTEMP_BALL) { + *temp = ab8500_btemp_get_temp(ab8500_data->btemp); + } else { + voltage = ab8500_gpadc_convert(ab8500_data->gpadc, sensor); + if (voltage < 0) + return voltage; + + ret = ab8500_voltage_to_temp(&ab8500_data->cfg, voltage, temp); + if (ret < 0) + return ret; + } + + return 0; +} + +static void ab8500_thermal_power_off(struct work_struct *work) +{ + struct ab8500_temp *ab8500_data = container_of(work, + struct ab8500_temp, power_off_work.work); + struct abx500_temp *abx500_data = ab8500_data->abx500_data; + + dev_warn(&abx500_data->pdev->dev, "Power off due to critical temp\n"); + + pm_power_off(); +} + +static ssize_t ab8500_show_name(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + return sprintf(buf, "ab8500\n"); +} + +static ssize_t ab8500_show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + char *label; + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int index = attr->index; + + switch (index) { + case 1: + label = "ext_adc1"; + break; + case 2: + label = "ext_adc2"; + break; + case 3: + label = "bat_temp"; + break; + case 4: + label = "bat_ctrl"; + break; + default: + return -EINVAL; + } + + return sprintf(buf, "%s\n", label); +} + +static int ab8500_temp_irq_handler(int irq, struct abx500_temp *data) +{ + struct ab8500_temp *ab8500_data = data->plat_data; + + dev_warn(&data->pdev->dev, "Power off in %d s\n", + DEFAULT_POWER_OFF_DELAY / HZ); + + schedule_delayed_work(&ab8500_data->power_off_work, + DEFAULT_POWER_OFF_DELAY); + return 0; +} + +int abx500_hwmon_init(struct abx500_temp *data) +{ + struct ab8500_temp *ab8500_data; + + ab8500_data = devm_kzalloc(&data->pdev->dev, sizeof(*ab8500_data), + GFP_KERNEL); + if (!ab8500_data) + return -ENOMEM; + + ab8500_data->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + if (IS_ERR(ab8500_data->gpadc)) + return PTR_ERR(ab8500_data->gpadc); + + ab8500_data->btemp = ab8500_btemp_get(); + if (IS_ERR(ab8500_data->btemp)) + return PTR_ERR(ab8500_data->btemp); + + INIT_DELAYED_WORK(&ab8500_data->power_off_work, + ab8500_thermal_power_off); + + ab8500_data->cfg.vcc = THERMAL_VCC; + ab8500_data->cfg.r_up = PULL_UP_RESISTOR; + ab8500_data->cfg.temp_tbl = ab8500_temp_tbl_a_thermistor; + ab8500_data->cfg.tbl_sz = ab8500_temp_tbl_a_size; + + data->plat_data = ab8500_data; + + /* + * ADC_AUX1 and ADC_AUX2, connected to external NTC + * BTEMP_BALL and BAT_CTRL, fixed usage + */ + data->gpadc_addr[0] = ADC_AUX1; + data->gpadc_addr[1] = ADC_AUX2; + data->gpadc_addr[2] = BTEMP_BALL; + data->gpadc_addr[3] = BAT_CTRL; + data->monitored_sensors = NUM_MONITORED_SENSORS; + + data->ops.read_sensor = ab8500_read_sensor; + data->ops.irq_handler = ab8500_temp_irq_handler; + data->ops.show_name = ab8500_show_name; + data->ops.show_label = ab8500_show_label; + data->ops.is_visible = NULL; + + return 0; +} +EXPORT_SYMBOL(abx500_hwmon_init); + +MODULE_AUTHOR("Hongbo Zhang <hongbo.zhang@linaro.org>"); +MODULE_DESCRIPTION("AB8500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.c b/drivers/hwmon/abx500.c new file mode 100644 index 00000000000..b4ad87b31a3 --- /dev/null +++ b/drivers/hwmon/abx500.c @@ -0,0 +1,491 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * Author: Martin Persson <martin.persson@stericsson.com> + * Hongbo Zhang <hongbo.zhang@linaro.org> + * License Terms: GNU General Public License v2 + * + * ABX500 does not provide auto ADC, so to monitor the required temperatures, + * a periodic work is used. It is more important to not wake up the CPU than + * to perform this job, hence the use of a deferred delay. + * + * A deferred delay for thermal monitor is considered safe because: + * If the chip gets too hot during a sleep state it's most likely due to + * external factors, such as the surrounding temperature. I.e. no SW decisions + * will make any difference. + */ + +#include <linux/err.h> +#include <linux/hwmon.h> +#include <linux/hwmon-sysfs.h> +#include <linux/interrupt.h> +#include <linux/jiffies.h> +#include <linux/module.h> +#include <linux/mutex.h> +#include <linux/of.h> +#include <linux/platform_device.h> +#include <linux/pm.h> +#include <linux/slab.h> +#include <linux/sysfs.h> +#include <linux/workqueue.h> +#include "abx500.h" + +#define DEFAULT_MONITOR_DELAY HZ +#define DEFAULT_MAX_TEMP 130 + +static inline void schedule_monitor(struct abx500_temp *data) +{ + data->work_active = true; + schedule_delayed_work(&data->work, DEFAULT_MONITOR_DELAY); +} + +static void threshold_updated(struct abx500_temp *data) +{ + int i; + for (i = 0; i < data->monitored_sensors; i++) + if (data->max[i] != 0 || data->min[i] != 0) { + schedule_monitor(data); + return; + } + + dev_dbg(&data->pdev->dev, "No active thresholds.\n"); + cancel_delayed_work_sync(&data->work); + data->work_active = false; +} + +static void gpadc_monitor(struct work_struct *work) +{ + int temp, i, ret; + char alarm_node[30]; + bool updated_min_alarm, updated_max_alarm; + struct abx500_temp *data; + + data = container_of(work, struct abx500_temp, work.work); + mutex_lock(&data->lock); + + for (i = 0; i < data->monitored_sensors; i++) { + /* Thresholds are considered inactive if set to 0 */ + if (data->max[i] == 0 && data->min[i] == 0) + continue; + + if (data->max[i] < data->min[i]) + continue; + + ret = data->ops.read_sensor(data, data->gpadc_addr[i], &temp); + if (ret < 0) { + dev_err(&data->pdev->dev, "GPADC read failed\n"); + continue; + } + + updated_min_alarm = false; + updated_max_alarm = false; + + if (data->min[i] != 0) { + if (temp < data->min[i]) { + if (data->min_alarm[i] == false) { + data->min_alarm[i] = true; + updated_min_alarm = true; + } + } else { + if (data->min_alarm[i] == true) { + data->min_alarm[i] = false; + updated_min_alarm = true; + } + } + } + if (data->max[i] != 0) { + if (temp > data->max[i]) { + if (data->max_alarm[i] == false) { + data->max_alarm[i] = true; + updated_max_alarm = true; + } + } else if (temp < data->max[i] - data->max_hyst[i]) { + if (data->max_alarm[i] == true) { + data->max_alarm[i] = false; + updated_max_alarm = true; + } + } + } + + if (updated_min_alarm) { + ret = sprintf(alarm_node, "temp%d_min_alarm", i + 1); + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + if (updated_max_alarm) { + ret = sprintf(alarm_node, "temp%d_max_alarm", i + 1); + sysfs_notify(&data->pdev->dev.kobj, NULL, alarm_node); + } + } + + schedule_monitor(data); + mutex_unlock(&data->lock); +} + +/* HWMON sysfs interfaces */ +static ssize_t show_name(struct device *dev, struct device_attribute *devattr, + char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* Show chip name */ + return data->ops.show_name(dev, devattr, buf); +} + +static ssize_t show_label(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + /* Show each sensor label */ + return data->ops.show_label(dev, devattr, buf); +} + +static ssize_t show_input(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + int ret, temp; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + u8 gpadc_addr = data->gpadc_addr[attr->index]; + + ret = data->ops.read_sensor(data, gpadc_addr, &temp); + if (ret < 0) + return ret; + + return sprintf(buf, "%d\n", temp); +} + +/* Set functions (RW nodes) */ +static ssize_t set_min(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtol(buf, 10, &val); + if (res < 0) + return res; + + val = clamp_val(val, 0, DEFAULT_MAX_TEMP); + + mutex_lock(&data->lock); + data->min[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max(struct device *dev, struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtol(buf, 10, &val); + if (res < 0) + return res; + + val = clamp_val(val, 0, DEFAULT_MAX_TEMP); + + mutex_lock(&data->lock); + data->max[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +static ssize_t set_max_hyst(struct device *dev, + struct device_attribute *devattr, + const char *buf, size_t count) +{ + unsigned long val; + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + int res = kstrtoul(buf, 10, &val); + if (res < 0) + return res; + + val = clamp_val(val, 0, DEFAULT_MAX_TEMP); + + mutex_lock(&data->lock); + data->max_hyst[attr->index] = val; + threshold_updated(data); + mutex_unlock(&data->lock); + + return count; +} + +/* Show functions (RO nodes) */ +static ssize_t show_min(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%ld\n", data->min[attr->index]); +} + +static ssize_t show_max(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%ld\n", data->max[attr->index]); +} + +static ssize_t show_max_hyst(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%ld\n", data->max_hyst[attr->index]); +} + +static ssize_t show_min_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%d\n", data->min_alarm[attr->index]); +} + +static ssize_t show_max_alarm(struct device *dev, + struct device_attribute *devattr, char *buf) +{ + struct abx500_temp *data = dev_get_drvdata(dev); + struct sensor_device_attribute *attr = to_sensor_dev_attr(devattr); + + return sprintf(buf, "%d\n", data->max_alarm[attr->index]); +} + +static mode_t abx500_attrs_visible(struct kobject *kobj, + struct attribute *attr, int n) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct abx500_temp *data = dev_get_drvdata(dev); + + if (data->ops.is_visible) + return data->ops.is_visible(attr, n); + + return attr->mode; +} + +/* Chip name, required by hwmon */ +static SENSOR_DEVICE_ATTR(name, S_IRUGO, show_name, NULL, 0); + +/* GPADC - SENSOR1 */ +static SENSOR_DEVICE_ATTR(temp1_label, S_IRUGO, show_label, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_input, S_IRUGO, show_input, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_min, S_IWUSR | S_IRUGO, show_min, set_min, 0); +static SENSOR_DEVICE_ATTR(temp1_max, S_IWUSR | S_IRUGO, show_max, set_max, 0); +static SENSOR_DEVICE_ATTR(temp1_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 0); +static SENSOR_DEVICE_ATTR(temp1_min_alarm, S_IRUGO, show_min_alarm, NULL, 0); +static SENSOR_DEVICE_ATTR(temp1_max_alarm, S_IRUGO, show_max_alarm, NULL, 0); + +/* GPADC - SENSOR2 */ +static SENSOR_DEVICE_ATTR(temp2_label, S_IRUGO, show_label, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_input, S_IRUGO, show_input, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_min, S_IWUSR | S_IRUGO, show_min, set_min, 1); +static SENSOR_DEVICE_ATTR(temp2_max, S_IWUSR | S_IRUGO, show_max, set_max, 1); +static SENSOR_DEVICE_ATTR(temp2_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 1); +static SENSOR_DEVICE_ATTR(temp2_min_alarm, S_IRUGO, show_min_alarm, NULL, 1); +static SENSOR_DEVICE_ATTR(temp2_max_alarm, S_IRUGO, show_max_alarm, NULL, 1); + +/* GPADC - SENSOR3 */ +static SENSOR_DEVICE_ATTR(temp3_label, S_IRUGO, show_label, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_input, S_IRUGO, show_input, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_min, S_IWUSR | S_IRUGO, show_min, set_min, 2); +static SENSOR_DEVICE_ATTR(temp3_max, S_IWUSR | S_IRUGO, show_max, set_max, 2); +static SENSOR_DEVICE_ATTR(temp3_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 2); +static SENSOR_DEVICE_ATTR(temp3_min_alarm, S_IRUGO, show_min_alarm, NULL, 2); +static SENSOR_DEVICE_ATTR(temp3_max_alarm, S_IRUGO, show_max_alarm, NULL, 2); + +/* GPADC - SENSOR4 */ +static SENSOR_DEVICE_ATTR(temp4_label, S_IRUGO, show_label, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_input, S_IRUGO, show_input, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_min, S_IWUSR | S_IRUGO, show_min, set_min, 3); +static SENSOR_DEVICE_ATTR(temp4_max, S_IWUSR | S_IRUGO, show_max, set_max, 3); +static SENSOR_DEVICE_ATTR(temp4_max_hyst, S_IWUSR | S_IRUGO, + show_max_hyst, set_max_hyst, 3); +static SENSOR_DEVICE_ATTR(temp4_min_alarm, S_IRUGO, show_min_alarm, NULL, 3); +static SENSOR_DEVICE_ATTR(temp4_max_alarm, S_IRUGO, show_max_alarm, NULL, 3); + +struct attribute *abx500_temp_attributes[] = { + &sensor_dev_attr_name.dev_attr.attr, + + &sensor_dev_attr_temp1_label.dev_attr.attr, + &sensor_dev_attr_temp1_input.dev_attr.attr, + &sensor_dev_attr_temp1_min.dev_attr.attr, + &sensor_dev_attr_temp1_max.dev_attr.attr, + &sensor_dev_attr_temp1_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp1_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp1_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp2_label.dev_attr.attr, + &sensor_dev_attr_temp2_input.dev_attr.attr, + &sensor_dev_attr_temp2_min.dev_attr.attr, + &sensor_dev_attr_temp2_max.dev_attr.attr, + &sensor_dev_attr_temp2_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp2_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp2_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp3_label.dev_attr.attr, + &sensor_dev_attr_temp3_input.dev_attr.attr, + &sensor_dev_attr_temp3_min.dev_attr.attr, + &sensor_dev_attr_temp3_max.dev_attr.attr, + &sensor_dev_attr_temp3_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp3_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp3_max_alarm.dev_attr.attr, + + &sensor_dev_attr_temp4_label.dev_attr.attr, + &sensor_dev_attr_temp4_input.dev_attr.attr, + &sensor_dev_attr_temp4_min.dev_attr.attr, + &sensor_dev_attr_temp4_max.dev_attr.attr, + &sensor_dev_attr_temp4_max_hyst.dev_attr.attr, + &sensor_dev_attr_temp4_min_alarm.dev_attr.attr, + &sensor_dev_attr_temp4_max_alarm.dev_attr.attr, + NULL +}; + +static const struct attribute_group abx500_temp_group = { + .attrs = abx500_temp_attributes, + .is_visible = abx500_attrs_visible, +}; + +static irqreturn_t abx500_temp_irq_handler(int irq, void *irq_data) +{ + struct platform_device *pdev = irq_data; + struct abx500_temp *data = platform_get_drvdata(pdev); + + data->ops.irq_handler(irq, data); + return IRQ_HANDLED; +} + +static int setup_irqs(struct platform_device *pdev) +{ + int ret; + int irq = platform_get_irq_byname(pdev, "ABX500_TEMP_WARM"); + + if (irq < 0) { + dev_err(&pdev->dev, "Get irq by name failed\n"); + return irq; + } + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + abx500_temp_irq_handler, IRQF_NO_SUSPEND, "abx500-temp", pdev); + if (ret < 0) + dev_err(&pdev->dev, "Request threaded irq failed (%d)\n", ret); + + return ret; +} + +static int abx500_temp_probe(struct platform_device *pdev) +{ + struct abx500_temp *data; + int err; + + data = devm_kzalloc(&pdev->dev, sizeof(*data), GFP_KERNEL); + if (!data) + return -ENOMEM; + + data->pdev = pdev; + mutex_init(&data->lock); + + /* Chip specific initialization */ + err = abx500_hwmon_init(data); + if (err < 0 || !data->ops.read_sensor || !data->ops.show_name || + !data->ops.show_label) + return err; + + INIT_DEFERRABLE_WORK(&data->work, gpadc_monitor); + + platform_set_drvdata(pdev, data); + + err = sysfs_create_group(&pdev->dev.kobj, &abx500_temp_group); + if (err < 0) { + dev_err(&pdev->dev, "Create sysfs group failed (%d)\n", err); + return err; + } + + data->hwmon_dev = hwmon_device_register(&pdev->dev); + if (IS_ERR(data->hwmon_dev)) { + err = PTR_ERR(data->hwmon_dev); + dev_err(&pdev->dev, "Class registration failed (%d)\n", err); + goto exit_sysfs_group; + } + + if (data->ops.irq_handler) { + err = setup_irqs(pdev); + if (err < 0) + goto exit_hwmon_reg; + } + return 0; + +exit_hwmon_reg: + hwmon_device_unregister(data->hwmon_dev); +exit_sysfs_group: + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); + return err; +} + +static int abx500_temp_remove(struct platform_device *pdev) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + cancel_delayed_work_sync(&data->work); + hwmon_device_unregister(data->hwmon_dev); + sysfs_remove_group(&pdev->dev.kobj, &abx500_temp_group); + + return 0; +} + +static int abx500_temp_suspend(struct platform_device *pdev, + pm_message_t state) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + if (data->work_active) + cancel_delayed_work_sync(&data->work); + + return 0; +} + +static int abx500_temp_resume(struct platform_device *pdev) +{ + struct abx500_temp *data = platform_get_drvdata(pdev); + + if (data->work_active) + schedule_monitor(data); + + return 0; +} + +#ifdef CONFIG_OF +static const struct of_device_id abx500_temp_match[] = { + { .compatible = "stericsson,abx500-temp" }, + {}, +}; +#endif + +static struct platform_driver abx500_temp_driver = { + .driver = { + .owner = THIS_MODULE, + .name = "abx500-temp", + .of_match_table = of_match_ptr(abx500_temp_match), + }, + .suspend = abx500_temp_suspend, + .resume = abx500_temp_resume, + .probe = abx500_temp_probe, + .remove = abx500_temp_remove, +}; + +module_platform_driver(abx500_temp_driver); + +MODULE_AUTHOR("Martin Persson <martin.persson@stericsson.com>"); +MODULE_DESCRIPTION("ABX500 temperature driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/hwmon/abx500.h b/drivers/hwmon/abx500.h new file mode 100644 index 00000000000..9b295e684d0 --- /dev/null +++ b/drivers/hwmon/abx500.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) ST-Ericsson 2010 - 2013 + * License terms: GNU General Public License v2 + * Author: Martin Persson <martin.persson@stericsson.com> + * Hongbo Zhang <hongbo.zhang@linaro.com> + */ + +#ifndef _ABX500_H +#define _ABX500_H + +#define NUM_SENSORS 5 + +struct abx500_temp; + +/* + * struct abx500_temp_ops - abx500 chip specific ops + * @read_sensor: reads gpadc output + * @irq_handler: irq handler + * @show_name: hwmon device name + * @show_label: hwmon attribute label + * @is_visible: is attribute visible + */ +struct abx500_temp_ops { + int (*read_sensor)(struct abx500_temp *, u8, int *); + int (*irq_handler)(int, struct abx500_temp *); + ssize_t (*show_name)(struct device *, + struct device_attribute *, char *); + ssize_t (*show_label) (struct device *, + struct device_attribute *, char *); + int (*is_visible)(struct attribute *, int); +}; + +/* + * struct abx500_temp - representation of temp mon device + * @pdev: platform device + * @hwmon_dev: hwmon device + * @ops: abx500 chip specific ops + * @gpadc_addr: gpadc channel address + * @min: sensor temperature min value + * @max: sensor temperature max value + * @max_hyst: sensor temperature hysteresis value for max limit + * @min_alarm: sensor temperature min alarm + * @max_alarm: sensor temperature max alarm + * @work: delayed work scheduled to monitor temperature periodically + * @work_active: True if work is active + * @lock: mutex + * @monitored_sensors: number of monitored sensors + * @plat_data: private usage, usually points to platform specific data + */ +struct abx500_temp { + struct platform_device *pdev; + struct device *hwmon_dev; + struct abx500_temp_ops ops; + u8 gpadc_addr[NUM_SENSORS]; + unsigned long min[NUM_SENSORS]; + unsigned long max[NUM_SENSORS]; + unsigned long max_hyst[NUM_SENSORS]; + bool min_alarm[NUM_SENSORS]; + bool max_alarm[NUM_SENSORS]; + struct delayed_work work; + bool work_active; + struct mutex lock; + int monitored_sensors; + void *plat_data; +}; + +int abx500_hwmon_init(struct abx500_temp *data); + +#endif /* _ABX500_H */ diff --git a/drivers/idle/intel_idle.c b/drivers/idle/intel_idle.c index 1a38dd7dfe4..0e8fab1913d 100644 --- a/drivers/idle/intel_idle.c +++ b/drivers/idle/intel_idle.c @@ -71,7 +71,6 @@ static struct cpuidle_driver intel_idle_driver = { .name = "intel_idle", .owner = THIS_MODULE, - .en_core_tk_irqen = 1, }; /* intel_idle.max_cstate=0 disables driver */ static int max_cstate = CPUIDLE_STATE_MAX - 1; @@ -339,7 +338,6 @@ static int intel_idle(struct cpuidle_device *dev, if (!(lapic_timer_reliable_states & (1 << (cstate)))) clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_ENTER, &cpu); - stop_critical_timings(); if (!need_resched()) { __monitor((void *)¤t_thread_info()->flags, 0, 0); @@ -348,8 +346,6 @@ static int intel_idle(struct cpuidle_device *dev, __mwait(eax, ecx); } - start_critical_timings(); - if (!(lapic_timer_reliable_states & (1 << (cstate)))) clockevents_notify(CLOCK_EVT_NOTIFY_BROADCAST_EXIT, &cpu); diff --git a/drivers/infiniband/Kconfig b/drivers/infiniband/Kconfig index a0f29c1d03b..c85b56c2809 100644 --- a/drivers/infiniband/Kconfig +++ b/drivers/infiniband/Kconfig @@ -59,5 +59,6 @@ source "drivers/infiniband/ulp/srp/Kconfig" source "drivers/infiniband/ulp/srpt/Kconfig" source "drivers/infiniband/ulp/iser/Kconfig" +source "drivers/infiniband/ulp/isert/Kconfig" endif # INFINIBAND diff --git a/drivers/infiniband/Makefile b/drivers/infiniband/Makefile index bf846a14b9d..b126fefe0b1 100644 --- a/drivers/infiniband/Makefile +++ b/drivers/infiniband/Makefile @@ -13,3 +13,4 @@ obj-$(CONFIG_INFINIBAND_IPOIB) += ulp/ipoib/ obj-$(CONFIG_INFINIBAND_SRP) += ulp/srp/ obj-$(CONFIG_INFINIBAND_SRPT) += ulp/srpt/ obj-$(CONFIG_INFINIBAND_ISER) += ulp/iser/ +obj-$(CONFIG_INFINIBAND_ISERT) += ulp/isert/ diff --git a/drivers/infiniband/ulp/isert/Kconfig b/drivers/infiniband/ulp/isert/Kconfig new file mode 100644 index 00000000000..ce3fd32167d --- /dev/null +++ b/drivers/infiniband/ulp/isert/Kconfig @@ -0,0 +1,5 @@ +config INFINIBAND_ISERT + tristate "iSCSI Extentions for RDMA (iSER) target support" + depends on INET && INFINIBAND_ADDR_TRANS && TARGET_CORE && ISCSI_TARGET + ---help--- + Support for iSCSI Extentions for RDMA (iSER) Target on Infiniband fabrics. diff --git a/drivers/infiniband/ulp/isert/Makefile b/drivers/infiniband/ulp/isert/Makefile new file mode 100644 index 00000000000..c8bf2421f5b --- /dev/null +++ b/drivers/infiniband/ulp/isert/Makefile @@ -0,0 +1,2 @@ +ccflags-y := -Idrivers/target -Idrivers/target/iscsi +obj-$(CONFIG_INFINIBAND_ISERT) += ib_isert.o diff --git a/drivers/infiniband/ulp/isert/ib_isert.c b/drivers/infiniband/ulp/isert/ib_isert.c new file mode 100644 index 00000000000..41712f09651 --- /dev/null +++ b/drivers/infiniband/ulp/isert/ib_isert.c @@ -0,0 +1,2281 @@ +/******************************************************************************* + * This file contains iSCSI extentions for RDMA (iSER) Verbs + * + * (c) Copyright 2013 RisingTide Systems LLC. + * + * Nicholas A. Bellinger <nab@linux-iscsi.org> + * + * 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. + ****************************************************************************/ + +#include <linux/string.h> +#include <linux/module.h> +#include <linux/scatterlist.h> +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <rdma/ib_verbs.h> +#include <rdma/rdma_cm.h> +#include <target/target_core_base.h> +#include <target/target_core_fabric.h> +#include <target/iscsi/iscsi_transport.h> + +#include "isert_proto.h" +#include "ib_isert.h" + +#define ISERT_MAX_CONN 8 +#define ISER_MAX_RX_CQ_LEN (ISERT_QP_MAX_RECV_DTOS * ISERT_MAX_CONN) +#define ISER_MAX_TX_CQ_LEN (ISERT_QP_MAX_REQ_DTOS * ISERT_MAX_CONN) + +static DEFINE_MUTEX(device_list_mutex); +static LIST_HEAD(device_list); +static struct workqueue_struct *isert_rx_wq; +static struct workqueue_struct *isert_comp_wq; +static struct kmem_cache *isert_cmd_cache; + +static void +isert_qp_event_callback(struct ib_event *e, void *context) +{ + struct isert_conn *isert_conn = (struct isert_conn *)context; + + pr_err("isert_qp_event_callback event: %d\n", e->event); + switch (e->event) { + case IB_EVENT_COMM_EST: + rdma_notify(isert_conn->conn_cm_id, IB_EVENT_COMM_EST); + break; + case IB_EVENT_QP_LAST_WQE_REACHED: + pr_warn("Reached TX IB_EVENT_QP_LAST_WQE_REACHED:\n"); + break; + default: + break; + } +} + +static int +isert_query_device(struct ib_device *ib_dev, struct ib_device_attr *devattr) +{ + int ret; + + ret = ib_query_device(ib_dev, devattr); + if (ret) { + pr_err("ib_query_device() failed: %d\n", ret); + return ret; + } + pr_debug("devattr->max_sge: %d\n", devattr->max_sge); + pr_debug("devattr->max_sge_rd: %d\n", devattr->max_sge_rd); + + return 0; +} + +static int +isert_conn_setup_qp(struct isert_conn *isert_conn, struct rdma_cm_id *cma_id) +{ + struct isert_device *device = isert_conn->conn_device; + struct ib_qp_init_attr attr; + struct ib_device_attr devattr; + int ret, index, min_index = 0; + + memset(&devattr, 0, sizeof(struct ib_device_attr)); + ret = isert_query_device(cma_id->device, &devattr); + if (ret) + return ret; + + mutex_lock(&device_list_mutex); + for (index = 0; index < device->cqs_used; index++) + if (device->cq_active_qps[index] < + device->cq_active_qps[min_index]) + min_index = index; + device->cq_active_qps[min_index]++; + pr_debug("isert_conn_setup_qp: Using min_index: %d\n", min_index); + mutex_unlock(&device_list_mutex); + + memset(&attr, 0, sizeof(struct ib_qp_init_attr)); + attr.event_handler = isert_qp_event_callback; + attr.qp_context = isert_conn; + attr.send_cq = device->dev_tx_cq[min_index]; + attr.recv_cq = device->dev_rx_cq[min_index]; + attr.cap.max_send_wr = ISERT_QP_MAX_REQ_DTOS; + attr.cap.max_recv_wr = ISERT_QP_MAX_RECV_DTOS; + /* + * FIXME: Use devattr.max_sge - 2 for max_send_sge as + * work-around for RDMA_READ.. + */ + attr.cap.max_send_sge = devattr.max_sge - 2; + isert_conn->max_sge = attr.cap.max_send_sge; + + attr.cap.max_recv_sge = 1; + attr.sq_sig_type = IB_SIGNAL_REQ_WR; + attr.qp_type = IB_QPT_RC; + + pr_debug("isert_conn_setup_qp cma_id->device: %p\n", + cma_id->device); + pr_debug("isert_conn_setup_qp conn_pd->device: %p\n", + isert_conn->conn_pd->device); + + ret = rdma_create_qp(cma_id, isert_conn->conn_pd, &attr); + if (ret) { + pr_err("rdma_create_qp failed for cma_id %d\n", ret); + return ret; + } + isert_conn->conn_qp = cma_id->qp; + pr_debug("rdma_create_qp() returned success >>>>>>>>>>>>>>>>>>>>>>>>>.\n"); + + return 0; +} + +static void +isert_cq_event_callback(struct ib_event *e, void *context) +{ + pr_debug("isert_cq_event_callback event: %d\n", e->event); +} + +static int +isert_alloc_rx_descriptors(struct isert_conn *isert_conn) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct iser_rx_desc *rx_desc; + struct ib_sge *rx_sg; + u64 dma_addr; + int i, j; + + isert_conn->conn_rx_descs = kzalloc(ISERT_QP_MAX_RECV_DTOS * + sizeof(struct iser_rx_desc), GFP_KERNEL); + if (!isert_conn->conn_rx_descs) + goto fail; + + rx_desc = isert_conn->conn_rx_descs; + + for (i = 0; i < ISERT_QP_MAX_RECV_DTOS; i++, rx_desc++) { + dma_addr = ib_dma_map_single(ib_dev, (void *)rx_desc, + ISER_RX_PAYLOAD_SIZE, DMA_FROM_DEVICE); + if (ib_dma_mapping_error(ib_dev, dma_addr)) + goto dma_map_fail; + + rx_desc->dma_addr = dma_addr; + + rx_sg = &rx_desc->rx_sg; + rx_sg->addr = rx_desc->dma_addr; + rx_sg->length = ISER_RX_PAYLOAD_SIZE; + rx_sg->lkey = isert_conn->conn_mr->lkey; + } + + isert_conn->conn_rx_desc_head = 0; + return 0; + +dma_map_fail: + rx_desc = isert_conn->conn_rx_descs; + for (j = 0; j < i; j++, rx_desc++) { + ib_dma_unmap_single(ib_dev, rx_desc->dma_addr, + ISER_RX_PAYLOAD_SIZE, DMA_FROM_DEVICE); + } + kfree(isert_conn->conn_rx_descs); + isert_conn->conn_rx_descs = NULL; +fail: + return -ENOMEM; +} + +static void +isert_free_rx_descriptors(struct isert_conn *isert_conn) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct iser_rx_desc *rx_desc; + int i; + + if (!isert_conn->conn_rx_descs) + return; + + rx_desc = isert_conn->conn_rx_descs; + for (i = 0; i < ISERT_QP_MAX_RECV_DTOS; i++, rx_desc++) { + ib_dma_unmap_single(ib_dev, rx_desc->dma_addr, + ISER_RX_PAYLOAD_SIZE, DMA_FROM_DEVICE); + } + + kfree(isert_conn->conn_rx_descs); + isert_conn->conn_rx_descs = NULL; +} + +static void isert_cq_tx_callback(struct ib_cq *, void *); +static void isert_cq_rx_callback(struct ib_cq *, void *); + +static int +isert_create_device_ib_res(struct isert_device *device) +{ + struct ib_device *ib_dev = device->ib_device; + struct isert_cq_desc *cq_desc; + int ret = 0, i, j; + + device->cqs_used = min_t(int, num_online_cpus(), + device->ib_device->num_comp_vectors); + device->cqs_used = min(ISERT_MAX_CQ, device->cqs_used); + pr_debug("Using %d CQs, device %s supports %d vectors\n", + device->cqs_used, device->ib_device->name, + device->ib_device->num_comp_vectors); + device->cq_desc = kzalloc(sizeof(struct isert_cq_desc) * + device->cqs_used, GFP_KERNEL); + if (!device->cq_desc) { + pr_err("Unable to allocate device->cq_desc\n"); + return -ENOMEM; + } + cq_desc = device->cq_desc; + + device->dev_pd = ib_alloc_pd(ib_dev); + if (IS_ERR(device->dev_pd)) { + ret = PTR_ERR(device->dev_pd); + pr_err("ib_alloc_pd failed for dev_pd: %d\n", ret); + goto out_cq_desc; + } + + for (i = 0; i < device->cqs_used; i++) { + cq_desc[i].device = device; + cq_desc[i].cq_index = i; + + device->dev_rx_cq[i] = ib_create_cq(device->ib_device, + isert_cq_rx_callback, + isert_cq_event_callback, + (void *)&cq_desc[i], + ISER_MAX_RX_CQ_LEN, i); + if (IS_ERR(device->dev_rx_cq[i])) + goto out_cq; + + device->dev_tx_cq[i] = ib_create_cq(device->ib_device, + isert_cq_tx_callback, + isert_cq_event_callback, + (void *)&cq_desc[i], + ISER_MAX_TX_CQ_LEN, i); + if (IS_ERR(device->dev_tx_cq[i])) + goto out_cq; + + if (ib_req_notify_cq(device->dev_rx_cq[i], IB_CQ_NEXT_COMP)) + goto out_cq; + + if (ib_req_notify_cq(device->dev_tx_cq[i], IB_CQ_NEXT_COMP)) + goto out_cq; + } + + device->dev_mr = ib_get_dma_mr(device->dev_pd, IB_ACCESS_LOCAL_WRITE); + if (IS_ERR(device->dev_mr)) { + ret = PTR_ERR(device->dev_mr); + pr_err("ib_get_dma_mr failed for dev_mr: %d\n", ret); + goto out_cq; + } + + return 0; + +out_cq: + for (j = 0; j < i; j++) { + cq_desc = &device->cq_desc[j]; + + if (device->dev_rx_cq[j]) { + cancel_work_sync(&cq_desc->cq_rx_work); + ib_destroy_cq(device->dev_rx_cq[j]); + } + if (device->dev_tx_cq[j]) { + cancel_work_sync(&cq_desc->cq_tx_work); + ib_destroy_cq(device->dev_tx_cq[j]); + } + } + ib_dealloc_pd(device->dev_pd); + +out_cq_desc: + kfree(device->cq_desc); + + return ret; +} + +static void +isert_free_device_ib_res(struct isert_device *device) +{ + struct isert_cq_desc *cq_desc; + int i; + + for (i = 0; i < device->cqs_used; i++) { + cq_desc = &device->cq_desc[i]; + + cancel_work_sync(&cq_desc->cq_rx_work); + cancel_work_sync(&cq_desc->cq_tx_work); + ib_destroy_cq(device->dev_rx_cq[i]); + ib_destroy_cq(device->dev_tx_cq[i]); + device->dev_rx_cq[i] = NULL; + device->dev_tx_cq[i] = NULL; + } + + ib_dereg_mr(device->dev_mr); + ib_dealloc_pd(device->dev_pd); + kfree(device->cq_desc); +} + +static void +isert_device_try_release(struct isert_device *device) +{ + mutex_lock(&device_list_mutex); + device->refcount--; + if (!device->refcount) { + isert_free_device_ib_res(device); + list_del(&device->dev_node); + kfree(device); + } + mutex_unlock(&device_list_mutex); +} + +static struct isert_device * +isert_device_find_by_ib_dev(struct rdma_cm_id *cma_id) +{ + struct isert_device *device; + int ret; + + mutex_lock(&device_list_mutex); + list_for_each_entry(device, &device_list, dev_node) { + if (device->ib_device->node_guid == cma_id->device->node_guid) { + device->refcount++; + mutex_unlock(&device_list_mutex); + return device; + } + } + + device = kzalloc(sizeof(struct isert_device), GFP_KERNEL); + if (!device) { + mutex_unlock(&device_list_mutex); + return ERR_PTR(-ENOMEM); + } + + INIT_LIST_HEAD(&device->dev_node); + + device->ib_device = cma_id->device; + ret = isert_create_device_ib_res(device); + if (ret) { + kfree(device); + mutex_unlock(&device_list_mutex); + return ERR_PTR(ret); + } + + device->refcount++; + list_add_tail(&device->dev_node, &device_list); + mutex_unlock(&device_list_mutex); + + return device; +} + +static int +isert_connect_request(struct rdma_cm_id *cma_id, struct rdma_cm_event *event) +{ + struct iscsi_np *np = cma_id->context; + struct isert_np *isert_np = np->np_context; + struct isert_conn *isert_conn; + struct isert_device *device; + struct ib_device *ib_dev = cma_id->device; + int ret = 0; + + pr_debug("Entering isert_connect_request cma_id: %p, context: %p\n", + cma_id, cma_id->context); + + isert_conn = kzalloc(sizeof(struct isert_conn), GFP_KERNEL); + if (!isert_conn) { + pr_err("Unable to allocate isert_conn\n"); + return -ENOMEM; + } + isert_conn->state = ISER_CONN_INIT; + INIT_LIST_HEAD(&isert_conn->conn_accept_node); + init_completion(&isert_conn->conn_login_comp); + init_waitqueue_head(&isert_conn->conn_wait); + init_waitqueue_head(&isert_conn->conn_wait_comp_err); + kref_init(&isert_conn->conn_kref); + kref_get(&isert_conn->conn_kref); + + cma_id->context = isert_conn; + isert_conn->conn_cm_id = cma_id; + isert_conn->responder_resources = event->param.conn.responder_resources; + isert_conn->initiator_depth = event->param.conn.initiator_depth; + pr_debug("Using responder_resources: %u initiator_depth: %u\n", + isert_conn->responder_resources, isert_conn->initiator_depth); + + isert_conn->login_buf = kzalloc(ISCSI_DEF_MAX_RECV_SEG_LEN + + ISER_RX_LOGIN_SIZE, GFP_KERNEL); + if (!isert_conn->login_buf) { + pr_err("Unable to allocate isert_conn->login_buf\n"); + ret = -ENOMEM; + goto out; + } + + isert_conn->login_req_buf = isert_conn->login_buf; + isert_conn->login_rsp_buf = isert_conn->login_buf + + ISCSI_DEF_MAX_RECV_SEG_LEN; + pr_debug("Set login_buf: %p login_req_buf: %p login_rsp_buf: %p\n", + isert_conn->login_buf, isert_conn->login_req_buf, + isert_conn->login_rsp_buf); + + isert_conn->login_req_dma = ib_dma_map_single(ib_dev, + (void *)isert_conn->login_req_buf, + ISCSI_DEF_MAX_RECV_SEG_LEN, DMA_FROM_DEVICE); + + ret = ib_dma_mapping_error(ib_dev, isert_conn->login_req_dma); + if (ret) { + pr_err("ib_dma_mapping_error failed for login_req_dma: %d\n", + ret); + isert_conn->login_req_dma = 0; + goto out_login_buf; + } + + isert_conn->login_rsp_dma = ib_dma_map_single(ib_dev, + (void *)isert_conn->login_rsp_buf, + ISER_RX_LOGIN_SIZE, DMA_TO_DEVICE); + + ret = ib_dma_mapping_error(ib_dev, isert_conn->login_rsp_dma); + if (ret) { + pr_err("ib_dma_mapping_error failed for login_rsp_dma: %d\n", + ret); + isert_conn->login_rsp_dma = 0; + goto out_req_dma_map; + } + + device = isert_device_find_by_ib_dev(cma_id); + if (IS_ERR(device)) { + ret = PTR_ERR(device); + goto out_rsp_dma_map; + } + + isert_conn->conn_device = device; + isert_conn->conn_pd = device->dev_pd; + isert_conn->conn_mr = device->dev_mr; + + ret = isert_conn_setup_qp(isert_conn, cma_id); + if (ret) + goto out_conn_dev; + + mutex_lock(&isert_np->np_accept_mutex); + list_add_tail(&isert_np->np_accept_list, &isert_conn->conn_accept_node); + mutex_unlock(&isert_np->np_accept_mutex); + + pr_debug("isert_connect_request() waking up np_accept_wq: %p\n", np); + wake_up(&isert_np->np_accept_wq); + return 0; + +out_conn_dev: + isert_device_try_release(device); +out_rsp_dma_map: + ib_dma_unmap_single(ib_dev, isert_conn->login_rsp_dma, + ISER_RX_LOGIN_SIZE, DMA_TO_DEVICE); +out_req_dma_map: + ib_dma_unmap_single(ib_dev, isert_conn->login_req_dma, + ISCSI_DEF_MAX_RECV_SEG_LEN, DMA_FROM_DEVICE); +out_login_buf: + kfree(isert_conn->login_buf); +out: + kfree(isert_conn); + return ret; +} + +static void +isert_connect_release(struct isert_conn *isert_conn) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct isert_device *device = isert_conn->conn_device; + int cq_index; + + pr_debug("Entering isert_connect_release(): >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + + if (isert_conn->conn_qp) { + cq_index = ((struct isert_cq_desc *) + isert_conn->conn_qp->recv_cq->cq_context)->cq_index; + pr_debug("isert_connect_release: cq_index: %d\n", cq_index); + isert_conn->conn_device->cq_active_qps[cq_index]--; + + rdma_destroy_qp(isert_conn->conn_cm_id); + } + + isert_free_rx_descriptors(isert_conn); + rdma_destroy_id(isert_conn->conn_cm_id); + + if (isert_conn->login_buf) { + ib_dma_unmap_single(ib_dev, isert_conn->login_rsp_dma, + ISER_RX_LOGIN_SIZE, DMA_TO_DEVICE); + ib_dma_unmap_single(ib_dev, isert_conn->login_req_dma, + ISCSI_DEF_MAX_RECV_SEG_LEN, + DMA_FROM_DEVICE); + kfree(isert_conn->login_buf); + } + kfree(isert_conn); + + if (device) + isert_device_try_release(device); + + pr_debug("Leaving isert_connect_release >>>>>>>>>>>>\n"); +} + +static void +isert_connected_handler(struct rdma_cm_id *cma_id) +{ + return; +} + +static void +isert_release_conn_kref(struct kref *kref) +{ + struct isert_conn *isert_conn = container_of(kref, + struct isert_conn, conn_kref); + + pr_debug("Calling isert_connect_release for final kref %s/%d\n", + current->comm, current->pid); + + isert_connect_release(isert_conn); +} + +static void +isert_put_conn(struct isert_conn *isert_conn) +{ + kref_put(&isert_conn->conn_kref, isert_release_conn_kref); +} + +static void +isert_disconnect_work(struct work_struct *work) +{ + struct isert_conn *isert_conn = container_of(work, + struct isert_conn, conn_logout_work); + + pr_debug("isert_disconnect_work(): >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + + isert_conn->state = ISER_CONN_DOWN; + + if (isert_conn->post_recv_buf_count == 0 && + atomic_read(&isert_conn->post_send_buf_count) == 0) { + pr_debug("Calling wake_up(&isert_conn->conn_wait);\n"); + wake_up(&isert_conn->conn_wait); + } + + isert_put_conn(isert_conn); +} + +static void +isert_disconnected_handler(struct rdma_cm_id *cma_id) +{ + struct isert_conn *isert_conn = (struct isert_conn *)cma_id->context; + + INIT_WORK(&isert_conn->conn_logout_work, isert_disconnect_work); + schedule_work(&isert_conn->conn_logout_work); +} + +static int +isert_cma_handler(struct rdma_cm_id *cma_id, struct rdma_cm_event *event) +{ + int ret = 0; + + pr_debug("isert_cma_handler: event %d status %d conn %p id %p\n", + event->event, event->status, cma_id->context, cma_id); + + switch (event->event) { + case RDMA_CM_EVENT_CONNECT_REQUEST: + pr_debug("RDMA_CM_EVENT_CONNECT_REQUEST: >>>>>>>>>>>>>>>\n"); + ret = isert_connect_request(cma_id, event); + break; + case RDMA_CM_EVENT_ESTABLISHED: + pr_debug("RDMA_CM_EVENT_ESTABLISHED >>>>>>>>>>>>>>\n"); + isert_connected_handler(cma_id); + break; + case RDMA_CM_EVENT_DISCONNECTED: + pr_debug("RDMA_CM_EVENT_DISCONNECTED: >>>>>>>>>>>>>>\n"); + isert_disconnected_handler(cma_id); + break; + case RDMA_CM_EVENT_DEVICE_REMOVAL: + case RDMA_CM_EVENT_ADDR_CHANGE: + break; + case RDMA_CM_EVENT_CONNECT_ERROR: + default: + pr_err("Unknown RDMA CMA event: %d\n", event->event); + break; + } + + if (ret != 0) { + pr_err("isert_cma_handler failed RDMA_CM_EVENT: 0x%08x %d\n", + event->event, ret); + dump_stack(); + } + + return ret; +} + +static int +isert_post_recv(struct isert_conn *isert_conn, u32 count) +{ + struct ib_recv_wr *rx_wr, *rx_wr_failed; + int i, ret; + unsigned int rx_head = isert_conn->conn_rx_desc_head; + struct iser_rx_desc *rx_desc; + + for (rx_wr = isert_conn->conn_rx_wr, i = 0; i < count; i++, rx_wr++) { + rx_desc = &isert_conn->conn_rx_descs[rx_head]; + rx_wr->wr_id = (unsigned long)rx_desc; + rx_wr->sg_list = &rx_desc->rx_sg; + rx_wr->num_sge = 1; + rx_wr->next = rx_wr + 1; + rx_head = (rx_head + 1) & (ISERT_QP_MAX_RECV_DTOS - 1); + } + + rx_wr--; + rx_wr->next = NULL; /* mark end of work requests list */ + + isert_conn->post_recv_buf_count += count; + ret = ib_post_recv(isert_conn->conn_qp, isert_conn->conn_rx_wr, + &rx_wr_failed); + if (ret) { + pr_err("ib_post_recv() failed with ret: %d\n", ret); + isert_conn->post_recv_buf_count -= count; + } else { + pr_debug("isert_post_recv(): Posted %d RX buffers\n", count); + isert_conn->conn_rx_desc_head = rx_head; + } + return ret; +} + +static int +isert_post_send(struct isert_conn *isert_conn, struct iser_tx_desc *tx_desc) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct ib_send_wr send_wr, *send_wr_failed; + int ret; + + ib_dma_sync_single_for_device(ib_dev, tx_desc->dma_addr, + ISER_HEADERS_LEN, DMA_TO_DEVICE); + + send_wr.next = NULL; + send_wr.wr_id = (unsigned long)tx_desc; + send_wr.sg_list = tx_desc->tx_sg; + send_wr.num_sge = tx_desc->num_sge; + send_wr.opcode = IB_WR_SEND; + send_wr.send_flags = IB_SEND_SIGNALED; + + atomic_inc(&isert_conn->post_send_buf_count); + + ret = ib_post_send(isert_conn->conn_qp, &send_wr, &send_wr_failed); + if (ret) { + pr_err("ib_post_send() failed, ret: %d\n", ret); + atomic_dec(&isert_conn->post_send_buf_count); + } + + return ret; +} + +static void +isert_create_send_desc(struct isert_conn *isert_conn, + struct isert_cmd *isert_cmd, + struct iser_tx_desc *tx_desc) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + + ib_dma_sync_single_for_cpu(ib_dev, tx_desc->dma_addr, + ISER_HEADERS_LEN, DMA_TO_DEVICE); + + memset(&tx_desc->iser_header, 0, sizeof(struct iser_hdr)); + tx_desc->iser_header.flags = ISER_VER; + + tx_desc->num_sge = 1; + tx_desc->isert_cmd = isert_cmd; + + if (tx_desc->tx_sg[0].lkey != isert_conn->conn_mr->lkey) { + tx_desc->tx_sg[0].lkey = isert_conn->conn_mr->lkey; + pr_debug("tx_desc %p lkey mismatch, fixing\n", tx_desc); + } +} + +static int +isert_init_tx_hdrs(struct isert_conn *isert_conn, + struct iser_tx_desc *tx_desc) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + u64 dma_addr; + + dma_addr = ib_dma_map_single(ib_dev, (void *)tx_desc, + ISER_HEADERS_LEN, DMA_TO_DEVICE); + if (ib_dma_mapping_error(ib_dev, dma_addr)) { + pr_err("ib_dma_mapping_error() failed\n"); + return -ENOMEM; + } + + tx_desc->dma_addr = dma_addr; + tx_desc->tx_sg[0].addr = tx_desc->dma_addr; + tx_desc->tx_sg[0].length = ISER_HEADERS_LEN; + tx_desc->tx_sg[0].lkey = isert_conn->conn_mr->lkey; + + pr_debug("isert_init_tx_hdrs: Setup tx_sg[0].addr: 0x%llx length: %u" + " lkey: 0x%08x\n", tx_desc->tx_sg[0].addr, + tx_desc->tx_sg[0].length, tx_desc->tx_sg[0].lkey); + + return 0; +} + +static void +isert_init_send_wr(struct isert_cmd *isert_cmd, struct ib_send_wr *send_wr) +{ + isert_cmd->rdma_wr.iser_ib_op = ISER_IB_SEND; + send_wr->wr_id = (unsigned long)&isert_cmd->tx_desc; + send_wr->opcode = IB_WR_SEND; + send_wr->send_flags = IB_SEND_SIGNALED; + send_wr->sg_list = &isert_cmd->tx_desc.tx_sg[0]; + send_wr->num_sge = isert_cmd->tx_desc.num_sge; +} + +static int +isert_rdma_post_recvl(struct isert_conn *isert_conn) +{ + struct ib_recv_wr rx_wr, *rx_wr_fail; + struct ib_sge sge; + int ret; + + memset(&sge, 0, sizeof(struct ib_sge)); + sge.addr = isert_conn->login_req_dma; + sge.length = ISER_RX_LOGIN_SIZE; + sge.lkey = isert_conn->conn_mr->lkey; + + pr_debug("Setup sge: addr: %llx length: %d 0x%08x\n", + sge.addr, sge.length, sge.lkey); + + memset(&rx_wr, 0, sizeof(struct ib_recv_wr)); + rx_wr.wr_id = (unsigned long)isert_conn->login_req_buf; + rx_wr.sg_list = &sge; + rx_wr.num_sge = 1; + + isert_conn->post_recv_buf_count++; + ret = ib_post_recv(isert_conn->conn_qp, &rx_wr, &rx_wr_fail); + if (ret) { + pr_err("ib_post_recv() failed: %d\n", ret); + isert_conn->post_recv_buf_count--; + } + + pr_debug("ib_post_recv(): returned success >>>>>>>>>>>>>>>>>>>>>>>>\n"); + return ret; +} + +static int +isert_put_login_tx(struct iscsi_conn *conn, struct iscsi_login *login, + u32 length) +{ + struct isert_conn *isert_conn = conn->context; + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct iser_tx_desc *tx_desc = &isert_conn->conn_login_tx_desc; + int ret; + + isert_create_send_desc(isert_conn, NULL, tx_desc); + + memcpy(&tx_desc->iscsi_header, &login->rsp[0], + sizeof(struct iscsi_hdr)); + + isert_init_tx_hdrs(isert_conn, tx_desc); + + if (length > 0) { + struct ib_sge *tx_dsg = &tx_desc->tx_sg[1]; + + ib_dma_sync_single_for_cpu(ib_dev, isert_conn->login_rsp_dma, + length, DMA_TO_DEVICE); + + memcpy(isert_conn->login_rsp_buf, login->rsp_buf, length); + + ib_dma_sync_single_for_device(ib_dev, isert_conn->login_rsp_dma, + length, DMA_TO_DEVICE); + + tx_dsg->addr = isert_conn->login_rsp_dma; + tx_dsg->length = length; + tx_dsg->lkey = isert_conn->conn_mr->lkey; + tx_desc->num_sge = 2; + } + if (!login->login_failed) { + if (login->login_complete) { + ret = isert_alloc_rx_descriptors(isert_conn); + if (ret) + return ret; + + ret = isert_post_recv(isert_conn, ISERT_MIN_POSTED_RX); + if (ret) + return ret; + + isert_conn->state = ISER_CONN_UP; + goto post_send; + } + + ret = isert_rdma_post_recvl(isert_conn); + if (ret) + return ret; + } +post_send: + ret = isert_post_send(isert_conn, tx_desc); + if (ret) + return ret; + + return 0; +} + +static void +isert_rx_login_req(struct iser_rx_desc *rx_desc, int rx_buflen, + struct isert_conn *isert_conn) +{ + struct iscsi_conn *conn = isert_conn->conn; + struct iscsi_login *login = conn->conn_login; + int size; + + if (!login) { + pr_err("conn->conn_login is NULL\n"); + dump_stack(); + return; + } + + if (login->first_request) { + struct iscsi_login_req *login_req = + (struct iscsi_login_req *)&rx_desc->iscsi_header; + /* + * Setup the initial iscsi_login values from the leading + * login request PDU. + */ + login->leading_connection = (!login_req->tsih) ? 1 : 0; + login->current_stage = + (login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) + >> 2; + login->version_min = login_req->min_version; + login->version_max = login_req->max_version; + memcpy(login->isid, login_req->isid, 6); + login->cmd_sn = be32_to_cpu(login_req->cmdsn); + login->init_task_tag = login_req->itt; + login->initial_exp_statsn = be32_to_cpu(login_req->exp_statsn); + login->cid = be16_to_cpu(login_req->cid); + login->tsih = be16_to_cpu(login_req->tsih); + } + + memcpy(&login->req[0], (void *)&rx_desc->iscsi_header, ISCSI_HDR_LEN); + + size = min(rx_buflen, MAX_KEY_VALUE_PAIRS); + pr_debug("Using login payload size: %d, rx_buflen: %d MAX_KEY_VALUE_PAIRS: %d\n", + size, rx_buflen, MAX_KEY_VALUE_PAIRS); + memcpy(login->req_buf, &rx_desc->data[0], size); + + complete(&isert_conn->conn_login_comp); +} + +static void +isert_release_cmd(struct iscsi_cmd *cmd) +{ + struct isert_cmd *isert_cmd = container_of(cmd, struct isert_cmd, + iscsi_cmd); + + pr_debug("Entering isert_release_cmd %p >>>>>>>>>>>>>>>.\n", isert_cmd); + + kfree(cmd->buf_ptr); + kfree(cmd->tmr_req); + + kmem_cache_free(isert_cmd_cache, isert_cmd); +} + +static struct iscsi_cmd +*isert_alloc_cmd(struct iscsi_conn *conn, gfp_t gfp) +{ + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + struct isert_cmd *isert_cmd; + + isert_cmd = kmem_cache_zalloc(isert_cmd_cache, gfp); + if (!isert_cmd) { + pr_err("Unable to allocate isert_cmd\n"); + return NULL; + } + isert_cmd->conn = isert_conn; + isert_cmd->iscsi_cmd.release_cmd = &isert_release_cmd; + + return &isert_cmd->iscsi_cmd; +} + +static int +isert_handle_scsi_cmd(struct isert_conn *isert_conn, + struct isert_cmd *isert_cmd, struct iser_rx_desc *rx_desc, + unsigned char *buf) +{ + struct iscsi_cmd *cmd = &isert_cmd->iscsi_cmd; + struct iscsi_conn *conn = isert_conn->conn; + struct iscsi_scsi_req *hdr = (struct iscsi_scsi_req *)buf; + struct scatterlist *sg; + int imm_data, imm_data_len, unsol_data, sg_nents, rc; + bool dump_payload = false; + + rc = iscsit_setup_scsi_cmd(conn, cmd, buf); + if (rc < 0) + return rc; + + imm_data = cmd->immediate_data; + imm_data_len = cmd->first_burst_len; + unsol_data = cmd->unsolicited_data; + + rc = iscsit_process_scsi_cmd(conn, cmd, hdr); + if (rc < 0) { + return 0; + } else if (rc > 0) { + dump_payload = true; + goto sequence_cmd; + } + + if (!imm_data) + return 0; + + sg = &cmd->se_cmd.t_data_sg[0]; + sg_nents = max(1UL, DIV_ROUND_UP(imm_data_len, PAGE_SIZE)); + + pr_debug("Copying Immediate SG: %p sg_nents: %u from %p imm_data_len: %d\n", + sg, sg_nents, &rx_desc->data[0], imm_data_len); + + sg_copy_from_buffer(sg, sg_nents, &rx_desc->data[0], imm_data_len); + + cmd->write_data_done += imm_data_len; + + if (cmd->write_data_done == cmd->se_cmd.data_length) { + spin_lock_bh(&cmd->istate_lock); + cmd->cmd_flags |= ICF_GOT_LAST_DATAOUT; + cmd->i_state = ISTATE_RECEIVED_LAST_DATAOUT; + spin_unlock_bh(&cmd->istate_lock); + } + +sequence_cmd: + rc = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); + + if (!rc && dump_payload == false && unsol_data) + iscsit_set_unsoliticed_dataout(cmd); + + if (rc == CMDSN_ERROR_CANNOT_RECOVER) + return iscsit_add_reject_from_cmd( + ISCSI_REASON_PROTOCOL_ERROR, + 1, 0, (unsigned char *)hdr, cmd); + + return 0; +} + +static int +isert_handle_iscsi_dataout(struct isert_conn *isert_conn, + struct iser_rx_desc *rx_desc, unsigned char *buf) +{ + struct scatterlist *sg_start; + struct iscsi_conn *conn = isert_conn->conn; + struct iscsi_cmd *cmd = NULL; + struct iscsi_data *hdr = (struct iscsi_data *)buf; + u32 unsol_data_len = ntoh24(hdr->dlength); + int rc, sg_nents, sg_off, page_off; + + rc = iscsit_check_dataout_hdr(conn, buf, &cmd); + if (rc < 0) + return rc; + else if (!cmd) + return 0; + /* + * FIXME: Unexpected unsolicited_data out + */ + if (!cmd->unsolicited_data) { + pr_err("Received unexpected solicited data payload\n"); + dump_stack(); + return -1; + } + + pr_debug("Unsolicited DataOut unsol_data_len: %u, write_data_done: %u, data_length: %u\n", + unsol_data_len, cmd->write_data_done, cmd->se_cmd.data_length); + + sg_off = cmd->write_data_done / PAGE_SIZE; + sg_start = &cmd->se_cmd.t_data_sg[sg_off]; + sg_nents = max(1UL, DIV_ROUND_UP(unsol_data_len, PAGE_SIZE)); + page_off = cmd->write_data_done % PAGE_SIZE; + /* + * FIXME: Non page-aligned unsolicited_data out + */ + if (page_off) { + pr_err("Received unexpected non-page aligned data payload\n"); + dump_stack(); + return -1; + } + pr_debug("Copying DataOut: sg_start: %p, sg_off: %u sg_nents: %u from %p %u\n", + sg_start, sg_off, sg_nents, &rx_desc->data[0], unsol_data_len); + + sg_copy_from_buffer(sg_start, sg_nents, &rx_desc->data[0], + unsol_data_len); + + rc = iscsit_check_dataout_payload(cmd, hdr, false); + if (rc < 0) + return rc; + + return 0; +} + +static int +isert_rx_opcode(struct isert_conn *isert_conn, struct iser_rx_desc *rx_desc, + uint32_t read_stag, uint64_t read_va, + uint32_t write_stag, uint64_t write_va) +{ + struct iscsi_hdr *hdr = &rx_desc->iscsi_header; + struct iscsi_conn *conn = isert_conn->conn; + struct iscsi_cmd *cmd; + struct isert_cmd *isert_cmd; + int ret = -EINVAL; + u8 opcode = (hdr->opcode & ISCSI_OPCODE_MASK); + + switch (opcode) { + case ISCSI_OP_SCSI_CMD: + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + break; + + isert_cmd = container_of(cmd, struct isert_cmd, iscsi_cmd); + isert_cmd->read_stag = read_stag; + isert_cmd->read_va = read_va; + isert_cmd->write_stag = write_stag; + isert_cmd->write_va = write_va; + + ret = isert_handle_scsi_cmd(isert_conn, isert_cmd, + rx_desc, (unsigned char *)hdr); + break; + case ISCSI_OP_NOOP_OUT: + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + break; + + ret = iscsit_handle_nop_out(conn, cmd, (unsigned char *)hdr); + break; + case ISCSI_OP_SCSI_DATA_OUT: + ret = isert_handle_iscsi_dataout(isert_conn, rx_desc, + (unsigned char *)hdr); + break; + case ISCSI_OP_SCSI_TMFUNC: + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + break; + + ret = iscsit_handle_task_mgt_cmd(conn, cmd, + (unsigned char *)hdr); + break; + case ISCSI_OP_LOGOUT: + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + break; + + ret = iscsit_handle_logout_cmd(conn, cmd, (unsigned char *)hdr); + if (ret > 0) + wait_for_completion_timeout(&conn->conn_logout_comp, + SECONDS_FOR_LOGOUT_COMP * + HZ); + break; + default: + pr_err("Got unknown iSCSI OpCode: 0x%02x\n", opcode); + dump_stack(); + break; + } + + return ret; +} + +static void +isert_rx_do_work(struct iser_rx_desc *rx_desc, struct isert_conn *isert_conn) +{ + struct iser_hdr *iser_hdr = &rx_desc->iser_header; + uint64_t read_va = 0, write_va = 0; + uint32_t read_stag = 0, write_stag = 0; + int rc; + + switch (iser_hdr->flags & 0xF0) { + case ISCSI_CTRL: + if (iser_hdr->flags & ISER_RSV) { + read_stag = be32_to_cpu(iser_hdr->read_stag); + read_va = be64_to_cpu(iser_hdr->read_va); + pr_debug("ISER_RSV: read_stag: 0x%08x read_va: 0x%16llx\n", + read_stag, (unsigned long long)read_va); + } + if (iser_hdr->flags & ISER_WSV) { + write_stag = be32_to_cpu(iser_hdr->write_stag); + write_va = be64_to_cpu(iser_hdr->write_va); + pr_debug("ISER_WSV: write__stag: 0x%08x write_va: 0x%16llx\n", + write_stag, (unsigned long long)write_va); + } + + pr_debug("ISER ISCSI_CTRL PDU\n"); + break; + case ISER_HELLO: + pr_err("iSER Hello message\n"); + break; + default: + pr_warn("Unknown iSER hdr flags: 0x%02x\n", iser_hdr->flags); + break; + } + + rc = isert_rx_opcode(isert_conn, rx_desc, + read_stag, read_va, write_stag, write_va); +} + +static void +isert_rx_completion(struct iser_rx_desc *desc, struct isert_conn *isert_conn, + unsigned long xfer_len) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct iscsi_hdr *hdr; + u64 rx_dma; + int rx_buflen, outstanding; + + if ((char *)desc == isert_conn->login_req_buf) { + rx_dma = isert_conn->login_req_dma; + rx_buflen = ISER_RX_LOGIN_SIZE; + pr_debug("ISER login_buf: Using rx_dma: 0x%llx, rx_buflen: %d\n", + rx_dma, rx_buflen); + } else { + rx_dma = desc->dma_addr; + rx_buflen = ISER_RX_PAYLOAD_SIZE; + pr_debug("ISER req_buf: Using rx_dma: 0x%llx, rx_buflen: %d\n", + rx_dma, rx_buflen); + } + + ib_dma_sync_single_for_cpu(ib_dev, rx_dma, rx_buflen, DMA_FROM_DEVICE); + + hdr = &desc->iscsi_header; + pr_debug("iSCSI opcode: 0x%02x, ITT: 0x%08x, flags: 0x%02x dlen: %d\n", + hdr->opcode, hdr->itt, hdr->flags, + (int)(xfer_len - ISER_HEADERS_LEN)); + + if ((char *)desc == isert_conn->login_req_buf) + isert_rx_login_req(desc, xfer_len - ISER_HEADERS_LEN, + isert_conn); + else + isert_rx_do_work(desc, isert_conn); + + ib_dma_sync_single_for_device(ib_dev, rx_dma, rx_buflen, + DMA_FROM_DEVICE); + + isert_conn->post_recv_buf_count--; + pr_debug("iSERT: Decremented post_recv_buf_count: %d\n", + isert_conn->post_recv_buf_count); + + if ((char *)desc == isert_conn->login_req_buf) + return; + + outstanding = isert_conn->post_recv_buf_count; + if (outstanding + ISERT_MIN_POSTED_RX <= ISERT_QP_MAX_RECV_DTOS) { + int err, count = min(ISERT_QP_MAX_RECV_DTOS - outstanding, + ISERT_MIN_POSTED_RX); + err = isert_post_recv(isert_conn, count); + if (err) { + pr_err("isert_post_recv() count: %d failed, %d\n", + count, err); + } + } +} + +static void +isert_unmap_cmd(struct isert_cmd *isert_cmd, struct isert_conn *isert_conn) +{ + struct isert_rdma_wr *wr = &isert_cmd->rdma_wr; + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + + pr_debug("isert_unmap_cmd >>>>>>>>>>>>>>>>>>>>>>>\n"); + + if (wr->sge) { + ib_dma_unmap_sg(ib_dev, wr->sge, wr->num_sge, DMA_TO_DEVICE); + wr->sge = NULL; + } + + kfree(wr->send_wr); + wr->send_wr = NULL; + + kfree(isert_cmd->ib_sge); + isert_cmd->ib_sge = NULL; +} + +static void +isert_put_cmd(struct isert_cmd *isert_cmd) +{ + struct iscsi_cmd *cmd = &isert_cmd->iscsi_cmd; + struct isert_conn *isert_conn = isert_cmd->conn; + struct iscsi_conn *conn; + + pr_debug("Entering isert_put_cmd: %p\n", isert_cmd); + + switch (cmd->iscsi_opcode) { + case ISCSI_OP_SCSI_CMD: + conn = isert_conn->conn; + + spin_lock_bh(&conn->cmd_lock); + if (!list_empty(&cmd->i_conn_node)) + list_del(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); + + if (cmd->data_direction == DMA_TO_DEVICE) + iscsit_stop_dataout_timer(cmd); + + isert_unmap_cmd(isert_cmd, isert_conn); + /* + * Fall-through + */ + case ISCSI_OP_SCSI_TMFUNC: + transport_generic_free_cmd(&cmd->se_cmd, 0); + break; + case ISCSI_OP_REJECT: + case ISCSI_OP_NOOP_OUT: + conn = isert_conn->conn; + + spin_lock_bh(&conn->cmd_lock); + if (!list_empty(&cmd->i_conn_node)) + list_del(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); + + /* + * Handle special case for REJECT when iscsi_add_reject*() has + * overwritten the original iscsi_opcode assignment, and the + * associated cmd->se_cmd needs to be released. + */ + if (cmd->se_cmd.se_tfo != NULL) { + transport_generic_free_cmd(&cmd->se_cmd, 0); + break; + } + /* + * Fall-through + */ + default: + isert_release_cmd(cmd); + break; + } +} + +static void +isert_unmap_tx_desc(struct iser_tx_desc *tx_desc, struct ib_device *ib_dev) +{ + if (tx_desc->dma_addr != 0) { + pr_debug("Calling ib_dma_unmap_single for tx_desc->dma_addr\n"); + ib_dma_unmap_single(ib_dev, tx_desc->dma_addr, + ISER_HEADERS_LEN, DMA_TO_DEVICE); + tx_desc->dma_addr = 0; + } +} + +static void +isert_completion_put(struct iser_tx_desc *tx_desc, struct isert_cmd *isert_cmd, + struct ib_device *ib_dev) +{ + if (isert_cmd->sense_buf_dma != 0) { + pr_debug("Calling ib_dma_unmap_single for isert_cmd->sense_buf_dma\n"); + ib_dma_unmap_single(ib_dev, isert_cmd->sense_buf_dma, + isert_cmd->sense_buf_len, DMA_TO_DEVICE); + isert_cmd->sense_buf_dma = 0; + } + + isert_unmap_tx_desc(tx_desc, ib_dev); + isert_put_cmd(isert_cmd); +} + +static void +isert_completion_rdma_read(struct iser_tx_desc *tx_desc, + struct isert_cmd *isert_cmd) +{ + struct isert_rdma_wr *wr = &isert_cmd->rdma_wr; + struct iscsi_cmd *cmd = &isert_cmd->iscsi_cmd; + struct se_cmd *se_cmd = &cmd->se_cmd; + struct ib_device *ib_dev = isert_cmd->conn->conn_cm_id->device; + + iscsit_stop_dataout_timer(cmd); + + if (wr->sge) { + pr_debug("isert_do_rdma_read_comp: Unmapping wr->sge from t_data_sg\n"); + ib_dma_unmap_sg(ib_dev, wr->sge, wr->num_sge, DMA_TO_DEVICE); + wr->sge = NULL; + } + + if (isert_cmd->ib_sge) { + pr_debug("isert_do_rdma_read_comp: Freeing isert_cmd->ib_sge\n"); + kfree(isert_cmd->ib_sge); + isert_cmd->ib_sge = NULL; + } + + cmd->write_data_done = se_cmd->data_length; + + pr_debug("isert_do_rdma_read_comp, calling target_execute_cmd\n"); + spin_lock_bh(&cmd->istate_lock); + cmd->cmd_flags |= ICF_GOT_LAST_DATAOUT; + cmd->i_state = ISTATE_RECEIVED_LAST_DATAOUT; + spin_unlock_bh(&cmd->istate_lock); + + target_execute_cmd(se_cmd); +} + +static void +isert_do_control_comp(struct work_struct *work) +{ + struct isert_cmd *isert_cmd = container_of(work, + struct isert_cmd, comp_work); + struct isert_conn *isert_conn = isert_cmd->conn; + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct iscsi_cmd *cmd = &isert_cmd->iscsi_cmd; + + switch (cmd->i_state) { + case ISTATE_SEND_TASKMGTRSP: + pr_debug("Calling iscsit_tmr_post_handler >>>>>>>>>>>>>>>>>\n"); + + atomic_dec(&isert_conn->post_send_buf_count); + iscsit_tmr_post_handler(cmd, cmd->conn); + + cmd->i_state = ISTATE_SENT_STATUS; + isert_completion_put(&isert_cmd->tx_desc, isert_cmd, ib_dev); + break; + case ISTATE_SEND_REJECT: + pr_debug("Got isert_do_control_comp ISTATE_SEND_REJECT: >>>\n"); + atomic_dec(&isert_conn->post_send_buf_count); + + cmd->i_state = ISTATE_SENT_STATUS; + complete(&cmd->reject_comp); + isert_completion_put(&isert_cmd->tx_desc, isert_cmd, ib_dev); + case ISTATE_SEND_LOGOUTRSP: + pr_debug("Calling iscsit_logout_post_handler >>>>>>>>>>>>>>\n"); + /* + * Call atomic_dec(&isert_conn->post_send_buf_count) + * from isert_free_conn() + */ + isert_conn->logout_posted = true; + iscsit_logout_post_handler(cmd, cmd->conn); + break; + default: + pr_err("Unknown do_control_comp i_state %d\n", cmd->i_state); + dump_stack(); + break; + } +} + +static void +isert_response_completion(struct iser_tx_desc *tx_desc, + struct isert_cmd *isert_cmd, + struct isert_conn *isert_conn, + struct ib_device *ib_dev) +{ + struct iscsi_cmd *cmd = &isert_cmd->iscsi_cmd; + + if (cmd->i_state == ISTATE_SEND_TASKMGTRSP || + cmd->i_state == ISTATE_SEND_LOGOUTRSP) { + isert_unmap_tx_desc(tx_desc, ib_dev); + + INIT_WORK(&isert_cmd->comp_work, isert_do_control_comp); + queue_work(isert_comp_wq, &isert_cmd->comp_work); + return; + } + atomic_dec(&isert_conn->post_send_buf_count); + + cmd->i_state = ISTATE_SENT_STATUS; + isert_completion_put(tx_desc, isert_cmd, ib_dev); +} + +static void +isert_send_completion(struct iser_tx_desc *tx_desc, + struct isert_conn *isert_conn) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct isert_cmd *isert_cmd = tx_desc->isert_cmd; + struct isert_rdma_wr *wr; + + if (!isert_cmd) { + atomic_dec(&isert_conn->post_send_buf_count); + isert_unmap_tx_desc(tx_desc, ib_dev); + return; + } + wr = &isert_cmd->rdma_wr; + + switch (wr->iser_ib_op) { + case ISER_IB_RECV: + pr_err("isert_send_completion: Got ISER_IB_RECV\n"); + dump_stack(); + break; + case ISER_IB_SEND: + pr_debug("isert_send_completion: Got ISER_IB_SEND\n"); + isert_response_completion(tx_desc, isert_cmd, + isert_conn, ib_dev); + break; + case ISER_IB_RDMA_WRITE: + pr_err("isert_send_completion: Got ISER_IB_RDMA_WRITE\n"); + dump_stack(); + break; + case ISER_IB_RDMA_READ: + pr_debug("isert_send_completion: Got ISER_IB_RDMA_READ:\n"); + + atomic_dec(&isert_conn->post_send_buf_count); + isert_completion_rdma_read(tx_desc, isert_cmd); + break; + default: + pr_err("Unknown wr->iser_ib_op: 0x%02x\n", wr->iser_ib_op); + dump_stack(); + break; + } +} + +static void +isert_cq_comp_err(struct iser_tx_desc *tx_desc, struct isert_conn *isert_conn) +{ + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + + if (tx_desc) { + struct isert_cmd *isert_cmd = tx_desc->isert_cmd; + + if (!isert_cmd) + isert_unmap_tx_desc(tx_desc, ib_dev); + else + isert_completion_put(tx_desc, isert_cmd, ib_dev); + } + + if (isert_conn->post_recv_buf_count == 0 && + atomic_read(&isert_conn->post_send_buf_count) == 0) { + pr_debug("isert_cq_comp_err >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>\n"); + pr_debug("Calling wake_up from isert_cq_comp_err\n"); + + isert_conn->state = ISER_CONN_TERMINATING; + wake_up(&isert_conn->conn_wait_comp_err); + } +} + +static void +isert_cq_tx_work(struct work_struct *work) +{ + struct isert_cq_desc *cq_desc = container_of(work, + struct isert_cq_desc, cq_tx_work); + struct isert_device *device = cq_desc->device; + int cq_index = cq_desc->cq_index; + struct ib_cq *tx_cq = device->dev_tx_cq[cq_index]; + struct isert_conn *isert_conn; + struct iser_tx_desc *tx_desc; + struct ib_wc wc; + + while (ib_poll_cq(tx_cq, 1, &wc) == 1) { + tx_desc = (struct iser_tx_desc *)(unsigned long)wc.wr_id; + isert_conn = wc.qp->qp_context; + + if (wc.status == IB_WC_SUCCESS) { + isert_send_completion(tx_desc, isert_conn); + } else { + pr_debug("TX wc.status != IB_WC_SUCCESS >>>>>>>>>>>>>>\n"); + pr_debug("TX wc.status: 0x%08x\n", wc.status); + atomic_dec(&isert_conn->post_send_buf_count); + isert_cq_comp_err(tx_desc, isert_conn); + } + } + + ib_req_notify_cq(tx_cq, IB_CQ_NEXT_COMP); +} + +static void +isert_cq_tx_callback(struct ib_cq *cq, void *context) +{ + struct isert_cq_desc *cq_desc = (struct isert_cq_desc *)context; + + INIT_WORK(&cq_desc->cq_tx_work, isert_cq_tx_work); + queue_work(isert_comp_wq, &cq_desc->cq_tx_work); +} + +static void +isert_cq_rx_work(struct work_struct *work) +{ + struct isert_cq_desc *cq_desc = container_of(work, + struct isert_cq_desc, cq_rx_work); + struct isert_device *device = cq_desc->device; + int cq_index = cq_desc->cq_index; + struct ib_cq *rx_cq = device->dev_rx_cq[cq_index]; + struct isert_conn *isert_conn; + struct iser_rx_desc *rx_desc; + struct ib_wc wc; + unsigned long xfer_len; + + while (ib_poll_cq(rx_cq, 1, &wc) == 1) { + rx_desc = (struct iser_rx_desc *)(unsigned long)wc.wr_id; + isert_conn = wc.qp->qp_context; + + if (wc.status == IB_WC_SUCCESS) { + xfer_len = (unsigned long)wc.byte_len; + isert_rx_completion(rx_desc, isert_conn, xfer_len); + } else { + pr_debug("RX wc.status != IB_WC_SUCCESS >>>>>>>>>>>>>>\n"); + if (wc.status != IB_WC_WR_FLUSH_ERR) + pr_debug("RX wc.status: 0x%08x\n", wc.status); + + isert_conn->post_recv_buf_count--; + isert_cq_comp_err(NULL, isert_conn); + } + } + + ib_req_notify_cq(rx_cq, IB_CQ_NEXT_COMP); +} + +static void +isert_cq_rx_callback(struct ib_cq *cq, void *context) +{ + struct isert_cq_desc *cq_desc = (struct isert_cq_desc *)context; + + INIT_WORK(&cq_desc->cq_rx_work, isert_cq_rx_work); + queue_work(isert_rx_wq, &cq_desc->cq_rx_work); +} + +static int +isert_post_response(struct isert_conn *isert_conn, struct isert_cmd *isert_cmd) +{ + struct ib_send_wr *wr_failed; + int ret; + + atomic_inc(&isert_conn->post_send_buf_count); + + ret = ib_post_send(isert_conn->conn_qp, &isert_cmd->tx_desc.send_wr, + &wr_failed); + if (ret) { + pr_err("ib_post_send failed with %d\n", ret); + atomic_dec(&isert_conn->post_send_buf_count); + return ret; + } + return ret; +} + +static int +isert_put_response(struct iscsi_conn *conn, struct iscsi_cmd *cmd) +{ + struct isert_cmd *isert_cmd = container_of(cmd, + struct isert_cmd, iscsi_cmd); + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + struct ib_send_wr *send_wr = &isert_cmd->tx_desc.send_wr; + struct iscsi_scsi_rsp *hdr = (struct iscsi_scsi_rsp *) + &isert_cmd->tx_desc.iscsi_header; + + isert_create_send_desc(isert_conn, isert_cmd, &isert_cmd->tx_desc); + iscsit_build_rsp_pdu(cmd, conn, true, hdr); + isert_init_tx_hdrs(isert_conn, &isert_cmd->tx_desc); + /* + * Attach SENSE DATA payload to iSCSI Response PDU + */ + if (cmd->se_cmd.sense_buffer && + ((cmd->se_cmd.se_cmd_flags & SCF_TRANSPORT_TASK_SENSE) || + (cmd->se_cmd.se_cmd_flags & SCF_EMULATED_TASK_SENSE))) { + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct ib_sge *tx_dsg = &isert_cmd->tx_desc.tx_sg[1]; + u32 padding, sense_len; + + put_unaligned_be16(cmd->se_cmd.scsi_sense_length, + cmd->sense_buffer); + cmd->se_cmd.scsi_sense_length += sizeof(__be16); + + padding = -(cmd->se_cmd.scsi_sense_length) & 3; + hton24(hdr->dlength, (u32)cmd->se_cmd.scsi_sense_length); + sense_len = cmd->se_cmd.scsi_sense_length + padding; + + isert_cmd->sense_buf_dma = ib_dma_map_single(ib_dev, + (void *)cmd->sense_buffer, sense_len, + DMA_TO_DEVICE); + + isert_cmd->sense_buf_len = sense_len; + tx_dsg->addr = isert_cmd->sense_buf_dma; + tx_dsg->length = sense_len; + tx_dsg->lkey = isert_conn->conn_mr->lkey; + isert_cmd->tx_desc.num_sge = 2; + } + + isert_init_send_wr(isert_cmd, send_wr); + + pr_debug("Posting SCSI Response IB_WR_SEND >>>>>>>>>>>>>>>>>>>>>>\n"); + + return isert_post_response(isert_conn, isert_cmd); +} + +static int +isert_put_nopin(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + bool nopout_response) +{ + struct isert_cmd *isert_cmd = container_of(cmd, + struct isert_cmd, iscsi_cmd); + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + struct ib_send_wr *send_wr = &isert_cmd->tx_desc.send_wr; + + isert_create_send_desc(isert_conn, isert_cmd, &isert_cmd->tx_desc); + iscsit_build_nopin_rsp(cmd, conn, (struct iscsi_nopin *) + &isert_cmd->tx_desc.iscsi_header, + nopout_response); + isert_init_tx_hdrs(isert_conn, &isert_cmd->tx_desc); + isert_init_send_wr(isert_cmd, send_wr); + + pr_debug("Posting NOPIN Reponse IB_WR_SEND >>>>>>>>>>>>>>>>>>>>>>\n"); + + return isert_post_response(isert_conn, isert_cmd); +} + +static int +isert_put_logout_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct isert_cmd *isert_cmd = container_of(cmd, + struct isert_cmd, iscsi_cmd); + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + struct ib_send_wr *send_wr = &isert_cmd->tx_desc.send_wr; + + isert_create_send_desc(isert_conn, isert_cmd, &isert_cmd->tx_desc); + iscsit_build_logout_rsp(cmd, conn, (struct iscsi_logout_rsp *) + &isert_cmd->tx_desc.iscsi_header); + isert_init_tx_hdrs(isert_conn, &isert_cmd->tx_desc); + isert_init_send_wr(isert_cmd, send_wr); + + pr_debug("Posting Logout Response IB_WR_SEND >>>>>>>>>>>>>>>>>>>>>>\n"); + + return isert_post_response(isert_conn, isert_cmd); +} + +static int +isert_put_tm_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct isert_cmd *isert_cmd = container_of(cmd, + struct isert_cmd, iscsi_cmd); + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + struct ib_send_wr *send_wr = &isert_cmd->tx_desc.send_wr; + + isert_create_send_desc(isert_conn, isert_cmd, &isert_cmd->tx_desc); + iscsit_build_task_mgt_rsp(cmd, conn, (struct iscsi_tm_rsp *) + &isert_cmd->tx_desc.iscsi_header); + isert_init_tx_hdrs(isert_conn, &isert_cmd->tx_desc); + isert_init_send_wr(isert_cmd, send_wr); + + pr_debug("Posting Task Management Response IB_WR_SEND >>>>>>>>>>>>>>>>>>>>>>\n"); + + return isert_post_response(isert_conn, isert_cmd); +} + +static int +isert_put_reject(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct isert_cmd *isert_cmd = container_of(cmd, + struct isert_cmd, iscsi_cmd); + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + struct ib_send_wr *send_wr = &isert_cmd->tx_desc.send_wr; + + isert_create_send_desc(isert_conn, isert_cmd, &isert_cmd->tx_desc); + iscsit_build_reject(cmd, conn, (struct iscsi_reject *) + &isert_cmd->tx_desc.iscsi_header); + isert_init_tx_hdrs(isert_conn, &isert_cmd->tx_desc); + isert_init_send_wr(isert_cmd, send_wr); + + pr_debug("Posting Reject IB_WR_SEND >>>>>>>>>>>>>>>>>>>>>>\n"); + + return isert_post_response(isert_conn, isert_cmd); +} + +static int +isert_build_rdma_wr(struct isert_conn *isert_conn, struct isert_cmd *isert_cmd, + struct ib_sge *ib_sge, struct ib_send_wr *send_wr, + u32 data_left, u32 offset) +{ + struct iscsi_cmd *cmd = &isert_cmd->iscsi_cmd; + struct scatterlist *sg_start, *tmp_sg; + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + u32 sg_off, page_off; + int i = 0, sg_nents; + + sg_off = offset / PAGE_SIZE; + sg_start = &cmd->se_cmd.t_data_sg[sg_off]; + sg_nents = min(cmd->se_cmd.t_data_nents - sg_off, isert_conn->max_sge); + page_off = offset % PAGE_SIZE; + + send_wr->sg_list = ib_sge; + send_wr->num_sge = sg_nents; + send_wr->wr_id = (unsigned long)&isert_cmd->tx_desc; + /* + * Perform mapping of TCM scatterlist memory ib_sge dma_addr. + */ + for_each_sg(sg_start, tmp_sg, sg_nents, i) { + pr_debug("ISER RDMA from SGL dma_addr: 0x%16llx dma_len: %u, page_off: %u\n", + (unsigned long long)tmp_sg->dma_address, + tmp_sg->length, page_off); + + ib_sge->addr = ib_sg_dma_address(ib_dev, tmp_sg) + page_off; + ib_sge->length = min_t(u32, data_left, + ib_sg_dma_len(ib_dev, tmp_sg) - page_off); + ib_sge->lkey = isert_conn->conn_mr->lkey; + + pr_debug("RDMA ib_sge: addr: 0x%16llx length: %u\n", + ib_sge->addr, ib_sge->length); + page_off = 0; + data_left -= ib_sge->length; + ib_sge++; + pr_debug("Incrementing ib_sge pointer to %p\n", ib_sge); + } + + pr_debug("Set outgoing sg_list: %p num_sg: %u from TCM SGLs\n", + send_wr->sg_list, send_wr->num_sge); + + return sg_nents; +} + +static int +isert_put_datain(struct iscsi_conn *conn, struct iscsi_cmd *cmd) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct isert_cmd *isert_cmd = container_of(cmd, + struct isert_cmd, iscsi_cmd); + struct isert_rdma_wr *wr = &isert_cmd->rdma_wr; + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + struct ib_send_wr *wr_failed, *send_wr; + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct ib_sge *ib_sge; + struct scatterlist *sg; + u32 offset = 0, data_len, data_left, rdma_write_max; + int rc, ret = 0, count, sg_nents, i, ib_sge_cnt; + + pr_debug("RDMA_WRITE: data_length: %u\n", se_cmd->data_length); + + sg = &se_cmd->t_data_sg[0]; + sg_nents = se_cmd->t_data_nents; + + count = ib_dma_map_sg(ib_dev, sg, sg_nents, DMA_TO_DEVICE); + if (unlikely(!count)) { + pr_err("Unable to map put_datain SGs\n"); + return -EINVAL; + } + wr->sge = sg; + wr->num_sge = sg_nents; + pr_debug("Mapped IB count: %u sg: %p sg_nents: %u for RDMA_WRITE\n", + count, sg, sg_nents); + + ib_sge = kzalloc(sizeof(struct ib_sge) * sg_nents, GFP_KERNEL); + if (!ib_sge) { + pr_warn("Unable to allocate datain ib_sge\n"); + ret = -ENOMEM; + goto unmap_sg; + } + isert_cmd->ib_sge = ib_sge; + + pr_debug("Allocated ib_sge: %p from t_data_ents: %d for RDMA_WRITE\n", + ib_sge, se_cmd->t_data_nents); + + wr->send_wr_num = DIV_ROUND_UP(sg_nents, isert_conn->max_sge); + wr->send_wr = kzalloc(sizeof(struct ib_send_wr) * wr->send_wr_num, + GFP_KERNEL); + if (!wr->send_wr) { + pr_err("Unable to allocate wr->send_wr\n"); + ret = -ENOMEM; + goto unmap_sg; + } + pr_debug("Allocated wr->send_wr: %p wr->send_wr_num: %u\n", + wr->send_wr, wr->send_wr_num); + + iscsit_increment_maxcmdsn(cmd, conn->sess); + cmd->stat_sn = conn->stat_sn++; + + wr->isert_cmd = isert_cmd; + rdma_write_max = isert_conn->max_sge * PAGE_SIZE; + data_left = se_cmd->data_length; + + for (i = 0; i < wr->send_wr_num; i++) { + send_wr = &isert_cmd->rdma_wr.send_wr[i]; + data_len = min(data_left, rdma_write_max); + + send_wr->opcode = IB_WR_RDMA_WRITE; + send_wr->send_flags = 0; + send_wr->wr.rdma.remote_addr = isert_cmd->read_va + offset; + send_wr->wr.rdma.rkey = isert_cmd->read_stag; + + ib_sge_cnt = isert_build_rdma_wr(isert_conn, isert_cmd, ib_sge, + send_wr, data_len, offset); + ib_sge += ib_sge_cnt; + + if (i + 1 == wr->send_wr_num) + send_wr->next = &isert_cmd->tx_desc.send_wr; + else + send_wr->next = &wr->send_wr[i + 1]; + + offset += data_len; + data_left -= data_len; + } + /* + * Build isert_conn->tx_desc for iSCSI response PDU and attach + */ + isert_create_send_desc(isert_conn, isert_cmd, &isert_cmd->tx_desc); + iscsit_build_rsp_pdu(cmd, conn, false, (struct iscsi_scsi_rsp *) + &isert_cmd->tx_desc.iscsi_header); + isert_init_tx_hdrs(isert_conn, &isert_cmd->tx_desc); + isert_init_send_wr(isert_cmd, &isert_cmd->tx_desc.send_wr); + + atomic_inc(&isert_conn->post_send_buf_count); + + rc = ib_post_send(isert_conn->conn_qp, wr->send_wr, &wr_failed); + if (rc) { + pr_warn("ib_post_send() failed for IB_WR_RDMA_WRITE\n"); + atomic_dec(&isert_conn->post_send_buf_count); + } + pr_debug("Posted RDMA_WRITE + Response for iSER Data READ\n"); + return 1; + +unmap_sg: + ib_dma_unmap_sg(ib_dev, sg, sg_nents, DMA_TO_DEVICE); + return ret; +} + +static int +isert_get_dataout(struct iscsi_conn *conn, struct iscsi_cmd *cmd, bool recovery) +{ + struct se_cmd *se_cmd = &cmd->se_cmd; + struct isert_cmd *isert_cmd = container_of(cmd, + struct isert_cmd, iscsi_cmd); + struct isert_rdma_wr *wr = &isert_cmd->rdma_wr; + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + struct ib_send_wr *wr_failed, *send_wr; + struct ib_sge *ib_sge; + struct ib_device *ib_dev = isert_conn->conn_cm_id->device; + struct scatterlist *sg_start; + u32 sg_off, sg_nents, page_off, va_offset = 0; + u32 offset = 0, data_len, data_left, rdma_write_max; + int rc, ret = 0, count, i, ib_sge_cnt; + + pr_debug("RDMA_READ: data_length: %u write_data_done: %u\n", + se_cmd->data_length, cmd->write_data_done); + + sg_off = cmd->write_data_done / PAGE_SIZE; + sg_start = &cmd->se_cmd.t_data_sg[sg_off]; + page_off = cmd->write_data_done % PAGE_SIZE; + + pr_debug("RDMA_READ: sg_off: %d, sg_start: %p page_off: %d\n", + sg_off, sg_start, page_off); + + data_left = se_cmd->data_length - cmd->write_data_done; + sg_nents = se_cmd->t_data_nents - sg_off; + + pr_debug("RDMA_READ: data_left: %d, sg_nents: %d\n", + data_left, sg_nents); + + count = ib_dma_map_sg(ib_dev, sg_start, sg_nents, DMA_FROM_DEVICE); + if (unlikely(!count)) { + pr_err("Unable to map get_dataout SGs\n"); + return -EINVAL; + } + wr->sge = sg_start; + wr->num_sge = sg_nents; + pr_debug("Mapped IB count: %u sg_start: %p sg_nents: %u for RDMA_READ\n", + count, sg_start, sg_nents); + + ib_sge = kzalloc(sizeof(struct ib_sge) * sg_nents, GFP_KERNEL); + if (!ib_sge) { + pr_warn("Unable to allocate dataout ib_sge\n"); + ret = -ENOMEM; + goto unmap_sg; + } + isert_cmd->ib_sge = ib_sge; + + pr_debug("Using ib_sge: %p from sg_ents: %d for RDMA_READ\n", + ib_sge, sg_nents); + + wr->send_wr_num = DIV_ROUND_UP(sg_nents, isert_conn->max_sge); + wr->send_wr = kzalloc(sizeof(struct ib_send_wr) * wr->send_wr_num, + GFP_KERNEL); + if (!wr->send_wr) { + pr_debug("Unable to allocate wr->send_wr\n"); + ret = -ENOMEM; + goto unmap_sg; + } + pr_debug("Allocated wr->send_wr: %p wr->send_wr_num: %u\n", + wr->send_wr, wr->send_wr_num); + + isert_cmd->tx_desc.isert_cmd = isert_cmd; + + wr->iser_ib_op = ISER_IB_RDMA_READ; + wr->isert_cmd = isert_cmd; + rdma_write_max = isert_conn->max_sge * PAGE_SIZE; + offset = cmd->write_data_done; + + for (i = 0; i < wr->send_wr_num; i++) { + send_wr = &isert_cmd->rdma_wr.send_wr[i]; + data_len = min(data_left, rdma_write_max); + + send_wr->opcode = IB_WR_RDMA_READ; + send_wr->wr.rdma.remote_addr = isert_cmd->write_va + va_offset; + send_wr->wr.rdma.rkey = isert_cmd->write_stag; + + ib_sge_cnt = isert_build_rdma_wr(isert_conn, isert_cmd, ib_sge, + send_wr, data_len, offset); + ib_sge += ib_sge_cnt; + + if (i + 1 == wr->send_wr_num) + send_wr->send_flags = IB_SEND_SIGNALED; + else + send_wr->next = &wr->send_wr[i + 1]; + + offset += data_len; + va_offset += data_len; + data_left -= data_len; + } + + atomic_inc(&isert_conn->post_send_buf_count); + + rc = ib_post_send(isert_conn->conn_qp, wr->send_wr, &wr_failed); + if (rc) { + pr_warn("ib_post_send() failed for IB_WR_RDMA_READ\n"); + atomic_dec(&isert_conn->post_send_buf_count); + } + pr_debug("Posted RDMA_READ memory for ISER Data WRITE\n"); + return 0; + +unmap_sg: + ib_dma_unmap_sg(ib_dev, sg_start, sg_nents, DMA_FROM_DEVICE); + return ret; +} + +static int +isert_immediate_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) +{ + int ret; + + switch (state) { + case ISTATE_SEND_NOPIN_WANT_RESPONSE: + ret = isert_put_nopin(cmd, conn, false); + break; + default: + pr_err("Unknown immediate state: 0x%02x\n", state); + ret = -EINVAL; + break; + } + + return ret; +} + +static int +isert_response_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) +{ + int ret; + + switch (state) { + case ISTATE_SEND_LOGOUTRSP: + ret = isert_put_logout_rsp(cmd, conn); + if (!ret) { + pr_debug("Returning iSER Logout -EAGAIN\n"); + ret = -EAGAIN; + } + break; + case ISTATE_SEND_NOPIN: + ret = isert_put_nopin(cmd, conn, true); + break; + case ISTATE_SEND_TASKMGTRSP: + ret = isert_put_tm_rsp(cmd, conn); + break; + case ISTATE_SEND_REJECT: + ret = isert_put_reject(cmd, conn); + break; + case ISTATE_SEND_STATUS: + /* + * Special case for sending non GOOD SCSI status from TX thread + * context during pre se_cmd excecution failure. + */ + ret = isert_put_response(conn, cmd); + break; + default: + pr_err("Unknown response state: 0x%02x\n", state); + ret = -EINVAL; + break; + } + + return ret; +} + +static int +isert_setup_np(struct iscsi_np *np, + struct __kernel_sockaddr_storage *ksockaddr) +{ + struct isert_np *isert_np; + struct rdma_cm_id *isert_lid; + struct sockaddr *sa; + int ret; + + isert_np = kzalloc(sizeof(struct isert_np), GFP_KERNEL); + if (!isert_np) { + pr_err("Unable to allocate struct isert_np\n"); + return -ENOMEM; + } + init_waitqueue_head(&isert_np->np_accept_wq); + mutex_init(&isert_np->np_accept_mutex); + INIT_LIST_HEAD(&isert_np->np_accept_list); + init_completion(&isert_np->np_login_comp); + + sa = (struct sockaddr *)ksockaddr; + pr_debug("ksockaddr: %p, sa: %p\n", ksockaddr, sa); + /* + * Setup the np->np_sockaddr from the passed sockaddr setup + * in iscsi_target_configfs.c code.. + */ + memcpy(&np->np_sockaddr, ksockaddr, + sizeof(struct __kernel_sockaddr_storage)); + + isert_lid = rdma_create_id(isert_cma_handler, np, RDMA_PS_TCP, + IB_QPT_RC); + if (IS_ERR(isert_lid)) { + pr_err("rdma_create_id() for isert_listen_handler failed: %ld\n", + PTR_ERR(isert_lid)); + ret = PTR_ERR(isert_lid); + goto out; + } + + ret = rdma_bind_addr(isert_lid, sa); + if (ret) { + pr_err("rdma_bind_addr() for isert_lid failed: %d\n", ret); + goto out_lid; + } + + ret = rdma_listen(isert_lid, ISERT_RDMA_LISTEN_BACKLOG); + if (ret) { + pr_err("rdma_listen() for isert_lid failed: %d\n", ret); + goto out_lid; + } + + isert_np->np_cm_id = isert_lid; + np->np_context = isert_np; + pr_debug("Setup isert_lid->context: %p\n", isert_lid->context); + + return 0; + +out_lid: + rdma_destroy_id(isert_lid); +out: + kfree(isert_np); + return ret; +} + +static int +isert_check_accept_queue(struct isert_np *isert_np) +{ + int empty; + + mutex_lock(&isert_np->np_accept_mutex); + empty = list_empty(&isert_np->np_accept_list); + mutex_unlock(&isert_np->np_accept_mutex); + + return empty; +} + +static int +isert_rdma_accept(struct isert_conn *isert_conn) +{ + struct rdma_cm_id *cm_id = isert_conn->conn_cm_id; + struct rdma_conn_param cp; + int ret; + + memset(&cp, 0, sizeof(struct rdma_conn_param)); + cp.responder_resources = isert_conn->responder_resources; + cp.initiator_depth = isert_conn->initiator_depth; + cp.retry_count = 7; + cp.rnr_retry_count = 7; + + pr_debug("Before rdma_accept >>>>>>>>>>>>>>>>>>>>.\n"); + + ret = rdma_accept(cm_id, &cp); + if (ret) { + pr_err("rdma_accept() failed with: %d\n", ret); + return ret; + } + + pr_debug("After rdma_accept >>>>>>>>>>>>>>>>>>>>>.\n"); + + return 0; +} + +static int +isert_get_login_rx(struct iscsi_conn *conn, struct iscsi_login *login) +{ + struct isert_conn *isert_conn = (struct isert_conn *)conn->context; + int ret; + + pr_debug("isert_get_login_rx before conn_login_comp conn: %p\n", conn); + + ret = wait_for_completion_interruptible(&isert_conn->conn_login_comp); + if (ret) + return ret; + + pr_debug("isert_get_login_rx processing login->req: %p\n", login->req); + return 0; +} + +static void +isert_set_conn_info(struct iscsi_np *np, struct iscsi_conn *conn, + struct isert_conn *isert_conn) +{ + struct rdma_cm_id *cm_id = isert_conn->conn_cm_id; + struct rdma_route *cm_route = &cm_id->route; + struct sockaddr_in *sock_in; + struct sockaddr_in6 *sock_in6; + + conn->login_family = np->np_sockaddr.ss_family; + + if (np->np_sockaddr.ss_family == AF_INET6) { + sock_in6 = (struct sockaddr_in6 *)&cm_route->addr.dst_addr; + snprintf(conn->login_ip, sizeof(conn->login_ip), "%pI6c", + &sock_in6->sin6_addr.in6_u); + conn->login_port = ntohs(sock_in6->sin6_port); + + sock_in6 = (struct sockaddr_in6 *)&cm_route->addr.src_addr; + snprintf(conn->local_ip, sizeof(conn->local_ip), "%pI6c", + &sock_in6->sin6_addr.in6_u); + conn->local_port = ntohs(sock_in6->sin6_port); + } else { + sock_in = (struct sockaddr_in *)&cm_route->addr.dst_addr; + sprintf(conn->login_ip, "%pI4", + &sock_in->sin_addr.s_addr); + conn->login_port = ntohs(sock_in->sin_port); + + sock_in = (struct sockaddr_in *)&cm_route->addr.src_addr; + sprintf(conn->local_ip, "%pI4", + &sock_in->sin_addr.s_addr); + conn->local_port = ntohs(sock_in->sin_port); + } +} + +static int +isert_accept_np(struct iscsi_np *np, struct iscsi_conn *conn) +{ + struct isert_np *isert_np = (struct isert_np *)np->np_context; + struct isert_conn *isert_conn; + int max_accept = 0, ret; + +accept_wait: + ret = wait_event_interruptible(isert_np->np_accept_wq, + !isert_check_accept_queue(isert_np) || + np->np_thread_state == ISCSI_NP_THREAD_RESET); + if (max_accept > 5) + return -ENODEV; + + spin_lock_bh(&np->np_thread_lock); + if (np->np_thread_state == ISCSI_NP_THREAD_RESET) { + spin_unlock_bh(&np->np_thread_lock); + pr_err("ISCSI_NP_THREAD_RESET for isert_accept_np\n"); + return -ENODEV; + } + spin_unlock_bh(&np->np_thread_lock); + + mutex_lock(&isert_np->np_accept_mutex); + if (list_empty(&isert_np->np_accept_list)) { + mutex_unlock(&isert_np->np_accept_mutex); + max_accept++; + goto accept_wait; + } + isert_conn = list_first_entry(&isert_np->np_accept_list, + struct isert_conn, conn_accept_node); + list_del_init(&isert_conn->conn_accept_node); + mutex_unlock(&isert_np->np_accept_mutex); + + conn->context = isert_conn; + isert_conn->conn = conn; + max_accept = 0; + + ret = isert_rdma_post_recvl(isert_conn); + if (ret) + return ret; + + ret = isert_rdma_accept(isert_conn); + if (ret) + return ret; + + isert_set_conn_info(np, conn, isert_conn); + + pr_debug("Processing isert_accept_np: isert_conn: %p\n", isert_conn); + return 0; +} + +static void +isert_free_np(struct iscsi_np *np) +{ + struct isert_np *isert_np = (struct isert_np *)np->np_context; + + rdma_destroy_id(isert_np->np_cm_id); + + np->np_context = NULL; + kfree(isert_np); +} + +static void isert_free_conn(struct iscsi_conn *conn) +{ + struct isert_conn *isert_conn = conn->context; + + pr_debug("isert_free_conn: Starting \n"); + /* + * Decrement post_send_buf_count for special case when called + * from isert_do_control_comp() -> iscsit_logout_post_handler() + */ + if (isert_conn->logout_posted) + atomic_dec(&isert_conn->post_send_buf_count); + + if (isert_conn->conn_cm_id) + rdma_disconnect(isert_conn->conn_cm_id); + /* + * Only wait for conn_wait_comp_err if the isert_conn made it + * into full feature phase.. + */ + if (isert_conn->state > ISER_CONN_INIT) { + pr_debug("isert_free_conn: Before wait_event comp_err %d\n", + isert_conn->state); + wait_event(isert_conn->conn_wait_comp_err, + isert_conn->state == ISER_CONN_TERMINATING); + pr_debug("isert_free_conn: After wait_event #1 >>>>>>>>>>>>\n"); + } + + pr_debug("isert_free_conn: wait_event conn_wait %d\n", isert_conn->state); + wait_event(isert_conn->conn_wait, isert_conn->state == ISER_CONN_DOWN); + pr_debug("isert_free_conn: After wait_event #2 >>>>>>>>>>>>>>>>>>>>\n"); + + isert_put_conn(isert_conn); +} + +static struct iscsit_transport iser_target_transport = { + .name = "IB/iSER", + .transport_type = ISCSI_INFINIBAND, + .owner = THIS_MODULE, + .iscsit_setup_np = isert_setup_np, + .iscsit_accept_np = isert_accept_np, + .iscsit_free_np = isert_free_np, + .iscsit_free_conn = isert_free_conn, + .iscsit_alloc_cmd = isert_alloc_cmd, + .iscsit_get_login_rx = isert_get_login_rx, + .iscsit_put_login_tx = isert_put_login_tx, + .iscsit_immediate_queue = isert_immediate_queue, + .iscsit_response_queue = isert_response_queue, + .iscsit_get_dataout = isert_get_dataout, + .iscsit_queue_data_in = isert_put_datain, + .iscsit_queue_status = isert_put_response, +}; + +static int __init isert_init(void) +{ + int ret; + + isert_rx_wq = alloc_workqueue("isert_rx_wq", 0, 0); + if (!isert_rx_wq) { + pr_err("Unable to allocate isert_rx_wq\n"); + return -ENOMEM; + } + + isert_comp_wq = alloc_workqueue("isert_comp_wq", 0, 0); + if (!isert_comp_wq) { + pr_err("Unable to allocate isert_comp_wq\n"); + ret = -ENOMEM; + goto destroy_rx_wq; + } + + isert_cmd_cache = kmem_cache_create("isert_cmd_cache", + sizeof(struct isert_cmd), __alignof__(struct isert_cmd), + 0, NULL); + if (!isert_cmd_cache) { + pr_err("Unable to create isert_cmd_cache\n"); + ret = -ENOMEM; + goto destroy_tx_cq; + } + + iscsit_register_transport(&iser_target_transport); + pr_debug("iSER_TARGET[0] - Loaded iser_target_transport\n"); + return 0; + +destroy_tx_cq: + destroy_workqueue(isert_comp_wq); +destroy_rx_wq: + destroy_workqueue(isert_rx_wq); + return ret; +} + +static void __exit isert_exit(void) +{ + kmem_cache_destroy(isert_cmd_cache); + destroy_workqueue(isert_comp_wq); + destroy_workqueue(isert_rx_wq); + iscsit_unregister_transport(&iser_target_transport); + pr_debug("iSER_TARGET[0] - Released iser_target_transport\n"); +} + +MODULE_DESCRIPTION("iSER-Target for mainline target infrastructure"); +MODULE_VERSION("0.1"); +MODULE_AUTHOR("nab@Linux-iSCSI.org"); +MODULE_LICENSE("GPL"); + +module_init(isert_init); +module_exit(isert_exit); diff --git a/drivers/infiniband/ulp/isert/ib_isert.h b/drivers/infiniband/ulp/isert/ib_isert.h new file mode 100644 index 00000000000..b104f4c2cd3 --- /dev/null +++ b/drivers/infiniband/ulp/isert/ib_isert.h @@ -0,0 +1,138 @@ +#include <linux/socket.h> +#include <linux/in.h> +#include <linux/in6.h> +#include <rdma/ib_verbs.h> +#include <rdma/rdma_cm.h> + +#define ISERT_RDMA_LISTEN_BACKLOG 10 + +enum isert_desc_type { + ISCSI_TX_CONTROL, + ISCSI_TX_DATAIN +}; + +enum iser_ib_op_code { + ISER_IB_RECV, + ISER_IB_SEND, + ISER_IB_RDMA_WRITE, + ISER_IB_RDMA_READ, +}; + +enum iser_conn_state { + ISER_CONN_INIT, + ISER_CONN_UP, + ISER_CONN_TERMINATING, + ISER_CONN_DOWN, +}; + +struct iser_rx_desc { + struct iser_hdr iser_header; + struct iscsi_hdr iscsi_header; + char data[ISER_RECV_DATA_SEG_LEN]; + u64 dma_addr; + struct ib_sge rx_sg; + char pad[ISER_RX_PAD_SIZE]; +} __packed; + +struct iser_tx_desc { + struct iser_hdr iser_header; + struct iscsi_hdr iscsi_header; + enum isert_desc_type type; + u64 dma_addr; + struct ib_sge tx_sg[2]; + int num_sge; + struct isert_cmd *isert_cmd; + struct ib_send_wr send_wr; +} __packed; + +struct isert_rdma_wr { + struct list_head wr_list; + struct isert_cmd *isert_cmd; + enum iser_ib_op_code iser_ib_op; + struct ib_sge *ib_sge; + int num_sge; + struct scatterlist *sge; + int send_wr_num; + struct ib_send_wr *send_wr; +}; + +struct isert_cmd { + uint32_t read_stag; + uint32_t write_stag; + uint64_t read_va; + uint64_t write_va; + u64 sense_buf_dma; + u32 sense_buf_len; + u32 read_va_off; + u32 write_va_off; + u32 rdma_wr_num; + struct isert_conn *conn; + struct iscsi_cmd iscsi_cmd; + struct ib_sge *ib_sge; + struct iser_tx_desc tx_desc; + struct isert_rdma_wr rdma_wr; + struct work_struct comp_work; +}; + +struct isert_device; + +struct isert_conn { + enum iser_conn_state state; + bool logout_posted; + int post_recv_buf_count; + atomic_t post_send_buf_count; + u32 responder_resources; + u32 initiator_depth; + u32 max_sge; + char *login_buf; + char *login_req_buf; + char *login_rsp_buf; + u64 login_req_dma; + u64 login_rsp_dma; + unsigned int conn_rx_desc_head; + struct iser_rx_desc *conn_rx_descs; + struct ib_recv_wr conn_rx_wr[ISERT_MIN_POSTED_RX]; + struct iscsi_conn *conn; + struct list_head conn_accept_node; + struct completion conn_login_comp; + struct iser_tx_desc conn_login_tx_desc; + struct rdma_cm_id *conn_cm_id; + struct ib_pd *conn_pd; + struct ib_mr *conn_mr; + struct ib_qp *conn_qp; + struct isert_device *conn_device; + struct work_struct conn_logout_work; + wait_queue_head_t conn_wait; + wait_queue_head_t conn_wait_comp_err; + struct kref conn_kref; +}; + +#define ISERT_MAX_CQ 64 + +struct isert_cq_desc { + struct isert_device *device; + int cq_index; + struct work_struct cq_rx_work; + struct work_struct cq_tx_work; +}; + +struct isert_device { + int cqs_used; + int refcount; + int cq_active_qps[ISERT_MAX_CQ]; + struct ib_device *ib_device; + struct ib_pd *dev_pd; + struct ib_mr *dev_mr; + struct ib_cq *dev_rx_cq[ISERT_MAX_CQ]; + struct ib_cq *dev_tx_cq[ISERT_MAX_CQ]; + struct isert_cq_desc *cq_desc; + struct list_head dev_node; +}; + +struct isert_np { + wait_queue_head_t np_accept_wq; + struct rdma_cm_id *np_cm_id; + struct mutex np_accept_mutex; + struct list_head np_accept_list; + struct completion np_login_comp; +}; diff --git a/drivers/infiniband/ulp/isert/isert_proto.h b/drivers/infiniband/ulp/isert/isert_proto.h new file mode 100644 index 00000000000..4dccd313b77 --- /dev/null +++ b/drivers/infiniband/ulp/isert/isert_proto.h @@ -0,0 +1,47 @@ +/* From iscsi_iser.h */ + +struct iser_hdr { + u8 flags; + u8 rsvd[3]; + __be32 write_stag; /* write rkey */ + __be64 write_va; + __be32 read_stag; /* read rkey */ + __be64 read_va; +} __packed; + +/*Constant PDU lengths calculations */ +#define ISER_HEADERS_LEN (sizeof(struct iser_hdr) + sizeof(struct iscsi_hdr)) + +#define ISER_RECV_DATA_SEG_LEN 8192 +#define ISER_RX_PAYLOAD_SIZE (ISER_HEADERS_LEN + ISER_RECV_DATA_SEG_LEN) +#define ISER_RX_LOGIN_SIZE (ISER_HEADERS_LEN + ISCSI_DEF_MAX_RECV_SEG_LEN) + +/* QP settings */ +/* Maximal bounds on received asynchronous PDUs */ +#define ISERT_MAX_TX_MISC_PDUS 4 /* NOOP_IN(2) , ASYNC_EVENT(2) */ + +#define ISERT_MAX_RX_MISC_PDUS 6 /* NOOP_OUT(2), TEXT(1), * + * SCSI_TMFUNC(2), LOGOUT(1) */ + +#define ISCSI_DEF_XMIT_CMDS_MAX 128 /* from libiscsi.h, must be power of 2 */ + +#define ISERT_QP_MAX_RECV_DTOS (ISCSI_DEF_XMIT_CMDS_MAX) + +#define ISERT_MIN_POSTED_RX (ISCSI_DEF_XMIT_CMDS_MAX >> 2) + +#define ISERT_INFLIGHT_DATAOUTS 8 + +#define ISERT_QP_MAX_REQ_DTOS (ISCSI_DEF_XMIT_CMDS_MAX * \ + (1 + ISERT_INFLIGHT_DATAOUTS) + \ + ISERT_MAX_TX_MISC_PDUS + \ + ISERT_MAX_RX_MISC_PDUS) + +#define ISER_RX_PAD_SIZE (ISER_RECV_DATA_SEG_LEN + 4096 - \ + (ISER_RX_PAYLOAD_SIZE + sizeof(u64) + sizeof(struct ib_sge))) + +#define ISER_VER 0x10 +#define ISER_WSV 0x08 +#define ISER_RSV 0x04 +#define ISCSI_CTRL 0x10 +#define ISER_HELLO 0x20 +#define ISER_HELLORPLY 0x30 diff --git a/drivers/md/bitmap.c b/drivers/md/bitmap.c index 4fd9d6aeff6..5a2c7549982 100644 --- a/drivers/md/bitmap.c +++ b/drivers/md/bitmap.c @@ -846,7 +846,7 @@ static void bitmap_file_set_bit(struct bitmap *bitmap, sector_t block) if (test_bit(BITMAP_HOSTENDIAN, &bitmap->flags)) set_bit(bit, kaddr); else - test_and_set_bit_le(bit, kaddr); + set_bit_le(bit, kaddr); kunmap_atomic(kaddr); pr_debug("set file bit %lu page %lu\n", bit, page->index); /* record page number so it gets flushed to disk when unplug occurs */ @@ -868,7 +868,7 @@ static void bitmap_file_clear_bit(struct bitmap *bitmap, sector_t block) if (test_bit(BITMAP_HOSTENDIAN, &bitmap->flags)) clear_bit(bit, paddr); else - test_and_clear_bit_le(bit, paddr); + clear_bit_le(bit, paddr); kunmap_atomic(paddr); if (!test_page_attr(bitmap, page->index, BITMAP_PAGE_NEEDWRITE)) { set_page_attr(bitmap, page->index, BITMAP_PAGE_PENDING); diff --git a/drivers/md/dm-raid.c b/drivers/md/dm-raid.c index 311e3d35b27..1d3fe1a40a9 100644 --- a/drivers/md/dm-raid.c +++ b/drivers/md/dm-raid.c @@ -1279,6 +1279,31 @@ static int raid_map(struct dm_target *ti, struct bio *bio) return DM_MAPIO_SUBMITTED; } +static const char *decipher_sync_action(struct mddev *mddev) +{ + if (test_bit(MD_RECOVERY_FROZEN, &mddev->recovery)) + return "frozen"; + + if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery) || + (!mddev->ro && test_bit(MD_RECOVERY_NEEDED, &mddev->recovery))) { + if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) + return "reshape"; + + if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery)) { + if (!test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery)) + return "resync"; + else if (test_bit(MD_RECOVERY_CHECK, &mddev->recovery)) + return "check"; + return "repair"; + } + + if (test_bit(MD_RECOVERY_RECOVER, &mddev->recovery)) + return "recover"; + } + + return "idle"; +} + static void raid_status(struct dm_target *ti, status_type_t type, unsigned status_flags, char *result, unsigned maxlen) { @@ -1298,8 +1323,18 @@ static void raid_status(struct dm_target *ti, status_type_t type, sync = rs->md.recovery_cp; if (sync >= rs->md.resync_max_sectors) { + /* + * Sync complete. + */ array_in_sync = 1; sync = rs->md.resync_max_sectors; + } else if (test_bit(MD_RECOVERY_REQUESTED, &rs->md.recovery)) { + /* + * If "check" or "repair" is occurring, the array has + * undergone and initial sync and the health characters + * should not be 'a' anymore. + */ + array_in_sync = 1; } else { /* * The array may be doing an initial sync, or it may @@ -1311,6 +1346,7 @@ static void raid_status(struct dm_target *ti, status_type_t type, if (!test_bit(In_sync, &rs->dev[i].rdev.flags)) array_in_sync = 1; } + /* * Status characters: * 'D' = Dead/Failed device @@ -1339,6 +1375,21 @@ static void raid_status(struct dm_target *ti, status_type_t type, (unsigned long long) sync, (unsigned long long) rs->md.resync_max_sectors); + /* + * Sync action: + * See Documentation/device-mapper/dm-raid.c for + * information on each of these states. + */ + DMEMIT(" %s", decipher_sync_action(&rs->md)); + + /* + * resync_mismatches/mismatch_cnt + * This field shows the number of discrepancies found when + * performing a "check" of the array. + */ + DMEMIT(" %llu", + (unsigned long long) + atomic64_read(&rs->md.resync_mismatches)); break; case STATUSTYPE_TABLE: /* The string you would use to construct this array */ @@ -1425,7 +1476,62 @@ static void raid_status(struct dm_target *ti, status_type_t type, } } -static int raid_iterate_devices(struct dm_target *ti, iterate_devices_callout_fn fn, void *data) +static int raid_message(struct dm_target *ti, unsigned argc, char **argv) +{ + struct raid_set *rs = ti->private; + struct mddev *mddev = &rs->md; + + if (!strcasecmp(argv[0], "reshape")) { + DMERR("Reshape not supported."); + return -EINVAL; + } + + if (!mddev->pers || !mddev->pers->sync_request) + return -EINVAL; + + if (!strcasecmp(argv[0], "frozen")) + set_bit(MD_RECOVERY_FROZEN, &mddev->recovery); + else + clear_bit(MD_RECOVERY_FROZEN, &mddev->recovery); + + if (!strcasecmp(argv[0], "idle") || !strcasecmp(argv[0], "frozen")) { + if (mddev->sync_thread) { + set_bit(MD_RECOVERY_INTR, &mddev->recovery); + md_reap_sync_thread(mddev); + } + } else if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery) || + test_bit(MD_RECOVERY_NEEDED, &mddev->recovery)) + return -EBUSY; + else if (!strcasecmp(argv[0], "resync")) + set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); + else if (!strcasecmp(argv[0], "recover")) { + set_bit(MD_RECOVERY_RECOVER, &mddev->recovery); + set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); + } else { + if (!strcasecmp(argv[0], "check")) + set_bit(MD_RECOVERY_CHECK, &mddev->recovery); + else if (!!strcasecmp(argv[0], "repair")) + return -EINVAL; + set_bit(MD_RECOVERY_REQUESTED, &mddev->recovery); + set_bit(MD_RECOVERY_SYNC, &mddev->recovery); + } + if (mddev->ro == 2) { + /* A write to sync_action is enough to justify + * canceling read-auto mode + */ + mddev->ro = 0; + if (!mddev->suspended) + md_wakeup_thread(mddev->sync_thread); + } + set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); + if (!mddev->suspended) + md_wakeup_thread(mddev->thread); + + return 0; +} + +static int raid_iterate_devices(struct dm_target *ti, + iterate_devices_callout_fn fn, void *data) { struct raid_set *rs = ti->private; unsigned i; @@ -1482,12 +1588,13 @@ static void raid_resume(struct dm_target *ti) static struct target_type raid_target = { .name = "raid", - .version = {1, 4, 2}, + .version = {1, 5, 0}, .module = THIS_MODULE, .ctr = raid_ctr, .dtr = raid_dtr, .map = raid_map, .status = raid_status, + .message = raid_message, .iterate_devices = raid_iterate_devices, .io_hints = raid_io_hints, .presuspend = raid_presuspend, diff --git a/drivers/md/md.c b/drivers/md/md.c index aeceedfc530..4c74424c78b 100644 --- a/drivers/md/md.c +++ b/drivers/md/md.c @@ -72,6 +72,9 @@ static DECLARE_WAIT_QUEUE_HEAD(resync_wait); static struct workqueue_struct *md_wq; static struct workqueue_struct *md_misc_wq; +static int remove_and_add_spares(struct mddev *mddev, + struct md_rdev *this); + #define MD_BUG(x...) { printk("md: bug in file %s, line %d\n", __FILE__, __LINE__); md_print_devices(); } /* @@ -1564,8 +1567,8 @@ static int super_1_load(struct md_rdev *rdev, struct md_rdev *refdev, int minor_ sector, count, 1) == 0) return -EINVAL; } - } else if (sb->bblog_offset == 0) - rdev->badblocks.shift = -1; + } else if (sb->bblog_offset != 0) + rdev->badblocks.shift = 0; if (!refdev) { ret = 1; @@ -2411,6 +2414,11 @@ static void md_update_sb(struct mddev * mddev, int force_change) int nospares = 0; int any_badblocks_changed = 0; + if (mddev->ro) { + if (force_change) + set_bit(MD_CHANGE_DEVS, &mddev->flags); + return; + } repeat: /* First make sure individual recovery_offsets are correct */ rdev_for_each(rdev, mddev) { @@ -2800,12 +2808,10 @@ slot_store(struct md_rdev *rdev, const char *buf, size_t len) /* personality does all needed checks */ if (rdev->mddev->pers->hot_remove_disk == NULL) return -EINVAL; - err = rdev->mddev->pers-> - hot_remove_disk(rdev->mddev, rdev); - if (err) - return err; - sysfs_unlink_rdev(rdev->mddev, rdev); - rdev->raid_disk = -1; + clear_bit(Blocked, &rdev->flags); + remove_and_add_spares(rdev->mddev, rdev); + if (rdev->raid_disk >= 0) + return -EBUSY; set_bit(MD_RECOVERY_NEEDED, &rdev->mddev->recovery); md_wakeup_thread(rdev->mddev->thread); } else if (rdev->mddev->pers) { @@ -3221,7 +3227,7 @@ int md_rdev_init(struct md_rdev *rdev) * be used - I wonder if that matters */ rdev->badblocks.count = 0; - rdev->badblocks.shift = 0; + rdev->badblocks.shift = -1; /* disabled until explicitly enabled */ rdev->badblocks.page = kmalloc(PAGE_SIZE, GFP_KERNEL); seqlock_init(&rdev->badblocks.lock); if (rdev->badblocks.page == NULL) @@ -3293,9 +3299,6 @@ static struct md_rdev *md_import_device(dev_t newdev, int super_format, int supe goto abort_free; } } - if (super_format == -1) - /* hot-add for 0.90, or non-persistent: so no badblocks */ - rdev->badblocks.shift = -1; return rdev; @@ -4225,8 +4228,6 @@ action_show(struct mddev *mddev, char *page) return sprintf(page, "%s\n", type); } -static void reap_sync_thread(struct mddev *mddev); - static ssize_t action_store(struct mddev *mddev, const char *page, size_t len) { @@ -4241,7 +4242,7 @@ action_store(struct mddev *mddev, const char *page, size_t len) if (cmd_match(page, "idle") || cmd_match(page, "frozen")) { if (mddev->sync_thread) { set_bit(MD_RECOVERY_INTR, &mddev->recovery); - reap_sync_thread(mddev); + md_reap_sync_thread(mddev); } } else if (test_bit(MD_RECOVERY_RUNNING, &mddev->recovery) || test_bit(MD_RECOVERY_NEEDED, &mddev->recovery)) @@ -5279,7 +5280,7 @@ static void __md_stop_writes(struct mddev *mddev) if (mddev->sync_thread) { set_bit(MD_RECOVERY_FROZEN, &mddev->recovery); set_bit(MD_RECOVERY_INTR, &mddev->recovery); - reap_sync_thread(mddev); + md_reap_sync_thread(mddev); } del_timer_sync(&mddev->safemode_timer); @@ -5287,7 +5288,8 @@ static void __md_stop_writes(struct mddev *mddev) bitmap_flush(mddev); md_super_wait(mddev); - if (!mddev->in_sync || mddev->flags) { + if (mddev->ro == 0 && + (!mddev->in_sync || mddev->flags)) { /* mark array as shutdown cleanly */ mddev->in_sync = 1; md_update_sb(mddev, 1); @@ -5810,7 +5812,7 @@ static int add_new_disk(struct mddev * mddev, mdu_disk_info_t *info) else sysfs_notify_dirent_safe(rdev->sysfs_state); - md_update_sb(mddev, 1); + set_bit(MD_CHANGE_DEVS, &mddev->flags); if (mddev->degraded) set_bit(MD_RECOVERY_RECOVER, &mddev->recovery); set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); @@ -5877,6 +5879,9 @@ static int hot_remove_disk(struct mddev * mddev, dev_t dev) if (!rdev) return -ENXIO; + clear_bit(Blocked, &rdev->flags); + remove_and_add_spares(mddev, rdev); + if (rdev->raid_disk >= 0) goto busy; @@ -6490,6 +6495,28 @@ static int md_ioctl(struct block_device *bdev, fmode_t mode, err = md_set_readonly(mddev, bdev); goto done_unlock; + case HOT_REMOVE_DISK: + err = hot_remove_disk(mddev, new_decode_dev(arg)); + goto done_unlock; + + case ADD_NEW_DISK: + /* We can support ADD_NEW_DISK on read-only arrays + * on if we are re-adding a preexisting device. + * So require mddev->pers and MD_DISK_SYNC. + */ + if (mddev->pers) { + mdu_disk_info_t info; + if (copy_from_user(&info, argp, sizeof(info))) + err = -EFAULT; + else if (!(info.state & (1<<MD_DISK_SYNC))) + /* Need to clear read-only for this */ + break; + else + err = add_new_disk(mddev, &info); + goto done_unlock; + } + break; + case BLKROSET: if (get_user(ro, (int __user *)(arg))) { err = -EFAULT; @@ -6560,10 +6587,6 @@ static int md_ioctl(struct block_device *bdev, fmode_t mode, goto done_unlock; } - case HOT_REMOVE_DISK: - err = hot_remove_disk(mddev, new_decode_dev(arg)); - goto done_unlock; - case HOT_ADD_DISK: err = hot_add_disk(mddev, new_decode_dev(arg)); goto done_unlock; @@ -7644,14 +7667,16 @@ void md_do_sync(struct md_thread *thread) } EXPORT_SYMBOL_GPL(md_do_sync); -static int remove_and_add_spares(struct mddev *mddev) +static int remove_and_add_spares(struct mddev *mddev, + struct md_rdev *this) { struct md_rdev *rdev; int spares = 0; int removed = 0; rdev_for_each(rdev, mddev) - if (rdev->raid_disk >= 0 && + if ((this == NULL || rdev == this) && + rdev->raid_disk >= 0 && !test_bit(Blocked, &rdev->flags) && (test_bit(Faulty, &rdev->flags) || ! test_bit(In_sync, &rdev->flags)) && @@ -7666,74 +7691,52 @@ static int remove_and_add_spares(struct mddev *mddev) if (removed && mddev->kobj.sd) sysfs_notify(&mddev->kobj, NULL, "degraded"); + if (this) + goto no_add; + rdev_for_each(rdev, mddev) { if (rdev->raid_disk >= 0 && !test_bit(In_sync, &rdev->flags) && !test_bit(Faulty, &rdev->flags)) spares++; - if (rdev->raid_disk < 0 - && !test_bit(Faulty, &rdev->flags)) { - rdev->recovery_offset = 0; - if (mddev->pers-> - hot_add_disk(mddev, rdev) == 0) { - if (sysfs_link_rdev(mddev, rdev)) - /* failure here is OK */; - spares++; - md_new_event(mddev); - set_bit(MD_CHANGE_DEVS, &mddev->flags); - } + if (rdev->raid_disk >= 0) + continue; + if (test_bit(Faulty, &rdev->flags)) + continue; + if (mddev->ro && + rdev->saved_raid_disk < 0) + continue; + + rdev->recovery_offset = 0; + if (rdev->saved_raid_disk >= 0 && mddev->in_sync) { + spin_lock_irq(&mddev->write_lock); + if (mddev->in_sync) + /* OK, this device, which is in_sync, + * will definitely be noticed before + * the next write, so recovery isn't + * needed. + */ + rdev->recovery_offset = mddev->recovery_cp; + spin_unlock_irq(&mddev->write_lock); + } + if (mddev->ro && rdev->recovery_offset != MaxSector) + /* not safe to add this disk now */ + continue; + if (mddev->pers-> + hot_add_disk(mddev, rdev) == 0) { + if (sysfs_link_rdev(mddev, rdev)) + /* failure here is OK */; + spares++; + md_new_event(mddev); + set_bit(MD_CHANGE_DEVS, &mddev->flags); } } +no_add: if (removed) set_bit(MD_CHANGE_DEVS, &mddev->flags); return spares; } -static void reap_sync_thread(struct mddev *mddev) -{ - struct md_rdev *rdev; - - /* resync has finished, collect result */ - md_unregister_thread(&mddev->sync_thread); - if (!test_bit(MD_RECOVERY_INTR, &mddev->recovery) && - !test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery)) { - /* success...*/ - /* activate any spares */ - if (mddev->pers->spare_active(mddev)) { - sysfs_notify(&mddev->kobj, NULL, - "degraded"); - set_bit(MD_CHANGE_DEVS, &mddev->flags); - } - } - if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery) && - mddev->pers->finish_reshape) - mddev->pers->finish_reshape(mddev); - - /* If array is no-longer degraded, then any saved_raid_disk - * information must be scrapped. Also if any device is now - * In_sync we must scrape the saved_raid_disk for that device - * do the superblock for an incrementally recovered device - * written out. - */ - rdev_for_each(rdev, mddev) - if (!mddev->degraded || - test_bit(In_sync, &rdev->flags)) - rdev->saved_raid_disk = -1; - - md_update_sb(mddev, 1); - clear_bit(MD_RECOVERY_RUNNING, &mddev->recovery); - clear_bit(MD_RECOVERY_SYNC, &mddev->recovery); - clear_bit(MD_RECOVERY_RESHAPE, &mddev->recovery); - clear_bit(MD_RECOVERY_REQUESTED, &mddev->recovery); - clear_bit(MD_RECOVERY_CHECK, &mddev->recovery); - /* flag recovery needed just to double check */ - set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); - sysfs_notify_dirent_safe(mddev->sysfs_action); - md_new_event(mddev); - if (mddev->event_work.func) - queue_work(md_misc_wq, &mddev->event_work); -} - /* * This routine is regularly called by all per-raid-array threads to * deal with generic issues like resync and super-block update. @@ -7789,22 +7792,16 @@ void md_check_recovery(struct mddev *mddev) int spares = 0; if (mddev->ro) { - /* Only thing we do on a ro array is remove - * failed devices. + /* On a read-only array we can: + * - remove failed devices + * - add already-in_sync devices if the array itself + * is in-sync. + * As we only add devices that are already in-sync, + * we can activate the spares immediately. */ - struct md_rdev *rdev; - rdev_for_each(rdev, mddev) - if (rdev->raid_disk >= 0 && - !test_bit(Blocked, &rdev->flags) && - test_bit(Faulty, &rdev->flags) && - atomic_read(&rdev->nr_pending)==0) { - if (mddev->pers->hot_remove_disk( - mddev, rdev) == 0) { - sysfs_unlink_rdev(mddev, rdev); - rdev->raid_disk = -1; - } - } clear_bit(MD_RECOVERY_NEEDED, &mddev->recovery); + remove_and_add_spares(mddev, NULL); + mddev->pers->spare_active(mddev); goto unlock; } @@ -7836,7 +7833,7 @@ void md_check_recovery(struct mddev *mddev) goto unlock; } if (mddev->sync_thread) { - reap_sync_thread(mddev); + md_reap_sync_thread(mddev); goto unlock; } /* Set RUNNING before clearing NEEDED to avoid @@ -7867,7 +7864,7 @@ void md_check_recovery(struct mddev *mddev) goto unlock; set_bit(MD_RECOVERY_RESHAPE, &mddev->recovery); clear_bit(MD_RECOVERY_RECOVER, &mddev->recovery); - } else if ((spares = remove_and_add_spares(mddev))) { + } else if ((spares = remove_and_add_spares(mddev, NULL))) { clear_bit(MD_RECOVERY_SYNC, &mddev->recovery); clear_bit(MD_RECOVERY_CHECK, &mddev->recovery); clear_bit(MD_RECOVERY_REQUESTED, &mddev->recovery); @@ -7917,6 +7914,51 @@ void md_check_recovery(struct mddev *mddev) } } +void md_reap_sync_thread(struct mddev *mddev) +{ + struct md_rdev *rdev; + + /* resync has finished, collect result */ + md_unregister_thread(&mddev->sync_thread); + if (!test_bit(MD_RECOVERY_INTR, &mddev->recovery) && + !test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery)) { + /* success...*/ + /* activate any spares */ + if (mddev->pers->spare_active(mddev)) { + sysfs_notify(&mddev->kobj, NULL, + "degraded"); + set_bit(MD_CHANGE_DEVS, &mddev->flags); + } + } + if (test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery) && + mddev->pers->finish_reshape) + mddev->pers->finish_reshape(mddev); + + /* If array is no-longer degraded, then any saved_raid_disk + * information must be scrapped. Also if any device is now + * In_sync we must scrape the saved_raid_disk for that device + * do the superblock for an incrementally recovered device + * written out. + */ + rdev_for_each(rdev, mddev) + if (!mddev->degraded || + test_bit(In_sync, &rdev->flags)) + rdev->saved_raid_disk = -1; + + md_update_sb(mddev, 1); + clear_bit(MD_RECOVERY_RUNNING, &mddev->recovery); + clear_bit(MD_RECOVERY_SYNC, &mddev->recovery); + clear_bit(MD_RECOVERY_RESHAPE, &mddev->recovery); + clear_bit(MD_RECOVERY_REQUESTED, &mddev->recovery); + clear_bit(MD_RECOVERY_CHECK, &mddev->recovery); + /* flag recovery needed just to double check */ + set_bit(MD_RECOVERY_NEEDED, &mddev->recovery); + sysfs_notify_dirent_safe(mddev->sysfs_action); + md_new_event(mddev); + if (mddev->event_work.func) + queue_work(md_misc_wq, &mddev->event_work); +} + void md_wait_for_blocked_rdev(struct md_rdev *rdev, struct mddev *mddev) { sysfs_notify_dirent_safe(rdev->sysfs_state); @@ -8642,6 +8684,7 @@ EXPORT_SYMBOL(md_register_thread); EXPORT_SYMBOL(md_unregister_thread); EXPORT_SYMBOL(md_wakeup_thread); EXPORT_SYMBOL(md_check_recovery); +EXPORT_SYMBOL(md_reap_sync_thread); MODULE_LICENSE("GPL"); MODULE_DESCRIPTION("MD RAID framework"); MODULE_ALIAS("md"); diff --git a/drivers/md/md.h b/drivers/md/md.h index d90fb1a879e..653f992b687 100644 --- a/drivers/md/md.h +++ b/drivers/md/md.h @@ -567,6 +567,7 @@ extern struct md_thread *md_register_thread( extern void md_unregister_thread(struct md_thread **threadp); extern void md_wakeup_thread(struct md_thread *thread); extern void md_check_recovery(struct mddev *mddev); +extern void md_reap_sync_thread(struct mddev *mddev); extern void md_write_start(struct mddev *mddev, struct bio *bi); extern void md_write_end(struct mddev *mddev); extern void md_done_sync(struct mddev *mddev, int blocks, int ok); diff --git a/drivers/md/raid1.c b/drivers/md/raid1.c index fd86b372692..851023e2ba5 100644 --- a/drivers/md/raid1.c +++ b/drivers/md/raid1.c @@ -981,7 +981,12 @@ static void raid1_unplug(struct blk_plug_cb *cb, bool from_schedule) while (bio) { /* submit pending writes */ struct bio *next = bio->bi_next; bio->bi_next = NULL; - generic_make_request(bio); + if (unlikely((bio->bi_rw & REQ_DISCARD) && + !blk_queue_discard(bdev_get_queue(bio->bi_bdev)))) + /* Just ignore it */ + bio_endio(bio, 0); + else + generic_make_request(bio); bio = next; } kfree(plug); @@ -2901,6 +2906,7 @@ static int stop(struct mddev *mddev) if (conf->r1bio_pool) mempool_destroy(conf->r1bio_pool); kfree(conf->mirrors); + safe_put_page(conf->tmppage); kfree(conf->poolinfo); kfree(conf); mddev->private = NULL; diff --git a/drivers/md/raid10.c b/drivers/md/raid10.c index 77b562d18a9..018741ba931 100644 --- a/drivers/md/raid10.c +++ b/drivers/md/raid10.c @@ -1133,7 +1133,12 @@ static void raid10_unplug(struct blk_plug_cb *cb, bool from_schedule) while (bio) { /* submit pending writes */ struct bio *next = bio->bi_next; bio->bi_next = NULL; - generic_make_request(bio); + if (unlikely((bio->bi_rw & REQ_DISCARD) && + !blk_queue_discard(bdev_get_queue(bio->bi_bdev)))) + /* Just ignore it */ + bio_endio(bio, 0); + else + generic_make_request(bio); bio = next; } kfree(plug); @@ -2913,6 +2918,22 @@ static sector_t sync_request(struct mddev *mddev, sector_t sector_nr, if (init_resync(conf)) return 0; + /* + * Allow skipping a full rebuild for incremental assembly + * of a clean array, like RAID1 does. + */ + if (mddev->bitmap == NULL && + mddev->recovery_cp == MaxSector && + !test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery) && + conf->fullsync == 0) { + *skipped = 1; + max_sector = mddev->dev_sectors; + if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery) || + test_bit(MD_RECOVERY_RESHAPE, &mddev->recovery)) + max_sector = mddev->resync_max_sectors; + return max_sector - sector_nr; + } + skipped: max_sector = mddev->dev_sectors; if (test_bit(MD_RECOVERY_SYNC, &mddev->recovery) || @@ -3810,6 +3831,7 @@ static int stop(struct mddev *mddev) if (conf->r10bio_pool) mempool_destroy(conf->r10bio_pool); + safe_put_page(conf->tmppage); kfree(conf->mirrors); kfree(conf); mddev->private = NULL; diff --git a/drivers/md/raid5.c b/drivers/md/raid5.c index f4e87bfc756..4a7be455d6d 100644 --- a/drivers/md/raid5.c +++ b/drivers/md/raid5.c @@ -1887,8 +1887,15 @@ static void raid5_end_write_request(struct bio *bi, int error) &rdev->mddev->recovery); } else if (is_badblock(rdev, sh->sector, STRIPE_SECTORS, - &first_bad, &bad_sectors)) + &first_bad, &bad_sectors)) { set_bit(R5_MadeGood, &sh->dev[i].flags); + if (test_bit(R5_ReadError, &sh->dev[i].flags)) + /* That was a successful write so make + * sure it looks like we already did + * a re-write. + */ + set_bit(R5_ReWrite, &sh->dev[i].flags); + } } rdev_dec_pending(rdev, conf->mddev); @@ -4672,9 +4679,10 @@ static inline sector_t sync_request(struct mddev *mddev, sector_t sector_nr, int *skipped = 1; return rv; } - if (!bitmap_start_sync(mddev->bitmap, sector_nr, &sync_blocks, 1) && - !test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery) && - !conf->fullsync && sync_blocks >= STRIPE_SECTORS) { + if (!test_bit(MD_RECOVERY_REQUESTED, &mddev->recovery) && + !conf->fullsync && + !bitmap_start_sync(mddev->bitmap, sector_nr, &sync_blocks, 1) && + sync_blocks >= STRIPE_SECTORS) { /* we can skip this block, and probably more */ sync_blocks /= STRIPE_SECTORS; *skipped = 1; diff --git a/drivers/mfd/ab8500-core.c b/drivers/mfd/ab8500-core.c index 7c84ced2e01..f276352cc9e 100644 --- a/drivers/mfd/ab8500-core.c +++ b/drivers/mfd/ab8500-core.c @@ -95,6 +95,7 @@ #define AB8500_IT_MASK22_REG 0x55 #define AB8500_IT_MASK23_REG 0x56 #define AB8500_IT_MASK24_REG 0x57 +#define AB8500_IT_MASK25_REG 0x58 /* * latch hierarchy registers @@ -102,15 +103,25 @@ #define AB8500_IT_LATCHHIER1_REG 0x60 #define AB8500_IT_LATCHHIER2_REG 0x61 #define AB8500_IT_LATCHHIER3_REG 0x62 +#define AB8540_IT_LATCHHIER4_REG 0x63 #define AB8500_IT_LATCHHIER_NUM 3 +#define AB8540_IT_LATCHHIER_NUM 4 #define AB8500_REV_REG 0x80 #define AB8500_IC_NAME_REG 0x82 #define AB8500_SWITCH_OFF_STATUS 0x00 #define AB8500_TURN_ON_STATUS 0x00 +#define AB8505_TURN_ON_STATUS_2 0x04 +#define AB8500_CH_USBCH_STAT1_REG 0x02 +#define VBUS_DET_DBNC100 0x02 +#define VBUS_DET_DBNC1 0x01 + +static DEFINE_SPINLOCK(on_stat_lock); +static u8 turn_on_stat_mask = 0xFF; +static u8 turn_on_stat_set; static bool no_bm; /* No battery management */ module_param(no_bm, bool, S_IRUGO); @@ -130,9 +141,15 @@ static const int ab8500_irq_regoffset[AB8500_NUM_IRQ_REGS] = { 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, }; -/* AB9540 support */ +/* AB9540 / AB8505 support */ static const int ab9540_irq_regoffset[AB9540_NUM_IRQ_REGS] = { - 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, 12, 13, 24, + 0, 1, 2, 3, 4, 6, 7, 8, 9, 11, 18, 19, 20, 21, 12, 13, 24, 5, 22, 23 +}; + +/* AB8540 support */ +static const int ab8540_irq_regoffset[AB8540_NUM_IRQ_REGS] = { + 0, 1, 2, 3, 4, -1, -1, -1, -1, 11, 18, 19, 20, 21, 12, 13, 24, 5, 22, 23, + 25, 26, 27, 28, 29, 30, 31, }; static const char ab8500_version_str[][7] = { @@ -352,6 +369,9 @@ static void ab8500_irq_sync_unlock(struct irq_data *data) is_ab8500_1p1_or_earlier(ab8500)) continue; + if (ab8500->irq_reg_offset[i] < 0) + continue; + ab8500->oldmask[i] = new; reg = AB8500_IT_MASK1_REG + ab8500->irq_reg_offset[i]; @@ -423,6 +443,18 @@ static struct irq_chip ab8500_irq_chip = { .irq_set_type = ab8500_irq_set_type, }; +static void update_latch_offset(u8 *offset, int i) +{ + /* Fix inconsistent ITFromLatch25 bit mapping... */ + if (unlikely(*offset == 17)) + *offset = 24; + /* Fix inconsistent ab8540 bit mapping... */ + if (unlikely(*offset == 16)) + *offset = 25; + if ((i==3) && (*offset >= 24)) + *offset += 2; +} + static int ab8500_handle_hierarchical_line(struct ab8500 *ab8500, int latch_offset, u8 latch_val) { @@ -474,9 +506,7 @@ static int ab8500_handle_hierarchical_latch(struct ab8500 *ab8500, latch_bit = __ffs(hier_val); latch_offset = (hier_offset << 3) + latch_bit; - /* Fix inconsistent ITFromLatch25 bit mapping... */ - if (unlikely(latch_offset == 17)) - latch_offset = 24; + update_latch_offset(&latch_offset, hier_offset); status = get_register_interruptible(ab8500, AB8500_INTERRUPT, @@ -504,7 +534,7 @@ static irqreturn_t ab8500_hierarchical_irq(int irq, void *dev) dev_vdbg(ab8500->dev, "interrupt\n"); /* Hierarchical interrupt version */ - for (i = 0; i < AB8500_IT_LATCHHIER_NUM; i++) { + for (i = 0; i < (ab8500->it_latchhier_num); i++) { int status; u8 hier_val; @@ -520,63 +550,6 @@ static irqreturn_t ab8500_hierarchical_irq(int irq, void *dev) return IRQ_HANDLED; } -/** - * ab8500_irq_get_virq(): Map an interrupt on a chip to a virtual IRQ - * - * @ab8500: ab8500_irq controller to operate on. - * @irq: index of the interrupt requested in the chip IRQs - * - * Useful for drivers to request their own IRQs. - */ -static int ab8500_irq_get_virq(struct ab8500 *ab8500, int irq) -{ - if (!ab8500) - return -EINVAL; - - return irq_create_mapping(ab8500->domain, irq); -} - -static irqreturn_t ab8500_irq(int irq, void *dev) -{ - struct ab8500 *ab8500 = dev; - int i; - - dev_vdbg(ab8500->dev, "interrupt\n"); - - atomic_inc(&ab8500->transfer_ongoing); - - for (i = 0; i < ab8500->mask_size; i++) { - int regoffset = ab8500->irq_reg_offset[i]; - int status; - u8 value; - - /* - * Interrupt register 12 doesn't exist prior to AB8500 version - * 2.0 - */ - if (regoffset == 11 && is_ab8500_1p1_or_earlier(ab8500)) - continue; - - status = get_register_interruptible(ab8500, AB8500_INTERRUPT, - AB8500_IT_LATCH1_REG + regoffset, &value); - if (status < 0 || value == 0) - continue; - - do { - int bit = __ffs(value); - int line = i * 8 + bit; - int virq = ab8500_irq_get_virq(ab8500, line); - - handle_nested_irq(virq); - ab8500_debug_register_interrupt(line); - value &= ~(1 << bit); - - } while (value); - } - atomic_dec(&ab8500->transfer_ongoing); - return IRQ_HANDLED; -} - static int ab8500_irq_map(struct irq_domain *d, unsigned int virq, irq_hw_number_t hwirq) { @@ -607,7 +580,9 @@ static int ab8500_irq_init(struct ab8500 *ab8500, struct device_node *np) { int num_irqs; - if (is_ab9540(ab8500)) + if (is_ab8540(ab8500)) + num_irqs = AB8540_NR_IRQS; + else if (is_ab9540(ab8500)) num_irqs = AB9540_NR_IRQS; else if (is_ab8505(ab8500)) num_irqs = AB8505_NR_IRQS; @@ -650,6 +625,15 @@ static struct resource ab8500_gpadc_resources[] = { }, }; +static struct resource ab8505_gpadc_resources[] = { + { + .name = "SW_CONV_END", + .start = AB8500_INT_GP_SW_ADC_CONV_END, + .end = AB8500_INT_GP_SW_ADC_CONV_END, + .flags = IORESOURCE_IRQ, + }, +}; + static struct resource ab8500_rtc_resources[] = { { .name = "60S", @@ -973,6 +957,30 @@ static struct resource ab8505_iddet_resources[] = { .end = AB8505_INT_KEYSTUCK, .flags = IORESOURCE_IRQ, }, + { + .name = "VBUS_DET_R", + .start = AB8500_INT_VBUS_DET_R, + .end = AB8500_INT_VBUS_DET_R, + .flags = IORESOURCE_IRQ, + }, + { + .name = "VBUS_DET_F", + .start = AB8500_INT_VBUS_DET_F, + .end = AB8500_INT_VBUS_DET_F, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ID_DET_PLUGR", + .start = AB8500_INT_ID_DET_PLUGR, + .end = AB8500_INT_ID_DET_PLUGR, + .flags = IORESOURCE_IRQ, + }, + { + .name = "ID_DET_PLUGF", + .start = AB8500_INT_ID_DET_PLUGF, + .end = AB8500_INT_ID_DET_PLUGF, + .flags = IORESOURCE_IRQ, + }, }; static struct resource ab8500_temp_resources[] = { @@ -984,7 +992,42 @@ static struct resource ab8500_temp_resources[] = { }, }; -static struct mfd_cell abx500_common_devs[] = { +static struct mfd_cell ab8500_bm_devs[] = { + { + .name = "ab8500-charger", + .of_compatible = "stericsson,ab8500-charger", + .num_resources = ARRAY_SIZE(ab8500_charger_resources), + .resources = ab8500_charger_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-btemp", + .of_compatible = "stericsson,ab8500-btemp", + .num_resources = ARRAY_SIZE(ab8500_btemp_resources), + .resources = ab8500_btemp_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-fg", + .of_compatible = "stericsson,ab8500-fg", + .num_resources = ARRAY_SIZE(ab8500_fg_resources), + .resources = ab8500_fg_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, + { + .name = "ab8500-chargalg", + .of_compatible = "stericsson,ab8500-chargalg", + .num_resources = ARRAY_SIZE(ab8500_chargalg_resources), + .resources = ab8500_chargalg_resources, + .platform_data = &ab8500_bm_data, + .pdata_size = sizeof(ab8500_bm_data), + }, +}; + +static struct mfd_cell ab8500_devs[] = { #ifdef CONFIG_DEBUG_FS { .name = "ab8500-debug", @@ -1007,7 +1050,6 @@ static struct mfd_cell abx500_common_devs[] = { }, { .name = "ab8500-gpadc", - .of_compatible = "stericsson,ab8500-gpadc", .num_resources = ARRAY_SIZE(ab8500_gpadc_resources), .resources = ab8500_gpadc_resources, }, @@ -1024,6 +1066,7 @@ static struct mfd_cell abx500_common_devs[] = { .resources = ab8500_av_acc_detect_resources, }, { + .name = "ab8500-poweron-key", .of_compatible = "stericsson,ab8500-poweron-key", .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), @@ -1053,82 +1096,220 @@ static struct mfd_cell abx500_common_devs[] = { .of_compatible = "stericsson,ab8500-denc", }, { + .name = "ab8500-gpio", + .of_compatible = "stericsson,ab8500-gpio", + }, + { .name = "abx500-temp", .of_compatible = "stericsson,abx500-temp", .num_resources = ARRAY_SIZE(ab8500_temp_resources), .resources = ab8500_temp_resources, }, + { + .name = "ab8500-usb", + .num_resources = ARRAY_SIZE(ab8500_usb_resources), + .resources = ab8500_usb_resources, + }, + { + .name = "ab8500-codec", + }, }; -static struct mfd_cell ab8500_bm_devs[] = { +static struct mfd_cell ab9540_devs[] = { +#ifdef CONFIG_DEBUG_FS { - .name = "ab8500-charger", - .of_compatible = "stericsson,ab8500-charger", - .num_resources = ARRAY_SIZE(ab8500_charger_resources), - .resources = ab8500_charger_resources, - .platform_data = &ab8500_bm_data, - .pdata_size = sizeof(ab8500_bm_data), + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, }, +#endif { - .name = "ab8500-btemp", - .of_compatible = "stericsson,ab8500-btemp", - .num_resources = ARRAY_SIZE(ab8500_btemp_resources), - .resources = ab8500_btemp_resources, - .platform_data = &ab8500_bm_data, - .pdata_size = sizeof(ab8500_bm_data), + .name = "ab8500-sysctrl", }, { - .name = "ab8500-fg", - .of_compatible = "stericsson,ab8500-fg", - .num_resources = ARRAY_SIZE(ab8500_fg_resources), - .resources = ab8500_fg_resources, - .platform_data = &ab8500_bm_data, - .pdata_size = sizeof(ab8500_bm_data), + .name = "ab8500-regulator", }, { - .name = "ab8500-chargalg", - .of_compatible = "stericsson,ab8500-chargalg", - .num_resources = ARRAY_SIZE(ab8500_chargalg_resources), - .resources = ab8500_chargalg_resources, - .platform_data = &ab8500_bm_data, - .pdata_size = sizeof(ab8500_bm_data), + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .of_compatible = "stericsson,ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8500_gpadc_resources), + .resources = ab8500_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "ab8500-leds", + }, + { + .name = "abx500-temp", + .num_resources = ARRAY_SIZE(ab8500_temp_resources), + .resources = ab8500_temp_resources, + }, + { + .name = "pinctrl-ab9540", + .of_compatible = "stericsson,ab9540-gpio", + }, + { + .name = "ab9540-usb", + .num_resources = ARRAY_SIZE(ab8500_usb_resources), + .resources = ab8500_usb_resources, + }, + { + .name = "ab9540-codec", + }, + { + .name = "ab-iddet", + .num_resources = ARRAY_SIZE(ab8505_iddet_resources), + .resources = ab8505_iddet_resources, }, }; -static struct mfd_cell ab8500_devs[] = { +/* Device list for ab8505 */ +static struct mfd_cell ab8505_devs[] = { +#ifdef CONFIG_DEBUG_FS { - .name = "pinctrl-ab8500", - .of_compatible = "stericsson,ab8500-gpio", + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, + }, +#endif + { + .name = "ab8500-sysctrl", + }, + { + .name = "ab8500-regulator", + }, + { + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8505_gpadc_resources), + .resources = ab8505_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "ab8500-leds", + }, + { + .name = "ab8500-gpio", }, { .name = "ab8500-usb", - .of_compatible = "stericsson,ab8500-usb", .num_resources = ARRAY_SIZE(ab8500_usb_resources), .resources = ab8500_usb_resources, }, { .name = "ab8500-codec", - .of_compatible = "stericsson,ab8500-codec", + }, + { + .name = "ab-iddet", + .num_resources = ARRAY_SIZE(ab8505_iddet_resources), + .resources = ab8505_iddet_resources, }, }; -static struct mfd_cell ab9540_devs[] = { +static struct mfd_cell ab8540_devs[] = { +#ifdef CONFIG_DEBUG_FS { - .name = "pinctrl-ab9540", - .of_compatible = "stericsson,ab9540-gpio", + .name = "ab8500-debug", + .num_resources = ARRAY_SIZE(ab8500_debug_resources), + .resources = ab8500_debug_resources, + }, +#endif + { + .name = "ab8500-sysctrl", }, { - .name = "ab9540-usb", + .name = "ab8500-regulator", + }, + { + .name = "abx500-clk", + .of_compatible = "stericsson,abx500-clk", + }, + { + .name = "ab8500-gpadc", + .num_resources = ARRAY_SIZE(ab8505_gpadc_resources), + .resources = ab8505_gpadc_resources, + }, + { + .name = "ab8500-rtc", + .num_resources = ARRAY_SIZE(ab8500_rtc_resources), + .resources = ab8500_rtc_resources, + }, + { + .name = "ab8500-acc-det", + .num_resources = ARRAY_SIZE(ab8500_av_acc_detect_resources), + .resources = ab8500_av_acc_detect_resources, + }, + { + .name = "ab8500-poweron-key", + .num_resources = ARRAY_SIZE(ab8500_poweronkey_db_resources), + .resources = ab8500_poweronkey_db_resources, + }, + { + .name = "ab8500-pwm", + .id = 1, + }, + { + .name = "ab8500-leds", + }, + { + .name = "abx500-temp", + .num_resources = ARRAY_SIZE(ab8500_temp_resources), + .resources = ab8500_temp_resources, + }, + { + .name = "ab8500-gpio", + }, + { + .name = "ab8540-usb", .num_resources = ARRAY_SIZE(ab8500_usb_resources), .resources = ab8500_usb_resources, }, { - .name = "ab9540-codec", + .name = "ab8540-codec", }, -}; - -/* Device list common to ab9540 and ab8505 */ -static struct mfd_cell ab9540_ab8505_devs[] = { { .name = "ab-iddet", .num_resources = ARRAY_SIZE(ab8505_iddet_resources), @@ -1142,6 +1323,7 @@ static ssize_t show_chip_id(struct device *dev, struct ab8500 *ab8500; ab8500 = dev_get_drvdata(dev); + return sprintf(buf, "%#x\n", ab8500 ? ab8500->chip_id : -EINVAL); } @@ -1171,6 +1353,15 @@ static ssize_t show_switch_off_status(struct device *dev, return sprintf(buf, "%#x\n", value); } +/* use mask and set to override the register turn_on_stat value */ +void ab8500_override_turn_on_stat(u8 mask, u8 set) +{ + spin_lock(&on_stat_lock); + turn_on_stat_mask = mask; + turn_on_stat_set = set; + spin_unlock(&on_stat_lock); +} + /* * ab8500 has turned on due to (TURN_ON_STATUS): * 0x01 PORnVbat @@ -1194,9 +1385,38 @@ static ssize_t show_turn_on_status(struct device *dev, AB8500_TURN_ON_STATUS, &value); if (ret < 0) return ret; + + /* + * In L9540, turn_on_status register is not updated correctly if + * the device is rebooted with AC/USB charger connected. Due to + * this, the device boots android instead of entering into charge + * only mode. Read the AC/USB status register to detect the charger + * presence and update the turn on status manually. + */ + if (is_ab9540(ab8500)) { + spin_lock(&on_stat_lock); + value = (value & turn_on_stat_mask) | turn_on_stat_set; + spin_unlock(&on_stat_lock); + } + return sprintf(buf, "%#x\n", value); } +static ssize_t show_turn_on_status_2(struct device *dev, + struct device_attribute *attr, char *buf) +{ + int ret; + u8 value; + struct ab8500 *ab8500; + + ab8500 = dev_get_drvdata(dev); + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8505_TURN_ON_STATUS_2, &value); + if (ret < 0) + return ret; + return sprintf(buf, "%#x\n", (value & 0x1)); +} + static ssize_t show_ab9540_dbbrstn(struct device *dev, struct device_attribute *attr, char *buf) { @@ -1253,6 +1473,7 @@ exit: static DEVICE_ATTR(chip_id, S_IRUGO, show_chip_id, NULL); static DEVICE_ATTR(switch_off_status, S_IRUGO, show_switch_off_status, NULL); static DEVICE_ATTR(turn_on_status, S_IRUGO, show_turn_on_status, NULL); +static DEVICE_ATTR(turn_on_status_2, S_IRUGO, show_turn_on_status_2, NULL); static DEVICE_ATTR(dbbrstn, S_IRUGO | S_IWUSR, show_ab9540_dbbrstn, store_ab9540_dbbrstn); @@ -1263,6 +1484,11 @@ static struct attribute *ab8500_sysfs_entries[] = { NULL, }; +static struct attribute *ab8505_sysfs_entries[] = { + &dev_attr_turn_on_status_2.attr, + NULL, +}; + static struct attribute *ab9540_sysfs_entries[] = { &dev_attr_chip_id.attr, &dev_attr_switch_off_status.attr, @@ -1275,6 +1501,10 @@ static struct attribute_group ab8500_attr_group = { .attrs = ab8500_sysfs_entries, }; +static struct attribute_group ab8505_attr_group = { + .attrs = ab8505_sysfs_entries, +}; + static struct attribute_group ab9540_attr_group = { .attrs = ab9540_sysfs_entries, }; @@ -1290,6 +1520,15 @@ static int ab8500_probe(struct platform_device *pdev) "Battery level lower than power on reset threshold", "Power on key 1 pressed longer than 10 seconds", "DB8500 thermal shutdown"}; + static char *turn_on_status[] = { + "Battery rising (Vbat)", + "Power On Key 1 dbF", + "Power On Key 2 dbF", + "RTC Alarm", + "Main Charger Detect", + "Vbus Detect (USB)", + "USB ID Detect", + "UART Factory Mode Detect"}; struct ab8500_platform_data *plat = dev_get_platdata(&pdev->dev); const struct platform_device_id *platid = platform_get_device_id(pdev); enum ab8500_version version = AB8500_VERSION_UNDEFINED; @@ -1351,13 +1590,20 @@ static int ab8500_probe(struct platform_device *pdev) ab8500->chip_id >> 4, ab8500->chip_id & 0x0F); - /* Configure AB8500 or AB9540 IRQ */ - if (is_ab9540(ab8500) || is_ab8505(ab8500)) { + /* Configure AB8540 */ + if (is_ab8540(ab8500)) { + ab8500->mask_size = AB8540_NUM_IRQ_REGS; + ab8500->irq_reg_offset = ab8540_irq_regoffset; + ab8500->it_latchhier_num = AB8540_IT_LATCHHIER_NUM; + }/* Configure AB8500 or AB9540 IRQ */ + else if (is_ab9540(ab8500) || is_ab8505(ab8500)) { ab8500->mask_size = AB9540_NUM_IRQ_REGS; ab8500->irq_reg_offset = ab9540_irq_regoffset; + ab8500->it_latchhier_num = AB8500_IT_LATCHHIER_NUM; } else { ab8500->mask_size = AB8500_NUM_IRQ_REGS; ab8500->irq_reg_offset = ab8500_irq_regoffset; + ab8500->it_latchhier_num = AB8500_IT_LATCHHIER_NUM; } ab8500->mask = devm_kzalloc(&pdev->dev, ab8500->mask_size, GFP_KERNEL); if (!ab8500->mask) @@ -1396,10 +1642,36 @@ static int ab8500_probe(struct platform_device *pdev) } else { printk(KERN_CONT " None\n"); } + ret = get_register_interruptible(ab8500, AB8500_SYS_CTRL1_BLOCK, + AB8500_TURN_ON_STATUS, &value); + if (ret < 0) + return ret; + dev_info(ab8500->dev, "turn on reason(s) (%#x): ", value); + + if (value) { + for (i = 0; i < ARRAY_SIZE(turn_on_status); i++) { + if (value & 1) + printk("\"%s\" ", turn_on_status[i]); + value = value >> 1; + } + printk("\n"); + } else { + printk("None\n"); + } if (plat && plat->init) plat->init(ab8500); + if (is_ab9540(ab8500)) { + ret = get_register_interruptible(ab8500, AB8500_CHARGER, + AB8500_CH_USBCH_STAT1_REG, &value); + if (ret < 0) + return ret; + if ((value & VBUS_DET_DBNC1) && (value & VBUS_DET_DBNC100)) + ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON, + AB8500_VBUS_DET); + } + /* Clear and mask all interrupts */ for (i = 0; i < ab8500->mask_size; i++) { /* @@ -1410,6 +1682,9 @@ static int ab8500_probe(struct platform_device *pdev) is_ab8500_1p1_or_earlier(ab8500)) continue; + if (ab8500->irq_reg_offset[i] < 0) + continue; + get_register_interruptible(ab8500, AB8500_INTERRUPT, AB8500_IT_LATCH1_REG + ab8500->irq_reg_offset[i], &value); @@ -1428,26 +1703,10 @@ static int ab8500_probe(struct platform_device *pdev) if (ret) return ret; - /* Activate this feature only in ab9540 */ - /* till tests are done on ab8500 1p2 or later*/ - if (is_ab9540(ab8500)) { - ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL, - ab8500_hierarchical_irq, - IRQF_ONESHOT | IRQF_NO_SUSPEND, - "ab8500", ab8500); - } - else { - ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL, - ab8500_irq, - IRQF_ONESHOT | IRQF_NO_SUSPEND, - "ab8500", ab8500); - if (ret) - return ret; - } - - ret = mfd_add_devices(ab8500->dev, 0, abx500_common_devs, - ARRAY_SIZE(abx500_common_devs), NULL, - ab8500->irq_base, ab8500->domain); + ret = devm_request_threaded_irq(&pdev->dev, ab8500->irq, NULL, + ab8500_hierarchical_irq, + IRQF_ONESHOT | IRQF_NO_SUSPEND, + "ab8500", ab8500); if (ret) return ret; @@ -1455,6 +1714,14 @@ static int ab8500_probe(struct platform_device *pdev) ret = mfd_add_devices(ab8500->dev, 0, ab9540_devs, ARRAY_SIZE(ab9540_devs), NULL, ab8500->irq_base, ab8500->domain); + else if (is_ab8540(ab8500)) + ret = mfd_add_devices(ab8500->dev, 0, ab8540_devs, + ARRAY_SIZE(ab8540_devs), NULL, + ab8500->irq_base, ab8500->domain); + else if (is_ab8505(ab8500)) + ret = mfd_add_devices(ab8500->dev, 0, ab8505_devs, + ARRAY_SIZE(ab8505_devs), NULL, + ab8500->irq_base, ab8500->domain); else ret = mfd_add_devices(ab8500->dev, 0, ab8500_devs, ARRAY_SIZE(ab8500_devs), NULL, @@ -1462,13 +1729,6 @@ static int ab8500_probe(struct platform_device *pdev) if (ret) return ret; - if (is_ab9540(ab8500) || is_ab8505(ab8500)) - ret = mfd_add_devices(ab8500->dev, 0, ab9540_ab8505_devs, - ARRAY_SIZE(ab9540_ab8505_devs), NULL, - ab8500->irq_base, ab8500->domain); - if (ret) - return ret; - if (!no_bm) { /* Add battery management devices */ ret = mfd_add_devices(ab8500->dev, 0, ab8500_bm_devs, @@ -1478,12 +1738,19 @@ static int ab8500_probe(struct platform_device *pdev) dev_err(ab8500->dev, "error adding bm devices\n"); } - if (is_ab9540(ab8500)) + if (((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) || is_ab8540(ab8500)) ret = sysfs_create_group(&ab8500->dev->kobj, &ab9540_attr_group); else ret = sysfs_create_group(&ab8500->dev->kobj, &ab8500_attr_group); + + if ((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) + ret = sysfs_create_group(&ab8500->dev->kobj, + &ab8505_attr_group); + if (ret) dev_err(ab8500->dev, "error creating sysfs entries\n"); @@ -1494,11 +1761,16 @@ static int ab8500_remove(struct platform_device *pdev) { struct ab8500 *ab8500 = platform_get_drvdata(pdev); - if (is_ab9540(ab8500)) + if (((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) || is_ab8540(ab8500)) sysfs_remove_group(&ab8500->dev->kobj, &ab9540_attr_group); else sysfs_remove_group(&ab8500->dev->kobj, &ab8500_attr_group); + if ((is_ab8505(ab8500) || is_ab9540(ab8500)) && + ab8500->chip_id >= AB8500_CUT2P0) + sysfs_remove_group(&ab8500->dev->kobj, &ab8505_attr_group); + mfd_remove_devices(ab8500->dev); return 0; diff --git a/drivers/mfd/ab8500-debugfs.c b/drivers/mfd/ab8500-debugfs.c index 45fe3c50eb0..b88bbbc15f1 100644 --- a/drivers/mfd/ab8500-debugfs.c +++ b/drivers/mfd/ab8500-debugfs.c @@ -80,6 +80,7 @@ #include <linux/interrupt.h> #include <linux/kobject.h> #include <linux/slab.h> +#include <linux/irq.h> #include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> @@ -90,6 +91,9 @@ #include <linux/ctype.h> #endif +/* TODO: this file should not reference IRQ_DB8500_AB8500! */ +#include <mach/irqs.h> + static u32 debug_bank; static u32 debug_address; @@ -101,6 +105,11 @@ static int num_irqs; static struct device_attribute **dev_attr; static char **event_name; +static u8 avg_sample = SAMPLE_16; +static u8 trig_edge = RISING_EDGE; +static u8 conv_type = ADC_SW; +static u8 trig_timer; + /** * struct ab8500_reg_range * @first: the first address of the range @@ -150,7 +159,9 @@ static struct hwreg_cfg hwreg_cfg = { #define AB8500_REV_REG 0x80 -static struct ab8500_prcmu_ranges debug_ranges[AB8500_NUM_BANKS] = { +static struct ab8500_prcmu_ranges *debug_ranges; + +struct ab8500_prcmu_ranges ab8500_debug_ranges[AB8500_NUM_BANKS] = { [0x0] = { .num_ranges = 0, .range = NULL, @@ -354,7 +365,7 @@ static struct ab8500_prcmu_ranges debug_ranges[AB8500_NUM_BANKS] = { }, { .first = 0xf5, - .last = 0xf6, + .last = 0xf6, }, }, }, @@ -479,6 +490,781 @@ static struct ab8500_prcmu_ranges debug_ranges[AB8500_NUM_BANKS] = { }, }; +struct ab8500_prcmu_ranges ab8505_debug_ranges[AB8500_NUM_BANKS] = { + [0x0] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_SYS_CTRL1_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x42, + .last = 0x42, + }, + { + .first = 0x52, + .last = 0x52, + }, + { + .first = 0x54, + .last = 0x57, + }, + { + .first = 0x80, + .last = 0x83, + }, + }, + }, + [AB8500_SYS_CTRL2_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0D, + }, + { + .first = 0x0F, + .last = 0x17, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x30, + .last = 0x30, + }, + { + .first = 0x32, + .last = 0x3A, + }, + }, + }, + [AB8500_REGU_CTRL1] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x03, + .last = 0x11, + }, + { + .first = 0x80, + .last = 0x86, + }, + }, + }, + [AB8500_REGU_CTRL2] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x08, + .last = 0x15, + }, + { + .first = 0x17, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x1D, + }, + { + .first = 0x1F, + .last = 0x30, + }, + { + .first = 0x40, + .last = 0x48, + }, + /* 0x80-0x8B is SIM registers and should + * not be accessed from here */ + }, + }, + [AB8500_USB] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8A, + }, + { + .first = 0x91, + .last = 0x94, + }, + }, + }, + [AB8500_TVOUT] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_DBI] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_ECI_AV_ACC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_RESERVED] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_GPADC] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x08, + }, + }, + }, + [AB8500_CHARGER] = { + .num_ranges = 9, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x02, + .last = 0x03, + }, + { + .first = 0x05, + .last = 0x05, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x57, + }, + { + .first = 0x60, + .last = 0x60, + }, + { + .first = 0xA0, + .last = 0xA7, + }, + { + .first = 0xAF, + .last = 0xB2, + }, + { + .first = 0xC0, + .last = 0xC2, + }, + { + .first = 0xF5, + .last = 0xF5, + }, + }, + }, + [AB8500_GAS_GAUGE] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x07, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_AUDIO] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x83, + }, + }, + }, + [AB8500_INTERRUPT] = { + .num_ranges = 11, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x06, + .last = 0x07, + }, + { + .first = 0x09, + .last = 0x09, + }, + { + .first = 0x0B, + .last = 0x0C, + }, + { + .first = 0x12, + .last = 0x15, + }, + { + .first = 0x18, + .last = 0x18, + }, + /* Latch registers should not be read here */ + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x46, + .last = 0x49, + }, + { + .first = 0x4B, + .last = 0x4D, + }, + { + .first = 0x52, + .last = 0x55, + }, + { + .first = 0x58, + .last = 0x58, + }, + /* LatchHier registers should not be read here */ + }, + }, + [AB8500_RTC] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x14, + }, + { + .first = 0x16, + .last = 0x17, + }, + }, + }, + [AB8500_MISC] = { + .num_ranges = 8, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x16, + }, + { + .first = 0x20, + .last = 0x26, + }, + { + .first = 0x30, + .last = 0x36, + }, + { + .first = 0x40, + .last = 0x46, + }, + { + .first = 0x50, + .last = 0x50, + }, + { + .first = 0x60, + .last = 0x6B, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x05, + .last = 0x05, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x05, + .last = 0x07, + }, + }, + }, + [AB8500_PROD_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_STE_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_OTP_EMUL] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x01, + .last = 0x15, + }, + }, + }, +}; + +struct ab8500_prcmu_ranges ab8540_debug_ranges[AB8500_NUM_BANKS] = { + [AB8500_M_FSM_RANK] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0B, + }, + }, + }, + [AB8500_SYS_CTRL1_BLOCK] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x04, + }, + { + .first = 0x42, + .last = 0x42, + }, + { + .first = 0x50, + .last = 0x54, + }, + { + .first = 0x57, + .last = 0x57, + }, + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x90, + .last = 0x90, + }, + }, + }, + [AB8500_SYS_CTRL2_BLOCK] = { + .num_ranges = 5, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x0D, + }, + { + .first = 0x0F, + .last = 0x10, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x32, + .last = 0x3C, + }, + { + .first = 0x40, + .last = 0x42, + }, + }, + }, + [AB8500_REGU_CTRL1] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x03, + .last = 0x15, + }, + { + .first = 0x20, + .last = 0x20, + }, + { + .first = 0x80, + .last = 0x85, + }, + { + .first = 0x87, + .last = 0x88, + }, + }, + }, + [AB8500_REGU_CTRL2] = { + .num_ranges = 8, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x08, + .last = 0x15, + }, + { + .first = 0x17, + .last = 0x19, + }, + { + .first = 0x1B, + .last = 0x1D, + }, + { + .first = 0x1F, + .last = 0x2F, + }, + { + .first = 0x31, + .last = 0x3A, + }, + { + .first = 0x43, + .last = 0x44, + }, + { + .first = 0x48, + .last = 0x49, + }, + }, + }, + [AB8500_USB] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x80, + .last = 0x83, + }, + { + .first = 0x87, + .last = 0x8A, + }, + { + .first = 0x91, + .last = 0x94, + }, + }, + }, + [AB8500_TVOUT] = { + .num_ranges = 0, + .range = NULL + }, + [AB8500_DBI] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x10, + .last = 0x11, + }, + { + .first = 0x20, + .last = 0x21, + }, + { + .first = 0x30, + .last = 0x43, + }, + }, + }, + [AB8500_ECI_AV_ACC] = { + .num_ranges = 2, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x03, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_RESERVED] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_GPADC] = { + .num_ranges = 4, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x04, + .last = 0x06, + }, + { + .first = 0x09, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_CHARGER] = { + .num_ranges = 10, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x02, + .last = 0x05, + }, + { + .first = 0x40, + .last = 0x44, + }, + { + .first = 0x50, + .last = 0x57, + }, + { + .first = 0x60, + .last = 0x60, + }, + { + .first = 0x70, + .last = 0x70, + }, + { + .first = 0xA0, + .last = 0xA9, + }, + { + .first = 0xAF, + .last = 0xB2, + }, + { + .first = 0xC0, + .last = 0xC6, + }, + { + .first = 0xF5, + .last = 0xF5, + }, + }, + }, + [AB8500_GAS_GAUGE] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x00, + }, + { + .first = 0x07, + .last = 0x0A, + }, + { + .first = 0x10, + .last = 0x14, + }, + }, + }, + [AB8500_AUDIO] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x9f, + }, + }, + }, + [AB8500_INTERRUPT] = { + .num_ranges = 6, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x05, + }, + { + .first = 0x0B, + .last = 0x0D, + }, + { + .first = 0x12, + .last = 0x20, + }, + /* Latch registers should not be read here */ + { + .first = 0x40, + .last = 0x45, + }, + { + .first = 0x4B, + .last = 0x4D, + }, + { + .first = 0x52, + .last = 0x60, + }, + /* LatchHier registers should not be read here */ + }, + }, + [AB8500_RTC] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x07, + }, + { + .first = 0x0B, + .last = 0x18, + }, + { + .first = 0x20, + .last = 0x25, + }, + }, + }, + [AB8500_MISC] = { + .num_ranges = 9, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x16, + }, + { + .first = 0x20, + .last = 0x26, + }, + { + .first = 0x30, + .last = 0x36, + }, + { + .first = 0x40, + .last = 0x49, + }, + { + .first = 0x50, + .last = 0x50, + }, + { + .first = 0x60, + .last = 0x6B, + }, + { + .first = 0x70, + .last = 0x74, + }, + { + .first = 0x80, + .last = 0x82, + }, + }, + }, + [AB8500_DEVELOPMENT] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x01, + }, + { + .first = 0x06, + .last = 0x06, + }, + { + .first = 0x10, + .last = 0x21, + }, + }, + }, + [AB8500_DEBUG] = { + .num_ranges = 3, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x01, + .last = 0x0C, + }, + { + .first = 0x0E, + .last = 0x11, + }, + { + .first = 0x80, + .last = 0x81, + }, + }, + }, + [AB8500_PROD_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_STE_TEST] = { + .num_ranges = 0, + .range = NULL, + }, + [AB8500_OTP_EMUL] = { + .num_ranges = 1, + .range = (struct ab8500_reg_range[]) { + { + .first = 0x00, + .last = 0x3F, + }, + }, + }, +}; + + static irqreturn_t ab8500_debug_handler(int irq, void *data) { char buf[16]; @@ -520,19 +1306,16 @@ static int ab8500_registers_print(struct device *dev, u32 bank, } if (s) { - err = seq_printf(s, " [%u/0x%02X]: 0x%02X\n", + err = seq_printf(s, " [0x%02X/0x%02X]: 0x%02X\n", bank, reg, value); if (err < 0) { - dev_err(dev, - "seq_printf overflow bank=%d reg=%d\n", - bank, reg); /* Error is not returned here since * the output is wanted in any case */ return 0; } } else { - printk(KERN_INFO" [%u/0x%02X]: 0x%02X\n", bank, - reg, value); + printk(KERN_INFO" [0x%02X/0x%02X]: 0x%02X\n", + bank, reg, value); } } } @@ -546,7 +1329,7 @@ static int ab8500_print_bank_registers(struct seq_file *s, void *p) seq_printf(s, AB8500_NAME_STRING " register values:\n"); - seq_printf(s, " bank %u:\n", bank); + seq_printf(s, " bank 0x%02X:\n", bank); ab8500_registers_print(dev, bank, s); return 0; @@ -573,10 +1356,8 @@ static int ab8500_print_all_banks(struct seq_file *s, void *p) seq_printf(s, AB8500_NAME_STRING " register values:\n"); - for (i = 1; i < AB8500_NUM_BANKS; i++) { - err = seq_printf(s, " bank %u:\n", i); - if (err < 0) - dev_err(dev, "seq_printf overflow, bank=%d\n", i); + for (i = 0; i < AB8500_NUM_BANKS; i++) { + err = seq_printf(s, " bank 0x%02X:\n", i); ab8500_registers_print(dev, i, s); } @@ -591,11 +1372,68 @@ void ab8500_dump_all_banks(struct device *dev) printk(KERN_INFO"ab8500 register values:\n"); for (i = 1; i < AB8500_NUM_BANKS; i++) { - printk(KERN_INFO" bank %u:\n", i); + printk(KERN_INFO" bank 0x%02X:\n", i); ab8500_registers_print(dev, i, NULL); } } +/* Space for 500 registers. */ +#define DUMP_MAX_REGS 700 +struct ab8500_register_dump +{ + u8 bank; + u8 reg; + u8 value; +} ab8500_complete_register_dump[DUMP_MAX_REGS]; + +extern int prcmu_abb_read(u8 slave, u8 reg, u8 *value, u8 size); + +/* This shall only be called upon kernel panic! */ +void ab8500_dump_all_banks_to_mem(void) +{ + int i, r = 0; + u8 bank; + int err = 0; + + pr_info("Saving all ABB registers at \"ab8500_complete_register_dump\" " + "for crash analyze.\n"); + + for (bank = 0; bank < AB8500_NUM_BANKS; bank++) { + for (i = 0; i < debug_ranges[bank].num_ranges; i++) { + u8 reg; + + for (reg = debug_ranges[bank].range[i].first; + reg <= debug_ranges[bank].range[i].last; + reg++) { + u8 value; + + err = prcmu_abb_read(bank, reg, &value, 1); + + if (err < 0) + goto out; + + ab8500_complete_register_dump[r].bank = bank; + ab8500_complete_register_dump[r].reg = reg; + ab8500_complete_register_dump[r].value = value; + + r++; + + if (r >= DUMP_MAX_REGS) { + pr_err("%s: too many register to dump!\n", + __func__); + err = -EINVAL; + goto out; + } + } + } + } +out: + if (err >= 0) + pr_info("Saved all ABB registers.\n"); + else + pr_info("Failed to save all ABB registers.\n"); +} + static int ab8500_all_banks_open(struct inode *inode, struct file *file) { struct seq_file *s; @@ -625,7 +1463,7 @@ static const struct file_operations ab8500_all_banks_fops = { static int ab8500_bank_print(struct seq_file *s, void *p) { - return seq_printf(s, "%d\n", debug_bank); + return seq_printf(s, "0x%02X\n", debug_bank); } static int ab8500_bank_open(struct inode *inode, struct file *file) @@ -641,7 +1479,6 @@ static ssize_t ab8500_bank_write(struct file *file, unsigned long user_bank; int err; - /* Get userspace string and assure termination */ err = kstrtoul_from_user(user_buf, count, 0, &user_bank); if (err) return err; @@ -667,14 +1504,13 @@ static int ab8500_address_open(struct inode *inode, struct file *file) } static ssize_t ab8500_address_write(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) + const char __user *user_buf, + size_t count, loff_t *ppos) { struct device *dev = ((struct seq_file *)(file->private_data))->private; unsigned long user_address; int err; - /* Get userspace string and assure termination */ err = kstrtoul_from_user(user_buf, count, 0, &user_address); if (err) return err; @@ -684,6 +1520,7 @@ static ssize_t ab8500_address_write(struct file *file, return -EINVAL; } debug_address = user_address; + return count; } @@ -711,14 +1548,13 @@ static int ab8500_val_open(struct inode *inode, struct file *file) } static ssize_t ab8500_val_write(struct file *file, - const char __user *user_buf, - size_t count, loff_t *ppos) + const char __user *user_buf, + size_t count, loff_t *ppos) { struct device *dev = ((struct seq_file *)(file->private_data))->private; unsigned long user_val; int err; - /* Get userspace string and assure termination */ err = kstrtoul_from_user(user_buf, count, 0, &user_val); if (err) return err; @@ -741,22 +1577,46 @@ static ssize_t ab8500_val_write(struct file *file, * Interrupt status */ static u32 num_interrupts[AB8500_MAX_NR_IRQS]; +static u32 num_wake_interrupts[AB8500_MAX_NR_IRQS]; static int num_interrupt_lines; +bool __attribute__((weak)) suspend_test_wake_cause_interrupt_is_mine(u32 my_int) +{ + return false; +} + void ab8500_debug_register_interrupt(int line) { - if (line < num_interrupt_lines) + if (line < num_interrupt_lines) { num_interrupts[line]++; + if (suspend_test_wake_cause_interrupt_is_mine(IRQ_DB8500_AB8500)) + num_wake_interrupts[line]++; + } } static int ab8500_interrupts_print(struct seq_file *s, void *p) { int line; - seq_printf(s, "irq: number of\n"); + seq_printf(s, "name: number: number of: wake:\n"); - for (line = 0; line < num_interrupt_lines; line++) - seq_printf(s, "%3i: %6i\n", line, num_interrupts[line]); + for (line = 0; line < num_interrupt_lines; line++) { + struct irq_desc *desc = irq_to_desc(line + irq_first); + struct irqaction *action = desc->action; + + seq_printf(s, "%3i: %6i %4i", line, + num_interrupts[line], + num_wake_interrupts[line]); + + if (desc && desc->name) + seq_printf(s, "-%-8s", desc->name); + if (action) { + seq_printf(s, " %s", action->name); + while ((action = action->next) != NULL) + seq_printf(s, ", %s", action->name); + } + seq_putc(s, '\n'); + } return 0; } @@ -801,6 +1661,79 @@ static int ab8500_hwreg_open(struct inode *inode, struct file *file) return single_open(file, ab8500_hwreg_print, inode->i_private); } +#define AB8500_SUPPLY_CONTROL_CONFIG_1 0x01 +#define AB8500_SUPPLY_CONTROL_REG 0x00 +#define AB8500_FIRST_SIM_REG 0x80 +#define AB8500_LAST_SIM_REG 0x8B +#define AB8505_LAST_SIM_REG 0x8C + +static int ab8500_print_modem_registers(struct seq_file *s, void *p) +{ + struct device *dev = s->private; + struct ab8500 *ab8500; + int err; + u8 value; + u8 orig_value; + u32 bank = AB8500_REGU_CTRL2; + u32 last_sim_reg = AB8500_LAST_SIM_REG; + u32 reg; + + ab8500 = dev_get_drvdata(dev->parent); + dev_warn(dev, "WARNING! This operation can interfer with modem side\n" + "and should only be done with care\n"); + + err = abx500_get_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, &orig_value); + if (err < 0) { + dev_err(dev, "ab->read fail %d\n", err); + return err; + } + /* Config 1 will allow APE side to read SIM registers */ + err = abx500_set_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, + AB8500_SUPPLY_CONTROL_CONFIG_1); + if (err < 0) { + dev_err(dev, "ab->write fail %d\n", err); + return err; + } + + seq_printf(s, " bank 0x%02X:\n", bank); + + if (is_ab9540(ab8500) || is_ab8505(ab8500)) + last_sim_reg = AB8505_LAST_SIM_REG; + + for (reg = AB8500_FIRST_SIM_REG; reg <= last_sim_reg; reg++) { + err = abx500_get_register_interruptible(dev, + bank, reg, &value); + if (err < 0) { + dev_err(dev, "ab->read fail %d\n", err); + return err; + } + err = seq_printf(s, " [0x%02X/0x%02X]: 0x%02X\n", + bank, reg, value); + } + err = abx500_set_register_interruptible(dev, + AB8500_REGU_CTRL1, AB8500_SUPPLY_CONTROL_REG, orig_value); + if (err < 0) { + dev_err(dev, "ab->write fail %d\n", err); + return err; + } + return 0; +} + +static int ab8500_modem_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_print_modem_registers, inode->i_private); +} + +static const struct file_operations ab8500_modem_fops = { + .open = ab8500_modem_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + static int ab8500_gpadc_bat_ctrl_print(struct seq_file *s, void *p) { int bat_ctrl_raw; @@ -808,12 +1741,13 @@ static int ab8500_gpadc_bat_ctrl_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - bat_ctrl_raw = ab8500_gpadc_read_raw(gpadc, BAT_CTRL); + bat_ctrl_raw = ab8500_gpadc_read_raw(gpadc, BAT_CTRL, + avg_sample, trig_edge, trig_timer, conv_type); bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, - BAT_CTRL, bat_ctrl_raw); + BAT_CTRL, bat_ctrl_raw); return seq_printf(s, "%d,0x%X\n", - bat_ctrl_convert, bat_ctrl_raw); + bat_ctrl_convert, bat_ctrl_raw); } static int ab8500_gpadc_bat_ctrl_open(struct inode *inode, struct file *file) @@ -836,16 +1770,17 @@ static int ab8500_gpadc_btemp_ball_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - btemp_ball_raw = ab8500_gpadc_read_raw(gpadc, BTEMP_BALL); + btemp_ball_raw = ab8500_gpadc_read_raw(gpadc, BTEMP_BALL, + avg_sample, trig_edge, trig_timer, conv_type); btemp_ball_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, - btemp_ball_raw); + btemp_ball_raw); return seq_printf(s, - "%d,0x%X\n", btemp_ball_convert, btemp_ball_raw); + "%d,0x%X\n", btemp_ball_convert, btemp_ball_raw); } static int ab8500_gpadc_btemp_ball_open(struct inode *inode, - struct file *file) + struct file *file) { return single_open(file, ab8500_gpadc_btemp_ball_print, inode->i_private); } @@ -865,19 +1800,20 @@ static int ab8500_gpadc_main_charger_v_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - main_charger_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_V); + main_charger_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_V, + avg_sample, trig_edge, trig_timer, conv_type); main_charger_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, - MAIN_CHARGER_V, main_charger_v_raw); + MAIN_CHARGER_V, main_charger_v_raw); return seq_printf(s, "%d,0x%X\n", main_charger_v_convert, main_charger_v_raw); } static int ab8500_gpadc_main_charger_v_open(struct inode *inode, - struct file *file) + struct file *file) { return single_open(file, ab8500_gpadc_main_charger_v_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_main_charger_v_fops = { @@ -895,19 +1831,20 @@ static int ab8500_gpadc_acc_detect1_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - acc_detect1_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT1); + acc_detect1_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT1, + avg_sample, trig_edge, trig_timer, conv_type); acc_detect1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ACC_DETECT1, - acc_detect1_raw); + acc_detect1_raw); return seq_printf(s, "%d,0x%X\n", - acc_detect1_convert, acc_detect1_raw); + acc_detect1_convert, acc_detect1_raw); } static int ab8500_gpadc_acc_detect1_open(struct inode *inode, - struct file *file) + struct file *file) { return single_open(file, ab8500_gpadc_acc_detect1_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_acc_detect1_fops = { @@ -925,19 +1862,20 @@ static int ab8500_gpadc_acc_detect2_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - acc_detect2_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT2); + acc_detect2_raw = ab8500_gpadc_read_raw(gpadc, ACC_DETECT2, + avg_sample, trig_edge, trig_timer, conv_type); acc_detect2_convert = ab8500_gpadc_ad_to_voltage(gpadc, - ACC_DETECT2, acc_detect2_raw); + ACC_DETECT2, acc_detect2_raw); return seq_printf(s, "%d,0x%X\n", - acc_detect2_convert, acc_detect2_raw); + acc_detect2_convert, acc_detect2_raw); } static int ab8500_gpadc_acc_detect2_open(struct inode *inode, struct file *file) { return single_open(file, ab8500_gpadc_acc_detect2_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_acc_detect2_fops = { @@ -955,12 +1893,13 @@ static int ab8500_gpadc_aux1_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - aux1_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX1); + aux1_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX1, + avg_sample, trig_edge, trig_timer, conv_type); aux1_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX1, - aux1_raw); + aux1_raw); return seq_printf(s, "%d,0x%X\n", - aux1_convert, aux1_raw); + aux1_convert, aux1_raw); } static int ab8500_gpadc_aux1_open(struct inode *inode, struct file *file) @@ -983,9 +1922,10 @@ static int ab8500_gpadc_aux2_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - aux2_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX2); + aux2_raw = ab8500_gpadc_read_raw(gpadc, ADC_AUX2, + avg_sample, trig_edge, trig_timer, conv_type); aux2_convert = ab8500_gpadc_ad_to_voltage(gpadc, ADC_AUX2, - aux2_raw); + aux2_raw); return seq_printf(s, "%d,0x%X\n", aux2_convert, aux2_raw); @@ -1011,16 +1951,17 @@ static int ab8500_gpadc_main_bat_v_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - main_bat_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_BAT_V); + main_bat_v_raw = ab8500_gpadc_read_raw(gpadc, MAIN_BAT_V, + avg_sample, trig_edge, trig_timer, conv_type); main_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, - main_bat_v_raw); + main_bat_v_raw); return seq_printf(s, "%d,0x%X\n", - main_bat_v_convert, main_bat_v_raw); + main_bat_v_convert, main_bat_v_raw); } static int ab8500_gpadc_main_bat_v_open(struct inode *inode, - struct file *file) + struct file *file) { return single_open(file, ab8500_gpadc_main_bat_v_print, inode->i_private); } @@ -1040,12 +1981,13 @@ static int ab8500_gpadc_vbus_v_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - vbus_v_raw = ab8500_gpadc_read_raw(gpadc, VBUS_V); + vbus_v_raw = ab8500_gpadc_read_raw(gpadc, VBUS_V, + avg_sample, trig_edge, trig_timer, conv_type); vbus_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, VBUS_V, - vbus_v_raw); + vbus_v_raw); return seq_printf(s, "%d,0x%X\n", - vbus_v_convert, vbus_v_raw); + vbus_v_convert, vbus_v_raw); } static int ab8500_gpadc_vbus_v_open(struct inode *inode, struct file *file) @@ -1068,19 +2010,20 @@ static int ab8500_gpadc_main_charger_c_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - main_charger_c_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_C); + main_charger_c_raw = ab8500_gpadc_read_raw(gpadc, MAIN_CHARGER_C, + avg_sample, trig_edge, trig_timer, conv_type); main_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, - MAIN_CHARGER_C, main_charger_c_raw); + MAIN_CHARGER_C, main_charger_c_raw); return seq_printf(s, "%d,0x%X\n", - main_charger_c_convert, main_charger_c_raw); + main_charger_c_convert, main_charger_c_raw); } static int ab8500_gpadc_main_charger_c_open(struct inode *inode, struct file *file) { return single_open(file, ab8500_gpadc_main_charger_c_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_main_charger_c_fops = { @@ -1098,19 +2041,20 @@ static int ab8500_gpadc_usb_charger_c_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - usb_charger_c_raw = ab8500_gpadc_read_raw(gpadc, USB_CHARGER_C); + usb_charger_c_raw = ab8500_gpadc_read_raw(gpadc, USB_CHARGER_C, + avg_sample, trig_edge, trig_timer, conv_type); usb_charger_c_convert = ab8500_gpadc_ad_to_voltage(gpadc, - USB_CHARGER_C, usb_charger_c_raw); + USB_CHARGER_C, usb_charger_c_raw); return seq_printf(s, "%d,0x%X\n", - usb_charger_c_convert, usb_charger_c_raw); + usb_charger_c_convert, usb_charger_c_raw); } static int ab8500_gpadc_usb_charger_c_open(struct inode *inode, struct file *file) { return single_open(file, ab8500_gpadc_usb_charger_c_print, - inode->i_private); + inode->i_private); } static const struct file_operations ab8500_gpadc_usb_charger_c_fops = { @@ -1128,12 +2072,13 @@ static int ab8500_gpadc_bk_bat_v_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - bk_bat_v_raw = ab8500_gpadc_read_raw(gpadc, BK_BAT_V); + bk_bat_v_raw = ab8500_gpadc_read_raw(gpadc, BK_BAT_V, + avg_sample, trig_edge, trig_timer, conv_type); bk_bat_v_convert = ab8500_gpadc_ad_to_voltage(gpadc, - BK_BAT_V, bk_bat_v_raw); + BK_BAT_V, bk_bat_v_raw); return seq_printf(s, "%d,0x%X\n", - bk_bat_v_convert, bk_bat_v_raw); + bk_bat_v_convert, bk_bat_v_raw); } static int ab8500_gpadc_bk_bat_v_open(struct inode *inode, struct file *file) @@ -1156,12 +2101,13 @@ static int ab8500_gpadc_die_temp_print(struct seq_file *s, void *p) struct ab8500_gpadc *gpadc; gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); - die_temp_raw = ab8500_gpadc_read_raw(gpadc, DIE_TEMP); + die_temp_raw = ab8500_gpadc_read_raw(gpadc, DIE_TEMP, + avg_sample, trig_edge, trig_timer, conv_type); die_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, DIE_TEMP, - die_temp_raw); + die_temp_raw); return seq_printf(s, "%d,0x%X\n", - die_temp_convert, die_temp_raw); + die_temp_convert, die_temp_raw); } static int ab8500_gpadc_die_temp_open(struct inode *inode, struct file *file) @@ -1177,6 +2123,453 @@ static const struct file_operations ab8500_gpadc_die_temp_fops = { .owner = THIS_MODULE, }; +static int ab8500_gpadc_usb_id_print(struct seq_file *s, void *p) +{ + int usb_id_raw; + int usb_id_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + usb_id_raw = ab8500_gpadc_read_raw(gpadc, USB_ID, + avg_sample, trig_edge, trig_timer, conv_type); + usb_id_convert = ab8500_gpadc_ad_to_voltage(gpadc, USB_ID, + usb_id_raw); + + return seq_printf(s, "%d,0x%X\n", + usb_id_convert, usb_id_raw); +} + +static int ab8500_gpadc_usb_id_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_usb_id_print, inode->i_private); +} + +static const struct file_operations ab8500_gpadc_usb_id_fops = { + .open = ab8500_gpadc_usb_id_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_xtal_temp_print(struct seq_file *s, void *p) +{ + int xtal_temp_raw; + int xtal_temp_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + xtal_temp_raw = ab8500_gpadc_read_raw(gpadc, XTAL_TEMP, + avg_sample, trig_edge, trig_timer, conv_type); + xtal_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, XTAL_TEMP, + xtal_temp_raw); + + return seq_printf(s, "%d,0x%X\n", + xtal_temp_convert, xtal_temp_raw); +} + +static int ab8540_gpadc_xtal_temp_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8540_gpadc_xtal_temp_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_xtal_temp_fops = { + .open = ab8540_gpadc_xtal_temp_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_true_meas_print(struct seq_file *s, void *p) +{ + int vbat_true_meas_raw; + int vbat_true_meas_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_true_meas_raw = ab8500_gpadc_read_raw(gpadc, VBAT_TRUE_MEAS, + avg_sample, trig_edge, trig_timer, conv_type); + vbat_true_meas_convert = ab8500_gpadc_ad_to_voltage(gpadc, VBAT_TRUE_MEAS, + vbat_true_meas_raw); + + return seq_printf(s, "%d,0x%X\n", + vbat_true_meas_convert, vbat_true_meas_raw); +} + +static int ab8540_gpadc_vbat_true_meas_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_true_meas_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_vbat_true_meas_fops = { + .open = ab8540_gpadc_vbat_true_meas_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_bat_ctrl_and_ibat_print(struct seq_file *s, void *p) +{ + int bat_ctrl_raw; + int bat_ctrl_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + bat_ctrl_raw = ab8500_gpadc_double_read_raw(gpadc, BAT_CTRL_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + + bat_ctrl_convert = ab8500_gpadc_ad_to_voltage(gpadc, BAT_CTRL, + bat_ctrl_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + return seq_printf(s, "%d,0x%X\n" "%d,0x%X\n", + bat_ctrl_convert, bat_ctrl_raw, + ibat_convert, ibat_raw); +} + +static int ab8540_gpadc_bat_ctrl_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_bat_ctrl_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_bat_ctrl_and_ibat_fops = { + .open = ab8540_gpadc_bat_ctrl_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_meas_and_ibat_print(struct seq_file *s, void *p) +{ + int vbat_meas_raw; + int vbat_meas_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_meas_raw = ab8500_gpadc_double_read_raw(gpadc, VBAT_MEAS_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + vbat_meas_convert = ab8500_gpadc_ad_to_voltage(gpadc, MAIN_BAT_V, + vbat_meas_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + return seq_printf(s, "%d,0x%X\n" "%d,0x%X\n", + vbat_meas_convert, vbat_meas_raw, + ibat_convert, ibat_raw); +} + +static int ab8540_gpadc_vbat_meas_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_meas_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_vbat_meas_and_ibat_fops = { + .open = ab8540_gpadc_vbat_meas_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_vbat_true_meas_and_ibat_print(struct seq_file *s, void *p) +{ + int vbat_true_meas_raw; + int vbat_true_meas_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + vbat_true_meas_raw = ab8500_gpadc_double_read_raw(gpadc, + VBAT_TRUE_MEAS_AND_IBAT, avg_sample, trig_edge, + trig_timer, conv_type, &ibat_raw); + vbat_true_meas_convert = ab8500_gpadc_ad_to_voltage(gpadc, + VBAT_TRUE_MEAS, vbat_true_meas_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + return seq_printf(s, "%d,0x%X\n" "%d,0x%X\n", + vbat_true_meas_convert, vbat_true_meas_raw, + ibat_convert, ibat_raw); +} + +static int ab8540_gpadc_vbat_true_meas_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_vbat_true_meas_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_vbat_true_meas_and_ibat_fops = { + .open = ab8540_gpadc_vbat_true_meas_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_bat_temp_and_ibat_print(struct seq_file *s, void *p) +{ + int bat_temp_raw; + int bat_temp_convert; + int ibat_raw; + int ibat_convert; + struct ab8500_gpadc *gpadc; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + bat_temp_raw = ab8500_gpadc_double_read_raw(gpadc, BAT_TEMP_AND_IBAT, + avg_sample, trig_edge, trig_timer, conv_type, &ibat_raw); + bat_temp_convert = ab8500_gpadc_ad_to_voltage(gpadc, BTEMP_BALL, + bat_temp_raw); + ibat_convert = ab8500_gpadc_ad_to_voltage(gpadc, IBAT_VIRTUAL_CHANNEL, + ibat_raw); + + return seq_printf(s, "%d,0x%X\n" "%d,0x%X\n", + bat_temp_convert, bat_temp_raw, + ibat_convert, ibat_raw); +} + +static int ab8540_gpadc_bat_temp_and_ibat_open(struct inode *inode, + struct file *file) +{ + return single_open(file, ab8540_gpadc_bat_temp_and_ibat_print, + inode->i_private); +} + +static const struct file_operations ab8540_gpadc_bat_temp_and_ibat_fops = { + .open = ab8540_gpadc_bat_temp_and_ibat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8540_gpadc_otp_cal_print(struct seq_file *s, void *p) +{ + struct ab8500_gpadc *gpadc; + u16 vmain_l, vmain_h, btemp_l, btemp_h; + u16 vbat_l, vbat_h, ibat_l, ibat_h; + + gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); + ab8540_gpadc_get_otp(gpadc, &vmain_l, &vmain_h, &btemp_l, &btemp_h, + &vbat_l, &vbat_h, &ibat_l, &ibat_h); + return seq_printf(s, "VMAIN_L:0x%X\n" + "VMAIN_H:0x%X\n" + "BTEMP_L:0x%X\n" + "BTEMP_H:0x%X\n" + "VBAT_L:0x%X\n" + "VBAT_H:0x%X\n" + "IBAT_L:0x%X\n" + "IBAT_H:0x%X\n", + vmain_l, vmain_h, btemp_l, btemp_h, vbat_l, vbat_h, ibat_l, ibat_h); +} + +static int ab8540_gpadc_otp_cal_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8540_gpadc_otp_cal_print, inode->i_private); +} + +static const struct file_operations ab8540_gpadc_otp_calib_fops = { + .open = ab8540_gpadc_otp_cal_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_avg_sample_print(struct seq_file *s, void *p) +{ + return seq_printf(s, "%d\n", avg_sample); +} + +static int ab8500_gpadc_avg_sample_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_avg_sample_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_avg_sample_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_avg_sample; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_avg_sample); + if (err) + return err; + + if ((user_avg_sample == SAMPLE_1) || (user_avg_sample == SAMPLE_4) + || (user_avg_sample == SAMPLE_8) + || (user_avg_sample == SAMPLE_16)) { + avg_sample = (u8) user_avg_sample; + } else { + dev_err(dev, "debugfs error input: " + "should be egal to 1, 4, 8 or 16\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_avg_sample_fops = { + .open = ab8500_gpadc_avg_sample_open, + .read = seq_read, + .write = ab8500_gpadc_avg_sample_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_trig_edge_print(struct seq_file *s, void *p) +{ + return seq_printf(s, "%d\n", trig_edge); +} + +static int ab8500_gpadc_trig_edge_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_trig_edge_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_trig_edge_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_trig_edge; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_trig_edge); + if (err) + return err; + + if ((user_trig_edge == RISING_EDGE) + || (user_trig_edge == FALLING_EDGE)) { + trig_edge = (u8) user_trig_edge; + } else { + dev_err(dev, "Wrong input:\n" + "Enter 0. Rising edge\n" + "Enter 1. Falling edge\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_trig_edge_fops = { + .open = ab8500_gpadc_trig_edge_open, + .read = seq_read, + .write = ab8500_gpadc_trig_edge_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_trig_timer_print(struct seq_file *s, void *p) +{ + return seq_printf(s, "%d\n", trig_timer); +} + +static int ab8500_gpadc_trig_timer_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_trig_timer_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_trig_timer_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_trig_timer; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_trig_timer); + if (err) + return err; + + if ((user_trig_timer >= 0) && (user_trig_timer <= 255)) { + trig_timer = (u8) user_trig_timer; + } else { + dev_err(dev, "debugfs error input: " + "should be beetween 0 to 255\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_trig_timer_fops = { + .open = ab8500_gpadc_trig_timer_open, + .read = seq_read, + .write = ab8500_gpadc_trig_timer_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + +static int ab8500_gpadc_conv_type_print(struct seq_file *s, void *p) +{ + return seq_printf(s, "%d\n", conv_type); +} + +static int ab8500_gpadc_conv_type_open(struct inode *inode, struct file *file) +{ + return single_open(file, ab8500_gpadc_conv_type_print, + inode->i_private); +} + +static ssize_t ab8500_gpadc_conv_type_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + struct device *dev = ((struct seq_file *)(file->private_data))->private; + unsigned long user_conv_type; + int err; + + err = kstrtoul_from_user(user_buf, count, 0, &user_conv_type); + if (err) + return err; + + if ((user_conv_type == ADC_SW) + || (user_conv_type == ADC_HW)) { + conv_type = (u8) user_conv_type; + } else { + dev_err(dev, "Wrong input:\n" + "Enter 0. ADC SW conversion\n" + "Enter 1. ADC HW conversion\n"); + return -EINVAL; + } + + return count; +} + +static const struct file_operations ab8500_gpadc_conv_type_fops = { + .open = ab8500_gpadc_conv_type_open, + .read = seq_read, + .write = ab8500_gpadc_conv_type_write, + .llseek = seq_lseek, + .release = single_release, + .owner = THIS_MODULE, +}; + /* * return length of an ASCII numerical value, 0 is string is not a * numerical value. @@ -1352,7 +2745,7 @@ static int ab8500_subscribe_unsubscribe_open(struct inode *inode, struct file *file) { return single_open(file, ab8500_subscribe_unsubscribe_print, - inode->i_private); + inode->i_private); } /* @@ -1382,21 +2775,14 @@ static ssize_t ab8500_subscribe_write(struct file *file, size_t count, loff_t *ppos) { struct device *dev = ((struct seq_file *)(file->private_data))->private; - char buf[32]; - int buf_size; unsigned long user_val; int err; unsigned int irq_index; - /* Get userspace string and assure termination */ - buf_size = min(count, (sizeof(buf)-1)); - if (copy_from_user(buf, user_buf, buf_size)) - return -EFAULT; - buf[buf_size] = 0; - - err = strict_strtoul(buf, 0, &user_val); + err = kstrtoul_from_user(user_buf, count, 0, &user_val); if (err) - return -EINVAL; + return err; + if (user_val < irq_first) { dev_err(dev, "debugfs error input < %d\n", irq_first); return -EINVAL; @@ -1416,7 +2802,7 @@ static ssize_t ab8500_subscribe_write(struct file *file, */ dev_attr[irq_index] = kmalloc(sizeof(struct device_attribute), GFP_KERNEL); - event_name[irq_index] = kmalloc(buf_size, GFP_KERNEL); + event_name[irq_index] = kmalloc(count, GFP_KERNEL); sprintf(event_name[irq_index], "%lu", user_val); dev_attr[irq_index]->show = show_irq; dev_attr[irq_index]->store = NULL; @@ -1438,7 +2824,7 @@ static ssize_t ab8500_subscribe_write(struct file *file, return err; } - return buf_size; + return count; } static ssize_t ab8500_unsubscribe_write(struct file *file, @@ -1446,21 +2832,14 @@ static ssize_t ab8500_unsubscribe_write(struct file *file, size_t count, loff_t *ppos) { struct device *dev = ((struct seq_file *)(file->private_data))->private; - char buf[32]; - int buf_size; unsigned long user_val; int err; unsigned int irq_index; - /* Get userspace string and assure termination */ - buf_size = min(count, (sizeof(buf)-1)); - if (copy_from_user(buf, user_buf, buf_size)) - return -EFAULT; - buf[buf_size] = 0; - - err = strict_strtoul(buf, 0, &user_val); + err = kstrtoul_from_user(user_buf, count, 0, &user_val); if (err) - return -EINVAL; + return err; + if (user_val < irq_first) { dev_err(dev, "debugfs error input < %d\n", irq_first); return -EINVAL; @@ -1485,7 +2864,7 @@ static ssize_t ab8500_unsubscribe_write(struct file *file, kfree(event_name[irq_index]); kfree(dev_attr[irq_index]); - return buf_size; + return count; } /* @@ -1583,7 +2962,7 @@ static int ab8500_debug_probe(struct platform_device *plf) irq_first = platform_get_irq_byname(plf, "IRQ_FIRST"); if (irq_first < 0) { dev_err(&plf->dev, "First irq not found, err %d\n", - irq_first); + irq_first); ret = irq_first; goto out_freeevent_name; } @@ -1591,9 +2970,9 @@ static int ab8500_debug_probe(struct platform_device *plf) irq_last = platform_get_irq_byname(plf, "IRQ_LAST"); if (irq_last < 0) { dev_err(&plf->dev, "Last irq not found, err %d\n", - irq_last); + irq_last); ret = irq_last; - goto out_freeevent_name; + goto out_freeevent_name; } ab8500_dir = debugfs_create_dir(AB8500_NAME_STRING, NULL); @@ -1601,124 +2980,198 @@ static int ab8500_debug_probe(struct platform_device *plf) goto err; ab8500_gpadc_dir = debugfs_create_dir(AB8500_ADC_NAME_STRING, - ab8500_dir); + ab8500_dir); if (!ab8500_gpadc_dir) goto err; file = debugfs_create_file("all-bank-registers", S_IRUGO, - ab8500_dir, &plf->dev, &ab8500_registers_fops); + ab8500_dir, &plf->dev, &ab8500_registers_fops); if (!file) goto err; file = debugfs_create_file("all-banks", S_IRUGO, - ab8500_dir, &plf->dev, &ab8500_all_banks_fops); + ab8500_dir, &plf->dev, &ab8500_all_banks_fops); if (!file) goto err; - file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_bank_fops); + file = debugfs_create_file("register-bank", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_bank_fops); if (!file) goto err; - file = debugfs_create_file("register-address", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_address_fops); + file = debugfs_create_file("register-address", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_address_fops); if (!file) goto err; - file = debugfs_create_file("register-value", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_val_fops); + file = debugfs_create_file("register-value", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_val_fops); if (!file) goto err; - file = debugfs_create_file("irq-subscribe", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_subscribe_fops); + file = debugfs_create_file("irq-subscribe", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_subscribe_fops); if (!file) goto err; - if (is_ab8500(ab8500)) + if (is_ab8500(ab8500)) { + debug_ranges = ab8500_debug_ranges; num_interrupt_lines = AB8500_NR_IRQS; - else if (is_ab8505(ab8500)) + } else if (is_ab8505(ab8500)) { + debug_ranges = ab8505_debug_ranges; num_interrupt_lines = AB8505_NR_IRQS; - else if (is_ab9540(ab8500)) + } else if (is_ab9540(ab8500)) { + debug_ranges = ab8505_debug_ranges; num_interrupt_lines = AB9540_NR_IRQS; + } else if (is_ab8540(ab8500)) { + debug_ranges = ab8540_debug_ranges; + num_interrupt_lines = AB8540_NR_IRQS; + } file = debugfs_create_file("interrupts", (S_IRUGO), - ab8500_dir, &plf->dev, &ab8500_interrupts_fops); + ab8500_dir, &plf->dev, &ab8500_interrupts_fops); if (!file) goto err; - file = debugfs_create_file("irq-unsubscribe", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_unsubscribe_fops); + file = debugfs_create_file("irq-unsubscribe", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_unsubscribe_fops); if (!file) goto err; - file = debugfs_create_file("hwreg", (S_IRUGO | S_IWUSR), - ab8500_dir, &plf->dev, &ab8500_hwreg_fops); + file = debugfs_create_file("hwreg", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_hwreg_fops); if (!file) goto err; - file = debugfs_create_file("bat_ctrl", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bat_ctrl_fops); + file = debugfs_create_file("all-modem-registers", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_dir, &plf->dev, &ab8500_modem_fops); if (!file) goto err; - file = debugfs_create_file("btemp_ball", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_btemp_ball_fops); + file = debugfs_create_file("bat_ctrl", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bat_ctrl_fops); if (!file) goto err; - file = debugfs_create_file("main_charger_v", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_v_fops); + file = debugfs_create_file("btemp_ball", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_btemp_ball_fops); if (!file) goto err; - file = debugfs_create_file("acc_detect1", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect1_fops); + file = debugfs_create_file("main_charger_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_v_fops); if (!file) goto err; - file = debugfs_create_file("acc_detect2", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect2_fops); + file = debugfs_create_file("acc_detect1", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect1_fops); if (!file) goto err; - file = debugfs_create_file("adc_aux1", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux1_fops); + file = debugfs_create_file("acc_detect2", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_acc_detect2_fops); if (!file) goto err; - file = debugfs_create_file("adc_aux2", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux2_fops); + file = debugfs_create_file("adc_aux1", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux1_fops); if (!file) goto err; - file = debugfs_create_file("main_bat_v", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_bat_v_fops); + file = debugfs_create_file("adc_aux2", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_aux2_fops); if (!file) goto err; - file = debugfs_create_file("vbus_v", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_vbus_v_fops); + file = debugfs_create_file("main_bat_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_bat_v_fops); if (!file) goto err; - file = debugfs_create_file("main_charger_c", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_c_fops); + file = debugfs_create_file("vbus_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_vbus_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("main_charger_c", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_main_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("usb_charger_c", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_charger_c_fops); + if (!file) + goto err; + + file = debugfs_create_file("bk_bat_v", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bk_bat_v_fops); + if (!file) + goto err; + + file = debugfs_create_file("die_temp", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_die_temp_fops); + if (!file) + goto err; + + file = debugfs_create_file("usb_id", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_id_fops); + if (!file) + goto err; + + if (is_ab8540(ab8500)) { + file = debugfs_create_file("xtal_temp", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8540_gpadc_xtal_temp_fops); + if (!file) + goto err; + file = debugfs_create_file("vbattruemeas", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, + &ab8540_gpadc_vbat_true_meas_fops); + if (!file) + goto err; + file = debugfs_create_file("batctrl_and_ibat", + (S_IRUGO | S_IWUGO), ab8500_gpadc_dir, + &plf->dev, &ab8540_gpadc_bat_ctrl_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("vbatmeas_and_ibat", + (S_IRUGO | S_IWUGO), ab8500_gpadc_dir, + &plf->dev, + &ab8540_gpadc_vbat_meas_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("vbattruemeas_and_ibat", + (S_IRUGO | S_IWUGO), ab8500_gpadc_dir, + &plf->dev, + &ab8540_gpadc_vbat_true_meas_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("battemp_and_ibat", + (S_IRUGO | S_IWUGO), ab8500_gpadc_dir, + &plf->dev, &ab8540_gpadc_bat_temp_and_ibat_fops); + if (!file) + goto err; + file = debugfs_create_file("otp_calib", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8540_gpadc_otp_calib_fops); + if (!file) + goto err; + } + file = debugfs_create_file("avg_sample", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_avg_sample_fops); if (!file) goto err; - file = debugfs_create_file("usb_charger_c", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_usb_charger_c_fops); + file = debugfs_create_file("trig_edge", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_trig_edge_fops); if (!file) goto err; - file = debugfs_create_file("bk_bat_v", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_bk_bat_v_fops); + file = debugfs_create_file("trig_timer", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_trig_timer_fops); if (!file) goto err; - file = debugfs_create_file("die_temp", (S_IRUGO | S_IWUSR), - ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_die_temp_fops); + file = debugfs_create_file("conv_type", (S_IRUGO | S_IWUSR | S_IWGRP), + ab8500_gpadc_dir, &plf->dev, &ab8500_gpadc_conv_type_fops); if (!file) goto err; diff --git a/drivers/mfd/ab8500-gpadc.c b/drivers/mfd/ab8500-gpadc.c index 5f341a50ee5..65f72284185 100644 --- a/drivers/mfd/ab8500-gpadc.c +++ b/drivers/mfd/ab8500-gpadc.c @@ -37,6 +37,13 @@ #define AB8500_GPADC_AUTODATAL_REG 0x07 #define AB8500_GPADC_AUTODATAH_REG 0x08 #define AB8500_GPADC_MUX_CTRL_REG 0x09 +#define AB8540_GPADC_MANDATA2L_REG 0x09 +#define AB8540_GPADC_MANDATA2H_REG 0x0A +#define AB8540_GPADC_APEAAX_REG 0x10 +#define AB8540_GPADC_APEAAT_REG 0x11 +#define AB8540_GPADC_APEAAM_REG 0x12 +#define AB8540_GPADC_APEAAH_REG 0x13 +#define AB8540_GPADC_APEAAL_REG 0x14 /* * OTP register offsets @@ -49,19 +56,29 @@ #define AB8500_GPADC_CAL_5 0x13 #define AB8500_GPADC_CAL_6 0x14 #define AB8500_GPADC_CAL_7 0x15 +/* New calibration for 8540 */ +#define AB8540_GPADC_OTP4_REG_7 0x38 +#define AB8540_GPADC_OTP4_REG_6 0x39 +#define AB8540_GPADC_OTP4_REG_5 0x3A /* gpadc constants */ #define EN_VINTCORE12 0x04 #define EN_VTVOUT 0x02 #define EN_GPADC 0x01 #define DIS_GPADC 0x00 -#define SW_AVG_16 0x60 +#define AVG_1 0x00 +#define AVG_4 0x20 +#define AVG_8 0x40 +#define AVG_16 0x60 #define ADC_SW_CONV 0x04 #define EN_ICHAR 0x80 #define BTEMP_PULL_UP 0x08 #define EN_BUF 0x40 #define DIS_ZERO 0x00 #define GPADC_BUSY 0x01 +#define EN_FALLING 0x10 +#define EN_TRIG_EDGE 0x02 +#define EN_VBIAS_XTAL_TEMP 0x02 /* GPADC constants from AB8500 spec, UM0836 */ #define ADC_RESOLUTION 1024 @@ -80,8 +97,21 @@ #define ADC_CH_BKBAT_MIN 0 #define ADC_CH_BKBAT_MAX 3200 +/* GPADC constants from AB8540 spec */ +#define ADC_CH_IBAT_MIN (-6000) /* mA range measured by ADC for ibat*/ +#define ADC_CH_IBAT_MAX 6000 +#define ADC_CH_IBAT_MIN_V (-60) /* mV range measured by ADC for ibat*/ +#define ADC_CH_IBAT_MAX_V 60 +#define IBAT_VDROP_L (-56) /* mV */ +#define IBAT_VDROP_H 56 + /* This is used to not lose precision when dividing to get gain and offset */ -#define CALIB_SCALE 1000 +#define CALIB_SCALE 1000 +/* + * Number of bits shift used to not lose precision + * when dividing to get ibat gain. + */ +#define CALIB_SHIFT_IBAT 20 /* Time in ms before disabling regulator */ #define GPADC_AUDOSUSPEND_DELAY 1 @@ -92,6 +122,7 @@ enum cal_channels { ADC_INPUT_VMAIN = 0, ADC_INPUT_BTEMP, ADC_INPUT_VBAT, + ADC_INPUT_IBAT, NBR_CAL_INPUTS, }; @@ -102,8 +133,10 @@ enum cal_channels { * @offset: Offset of the ADC channel */ struct adc_cal_data { - u64 gain; - u64 offset; + s64 gain; + s64 offset; + u16 otp_calib_hi; + u16 otp_calib_lo; }; /** @@ -116,7 +149,10 @@ struct adc_cal_data { * the completion of gpadc conversion * @ab8500_gpadc_lock: structure of type mutex * @regu: pointer to the struct regulator - * @irq: interrupt number that is used by gpadc + * @irq_sw: interrupt number that is used by gpadc for Sw + * conversion + * @irq_hw: interrupt number that is used by gpadc for Hw + * conversion * @cal_data array of ADC calibration data structs */ struct ab8500_gpadc { @@ -126,7 +162,8 @@ struct ab8500_gpadc { struct completion ab8500_gpadc_complete; struct mutex ab8500_gpadc_lock; struct regulator *regu; - int irq; + int irq_sw; + int irq_hw; struct adc_cal_data cal_data[NBR_CAL_INPUTS]; }; @@ -171,6 +208,7 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, gpadc->cal_data[ADC_INPUT_VMAIN].offset) / CALIB_SCALE; break; + case XTAL_TEMP: case BAT_CTRL: case BTEMP_BALL: case ACC_DETECT1: @@ -189,6 +227,7 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, break; case MAIN_BAT_V: + case VBAT_TRUE_MEAS: /* For some reason we don't have calibrated data */ if (!gpadc->cal_data[ADC_INPUT_VBAT].gain) { res = ADC_CH_VBAT_MIN + (ADC_CH_VBAT_MAX - @@ -232,6 +271,20 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, ADC_RESOLUTION; break; + case IBAT_VIRTUAL_CHANNEL: + /* For some reason we don't have calibrated data */ + if (!gpadc->cal_data[ADC_INPUT_IBAT].gain) { + res = ADC_CH_IBAT_MIN + (ADC_CH_IBAT_MAX - + ADC_CH_IBAT_MIN) * ad_value / + ADC_RESOLUTION; + break; + } + /* Here we can use the calibrated data */ + res = (int) (ad_value * gpadc->cal_data[ADC_INPUT_IBAT].gain + + gpadc->cal_data[ADC_INPUT_IBAT].offset) + >> CALIB_SHIFT_IBAT; + break; + default: dev_err(gpadc->dev, "unknown channel, not possible to convert\n"); @@ -244,25 +297,35 @@ int ab8500_gpadc_ad_to_voltage(struct ab8500_gpadc *gpadc, u8 channel, EXPORT_SYMBOL(ab8500_gpadc_ad_to_voltage); /** - * ab8500_gpadc_convert() - gpadc conversion + * ab8500_gpadc_sw_hw_convert() - gpadc conversion * @channel: analog channel to be converted to digital data + * @avg_sample: number of ADC sample to average + * @trig_egde: selected ADC trig edge + * @trig_timer: selected ADC trigger delay timer + * @conv_type: selected conversion type (HW or SW conversion) * * This function converts the selected analog i/p to digital * data. */ -int ab8500_gpadc_convert(struct ab8500_gpadc *gpadc, u8 channel) +int ab8500_gpadc_sw_hw_convert(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type) { int ad_value; int voltage; - ad_value = ab8500_gpadc_read_raw(gpadc, channel); - if (ad_value < 0) { - dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", channel); + ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample, + trig_edge, trig_timer, conv_type); +/* On failure retry a second time */ + if (ad_value < 0) + ad_value = ab8500_gpadc_read_raw(gpadc, channel, avg_sample, + trig_edge, trig_timer, conv_type); +if (ad_value < 0) { + dev_err(gpadc->dev, "GPADC raw value failed ch: %d\n", + channel); return ad_value; } voltage = ab8500_gpadc_ad_to_voltage(gpadc, channel, ad_value); - if (voltage < 0) dev_err(gpadc->dev, "GPADC to voltage conversion failed ch:" " %d AD: 0x%x\n", channel, ad_value); @@ -274,21 +337,46 @@ EXPORT_SYMBOL(ab8500_gpadc_convert); /** * ab8500_gpadc_read_raw() - gpadc read * @channel: analog channel to be read + * @avg_sample: number of ADC sample to average + * @trig_edge: selected trig edge + * @trig_timer: selected ADC trigger delay timer + * @conv_type: selected conversion type (HW or SW conversion) * - * This function obtains the raw ADC value, this then needs - * to be converted by calling ab8500_gpadc_ad_to_voltage() + * This function obtains the raw ADC value for an hardware conversion, + * this then needs to be converted by calling ab8500_gpadc_ad_to_voltage() */ -int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) +int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type) +{ + int raw_data; + raw_data = ab8500_gpadc_double_read_raw(gpadc, channel, + avg_sample, trig_edge, trig_timer, conv_type, NULL); + return raw_data; +} + +int ab8500_gpadc_double_read_raw(struct ab8500_gpadc *gpadc, u8 channel, + u8 avg_sample, u8 trig_edge, u8 trig_timer, u8 conv_type, + int *ibat) { int ret; int looplimit = 0; - u8 val, low_data, high_data; + unsigned long completion_timeout; + u8 val, low_data, high_data, low_data2, high_data2; + u8 val_reg1 = 0; + unsigned int delay_min = 0; + unsigned int delay_max = 0; + u8 data_low_addr, data_high_addr; if (!gpadc) return -ENODEV; - mutex_lock(&gpadc->ab8500_gpadc_lock); + /* check if convertion is supported */ + if ((gpadc->irq_sw < 0) && (conv_type == ADC_SW)) + return -ENOTSUPP; + if ((gpadc->irq_hw < 0) && (conv_type == ADC_HW)) + return -ENOTSUPP; + mutex_lock(&gpadc->ab8500_gpadc_lock); /* Enable VTVout LDO this is required for GPADC */ pm_runtime_get_sync(gpadc->dev); @@ -309,16 +397,34 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) } /* Enable GPADC */ - ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_GPADC, EN_GPADC); - if (ret < 0) { - dev_err(gpadc->dev, "gpadc_conversion: enable gpadc failed\n"); - goto out; + val_reg1 |= EN_GPADC; + + /* Select the channel source and set average samples */ + switch (avg_sample) { + case SAMPLE_1: + val = channel | AVG_1; + break; + case SAMPLE_4: + val = channel | AVG_4; + break; + case SAMPLE_8: + val = channel | AVG_8; + break; + default: + val = channel | AVG_16; + break; } - /* Select the channel source and set average samples to 16 */ - ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, - AB8500_GPADC_CTRL2_REG, (channel | SW_AVG_16)); + if (conv_type == ADC_HW) { + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL3_REG, val); + val_reg1 |= EN_TRIG_EDGE; + if (trig_edge) + val_reg1 |= EN_FALLING; + } + else + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL2_REG, val); if (ret < 0) { dev_err(gpadc->dev, "gpadc_conversion: set avg samples failed\n"); @@ -333,71 +439,129 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) switch (channel) { case MAIN_CHARGER_C: case USB_CHARGER_C: - ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, - EN_BUF | EN_ICHAR, - EN_BUF | EN_ICHAR); + val_reg1 |= EN_BUF | EN_ICHAR; break; case BTEMP_BALL: if (!is_ab8500_2p0_or_earlier(gpadc->parent)) { - /* Turn on btemp pull-up on ABB 3.0 */ - ret = abx500_mask_and_set_register_interruptible( - gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, - EN_BUF | BTEMP_PULL_UP, - EN_BUF | BTEMP_PULL_UP); - - /* - * Delay might be needed for ABB8500 cut 3.0, if not, remove - * when hardware will be available - */ - usleep_range(1000, 1000); + val_reg1 |= EN_BUF | BTEMP_PULL_UP; + /* + * Delay might be needed for ABB8500 cut 3.0, if not, + * remove when hardware will be availible + */ + delay_min = 1000; /* Delay in micro seconds */ + delay_max = 10000; /* large range to optimise sleep mode */ break; } /* Intentional fallthrough */ default: - ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, EN_BUF, EN_BUF); + val_reg1 |= EN_BUF; break; } + + /* Write configuration to register */ + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, val_reg1); if (ret < 0) { dev_err(gpadc->dev, - "gpadc_conversion: select falling edge failed\n"); + "gpadc_conversion: set Control register failed\n"); goto out; } - ret = abx500_mask_and_set_register_interruptible(gpadc->dev, - AB8500_GPADC, AB8500_GPADC_CTRL1_REG, ADC_SW_CONV, ADC_SW_CONV); - if (ret < 0) { - dev_err(gpadc->dev, - "gpadc_conversion: start s/w conversion failed\n"); - goto out; + if (delay_min != 0) + usleep_range(delay_min, delay_max); + + if (conv_type == ADC_HW) { + /* Set trigger delay timer */ + ret = abx500_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_AUTO_TIMER_REG, trig_timer); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: trig timer failed\n"); + goto out; + } + completion_timeout = 2 * HZ; + data_low_addr = AB8500_GPADC_AUTODATAL_REG; + data_high_addr = AB8500_GPADC_AUTODATAH_REG; + } else { + /* Start SW conversion */ + ret = abx500_mask_and_set_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8500_GPADC_CTRL1_REG, + ADC_SW_CONV, ADC_SW_CONV); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: start s/w conv failed\n"); + goto out; + } + completion_timeout = msecs_to_jiffies(CONVERSION_TIME); + data_low_addr = AB8500_GPADC_MANDATAL_REG; + data_high_addr = AB8500_GPADC_MANDATAH_REG; } + /* wait for completion of conversion */ if (!wait_for_completion_timeout(&gpadc->ab8500_gpadc_complete, - msecs_to_jiffies(CONVERSION_TIME))) { + completion_timeout)) { dev_err(gpadc->dev, - "timeout: didn't receive GPADC conversion interrupt\n"); + "timeout didn't receive GPADC conv interrupt\n"); ret = -EINVAL; goto out; } /* Read the converted RAW data */ - ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC, - AB8500_GPADC_MANDATAL_REG, &low_data); + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, data_low_addr, &low_data); if (ret < 0) { dev_err(gpadc->dev, "gpadc_conversion: read low data failed\n"); goto out; } - ret = abx500_get_register_interruptible(gpadc->dev, AB8500_GPADC, - AB8500_GPADC_MANDATAH_REG, &high_data); + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, data_high_addr, &high_data); if (ret < 0) { - dev_err(gpadc->dev, - "gpadc_conversion: read high data failed\n"); + dev_err(gpadc->dev, "gpadc_conversion: read high data failed\n"); goto out; } + /* Check if double convertion is required */ + if ((channel == BAT_CTRL_AND_IBAT) || + (channel == VBAT_MEAS_AND_IBAT) || + (channel == VBAT_TRUE_MEAS_AND_IBAT) || + (channel == BAT_TEMP_AND_IBAT)) { + + if (conv_type == ADC_HW) { + /* not supported */ + ret = -ENOTSUPP; + dev_err(gpadc->dev, + "gpadc_conversion: only SW double conversion supported\n"); + goto out; + } else { + /* Read the converted RAW data 2 */ + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8540_GPADC_MANDATA2L_REG, + &low_data2); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read sw low data 2 failed\n"); + goto out; + } + + ret = abx500_get_register_interruptible(gpadc->dev, + AB8500_GPADC, AB8540_GPADC_MANDATA2H_REG, + &high_data2); + if (ret < 0) { + dev_err(gpadc->dev, + "gpadc_conversion: read sw high data 2 failed\n"); + goto out; + } + if (ibat != NULL) { + *ibat = (high_data2 << 8) | low_data2; + } else { + dev_warn(gpadc->dev, + "gpadc_conversion: ibat not stored\n"); + } + + } + } + /* Disable GPADC */ ret = abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, DIS_GPADC); @@ -406,6 +570,7 @@ int ab8500_gpadc_read_raw(struct ab8500_gpadc *gpadc, u8 channel) goto out; } + /* Disable VTVout LDO this is required for GPADC */ pm_runtime_mark_last_busy(gpadc->dev); pm_runtime_put_autosuspend(gpadc->dev); @@ -422,9 +587,7 @@ out: */ (void) abx500_set_register_interruptible(gpadc->dev, AB8500_GPADC, AB8500_GPADC_CTRL1_REG, DIS_GPADC); - pm_runtime_put(gpadc->dev); - mutex_unlock(&gpadc->ab8500_gpadc_lock); dev_err(gpadc->dev, "gpadc_conversion: Failed to AD convert channel %d\n", channel); @@ -433,16 +596,16 @@ out: EXPORT_SYMBOL(ab8500_gpadc_read_raw); /** - * ab8500_bm_gpswadcconvend_handler() - isr for s/w gpadc conversion completion + * ab8500_bm_gpadcconvend_handler() - isr for gpadc conversion completion * @irq: irq number * @data: pointer to the data passed during request irq * - * This is a interrupt service routine for s/w gpadc conversion completion. + * This is a interrupt service routine for gpadc conversion completion. * Notifies the gpadc completion is completed and the converted raw value * can be read from the registers. * Returns IRQ status(IRQ_HANDLED) */ -static irqreturn_t ab8500_bm_gpswadcconvend_handler(int irq, void *_gpadc) +static irqreturn_t ab8500_bm_gpadcconvend_handler(int irq, void *_gpadc) { struct ab8500_gpadc *gpadc = _gpadc; @@ -461,15 +624,27 @@ static int otp_cal_regs[] = { AB8500_GPADC_CAL_7, }; +static int otp4_cal_regs[] = { + AB8540_GPADC_OTP4_REG_7, + AB8540_GPADC_OTP4_REG_6, + AB8540_GPADC_OTP4_REG_5, +}; + static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) { int i; int ret[ARRAY_SIZE(otp_cal_regs)]; u8 gpadc_cal[ARRAY_SIZE(otp_cal_regs)]; - + int ret_otp4[ARRAY_SIZE(otp4_cal_regs)]; + u8 gpadc_otp4[ARRAY_SIZE(otp4_cal_regs)]; int vmain_high, vmain_low; int btemp_high, btemp_low; int vbat_high, vbat_low; + int ibat_high, ibat_low; + s64 V_gain, V_offset, V2A_gain, V2A_offset; + struct ab8500 *ab8500; + + ab8500 = gpadc->parent; /* First we read all OTP registers and store the error code */ for (i = 0; i < ARRAY_SIZE(otp_cal_regs); i++) { @@ -489,7 +664,7 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) * bt_h/l = btemp_high/low * vb_h/l = vbat_high/low * - * Data bits: + * Data bits 8500/9540: * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 * |.......|.......|.......|.......|.......|.......|.......|....... * | | vm_h9 | vm_h8 @@ -507,6 +682,35 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | * |.......|.......|.......|.......|.......|.......|.......|....... * + * Data bits 8540: + * OTP2 + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h9 | vm_h8 | vm_h7 | vm_h6 | vm_h5 | vm_h4 | vm_h3 | vm_h2 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vm_h1 | vm_h0 | vm_l4 | vm_l3 | vm_l2 | vm_l1 | vm_l0 | bt_h9 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h8 | bt_h7 | bt_h6 | bt_h5 | bt_h4 | bt_h3 | bt_h2 | bt_h1 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | bt_h0 | bt_l4 | bt_l3 | bt_l2 | bt_l1 | bt_l0 | vb_h9 | vb_h8 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_h7 | vb_h6 | vb_h5 | vb_h4 | vb_h3 | vb_h2 | vb_h1 | vb_h0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | vb_l5 | vb_l4 | vb_l3 | vb_l2 | vb_l1 | vb_l0 | + * |.......|.......|.......|.......|.......|.......|.......|....... + * + * Data bits 8540: + * OTP4 + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | | ib_h9 | ib_h8 | ib_h7 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | ib_h6 | ib_h5 | ib_h4 | ib_h3 | ib_h2 | ib_h1 | ib_h0 | ib_l5 + * |.......|.......|.......|.......|.......|.......|.......|....... + * | ib_l4 | ib_l3 | ib_l2 | ib_l1 | ib_l0 | + * * * Ideal output ADC codes corresponding to injected input voltages * during manufacturing is: @@ -519,38 +723,116 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) * vbat_low: Vin = 2380mV / ADC ideal code = 33 */ - /* Calculate gain and offset for VMAIN if all reads succeeded */ - if (!(ret[0] < 0 || ret[1] < 0 || ret[2] < 0)) { - vmain_high = (((gpadc_cal[0] & 0x03) << 8) | - ((gpadc_cal[1] & 0x3F) << 2) | - ((gpadc_cal[2] & 0xC0) >> 6)); + if (is_ab8540(ab8500)) { + /* Calculate gain and offset for VMAIN if all reads succeeded*/ + if (!(ret[1] < 0 || ret[2] < 0)) { + vmain_high = (((gpadc_cal[1] & 0xFF) << 2) | + ((gpadc_cal[2] & 0xC0) >> 6)); + vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi = + (u16)vmain_high; + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo = + (u16)vmain_low; + + gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * + (19500 - 315) / (vmain_high - vmain_low); + gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * + 19500 - (CALIB_SCALE * (19500 - 315) / + (vmain_high - vmain_low)) * vmain_high; + } else { + gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; + } - vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + /* Read IBAT calibration Data */ + for (i = 0; i < ARRAY_SIZE(otp4_cal_regs); i++) { + ret_otp4[i] = abx500_get_register_interruptible( + gpadc->dev, AB8500_OTP_EMUL, + otp4_cal_regs[i], &gpadc_otp4[i]); + if (ret_otp4[i] < 0) + dev_err(gpadc->dev, + "%s: read otp4 reg 0x%02x failed\n", + __func__, otp4_cal_regs[i]); + } - gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * - (19500 - 315) / (vmain_high - vmain_low); + /* Calculate gain and offset for IBAT if all reads succeeded */ + if (!(ret_otp4[0] < 0 || ret_otp4[1] < 0 || ret_otp4[2] < 0)) { + ibat_high = (((gpadc_otp4[0] & 0x07) << 7) | + ((gpadc_otp4[1] & 0xFE) >> 1)); + ibat_low = (((gpadc_otp4[1] & 0x01) << 5) | + ((gpadc_otp4[2] & 0xF8) >> 3)); + + gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_hi = + (u16)ibat_high; + gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_lo = + (u16)ibat_low; + + V_gain = ((IBAT_VDROP_H - IBAT_VDROP_L) + << CALIB_SHIFT_IBAT) / (ibat_high - ibat_low); + + V_offset = (IBAT_VDROP_H << CALIB_SHIFT_IBAT) - + (((IBAT_VDROP_H - IBAT_VDROP_L) << + CALIB_SHIFT_IBAT) / (ibat_high - ibat_low)) + * ibat_high; + /* + * Result obtained is in mV (at a scale factor), + * we need to calculate gain and offset to get mA + */ + V2A_gain = (ADC_CH_IBAT_MAX - ADC_CH_IBAT_MIN)/ + (ADC_CH_IBAT_MAX_V - ADC_CH_IBAT_MIN_V); + V2A_offset = ((ADC_CH_IBAT_MAX_V * ADC_CH_IBAT_MIN - + ADC_CH_IBAT_MAX * ADC_CH_IBAT_MIN_V) + << CALIB_SHIFT_IBAT) + / (ADC_CH_IBAT_MAX_V - ADC_CH_IBAT_MIN_V); + + gpadc->cal_data[ADC_INPUT_IBAT].gain = V_gain * V2A_gain; + gpadc->cal_data[ADC_INPUT_IBAT].offset = V_offset * + V2A_gain + V2A_offset; + } else { + gpadc->cal_data[ADC_INPUT_IBAT].gain = 0; + } - gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * 19500 - - (CALIB_SCALE * (19500 - 315) / - (vmain_high - vmain_low)) * vmain_high; + dev_dbg(gpadc->dev, "IBAT gain %llu offset %llu\n", + gpadc->cal_data[ADC_INPUT_IBAT].gain, + gpadc->cal_data[ADC_INPUT_IBAT].offset); } else { - gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; + /* Calculate gain and offset for VMAIN if all reads succeeded */ + if (!(ret[0] < 0 || ret[1] < 0 || ret[2] < 0)) { + vmain_high = (((gpadc_cal[0] & 0x03) << 8) | + ((gpadc_cal[1] & 0x3F) << 2) | + ((gpadc_cal[2] & 0xC0) >> 6)); + vmain_low = ((gpadc_cal[2] & 0x3E) >> 1); + + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi = + (u16)vmain_high; + gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo = + (u16)vmain_low; + + gpadc->cal_data[ADC_INPUT_VMAIN].gain = CALIB_SCALE * + (19500 - 315) / (vmain_high - vmain_low); + + gpadc->cal_data[ADC_INPUT_VMAIN].offset = CALIB_SCALE * + 19500 - (CALIB_SCALE * (19500 - 315) / + (vmain_high - vmain_low)) * vmain_high; + } else { + gpadc->cal_data[ADC_INPUT_VMAIN].gain = 0; + } } /* Calculate gain and offset for BTEMP if all reads succeeded */ if (!(ret[2] < 0 || ret[3] < 0 || ret[4] < 0)) { btemp_high = (((gpadc_cal[2] & 0x01) << 9) | - (gpadc_cal[3] << 1) | - ((gpadc_cal[4] & 0x80) >> 7)); - + (gpadc_cal[3] << 1) | ((gpadc_cal[4] & 0x80) >> 7)); btemp_low = ((gpadc_cal[4] & 0x7C) >> 2); + gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_hi = (u16)btemp_high; + gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_lo = (u16)btemp_low; + gpadc->cal_data[ADC_INPUT_BTEMP].gain = CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low); - gpadc->cal_data[ADC_INPUT_BTEMP].offset = CALIB_SCALE * 1300 - - (CALIB_SCALE * (1300 - 21) / - (btemp_high - btemp_low)) * btemp_high; + (CALIB_SCALE * (1300 - 21) / (btemp_high - btemp_low)) + * btemp_high; } else { gpadc->cal_data[ADC_INPUT_BTEMP].gain = 0; } @@ -560,9 +842,11 @@ static void ab8500_gpadc_read_calibration_data(struct ab8500_gpadc *gpadc) vbat_high = (((gpadc_cal[4] & 0x03) << 8) | gpadc_cal[5]); vbat_low = ((gpadc_cal[6] & 0xFC) >> 2); + gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_hi = (u16)vbat_high; + gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_lo = (u16)vbat_low; + gpadc->cal_data[ADC_INPUT_VBAT].gain = CALIB_SCALE * (4700 - 2380) / (vbat_high - vbat_low); - gpadc->cal_data[ADC_INPUT_VBAT].offset = CALIB_SCALE * 4700 - (CALIB_SCALE * (4700 - 2380) / (vbat_high - vbat_low)) * vbat_high; @@ -608,6 +892,31 @@ static int ab8500_gpadc_runtime_idle(struct device *dev) return 0; } +static int ab8500_gpadc_suspend(struct device *dev) +{ + struct ab8500_gpadc *gpadc = dev_get_drvdata(dev); + + mutex_lock(&gpadc->ab8500_gpadc_lock); + + pm_runtime_get_sync(dev); + + regulator_disable(gpadc->regu); + return 0; +} + +static int ab8500_gpadc_resume(struct device *dev) +{ + struct ab8500_gpadc *gpadc = dev_get_drvdata(dev); + + regulator_enable(gpadc->regu); + + pm_runtime_mark_last_busy(gpadc->dev); + pm_runtime_put_autosuspend(gpadc->dev); + + mutex_unlock(&gpadc->ab8500_gpadc_lock); + return 0; +} + static int ab8500_gpadc_probe(struct platform_device *pdev) { int ret = 0; @@ -619,13 +928,13 @@ static int ab8500_gpadc_probe(struct platform_device *pdev) return -ENOMEM; } - gpadc->irq = platform_get_irq_byname(pdev, "SW_CONV_END"); - if (gpadc->irq < 0) { - dev_err(&pdev->dev, "failed to get platform irq-%d\n", - gpadc->irq); - ret = gpadc->irq; - goto fail; - } + gpadc->irq_sw = platform_get_irq_byname(pdev, "SW_CONV_END"); + if (gpadc->irq_sw < 0) + dev_err(gpadc->dev, "failed to get platform sw_conv_end irq\n"); + + gpadc->irq_hw = platform_get_irq_byname(pdev, "HW_CONV_END"); + if (gpadc->irq_hw < 0) + dev_err(gpadc->dev, "failed to get platform hw_conv_end irq\n"); gpadc->dev = &pdev->dev; gpadc->parent = dev_get_drvdata(pdev->dev.parent); @@ -634,15 +943,31 @@ static int ab8500_gpadc_probe(struct platform_device *pdev) /* Initialize completion used to notify completion of conversion */ init_completion(&gpadc->ab8500_gpadc_complete); - /* Register interrupt - SwAdcComplete */ - ret = request_threaded_irq(gpadc->irq, NULL, - ab8500_bm_gpswadcconvend_handler, - IRQF_ONESHOT | IRQF_NO_SUSPEND | IRQF_SHARED, - "ab8500-gpadc", gpadc); - if (ret < 0) { - dev_err(gpadc->dev, "Failed to register interrupt, irq: %d\n", - gpadc->irq); - goto fail; + /* Register interrupts */ + if (gpadc->irq_sw >= 0) { + ret = request_threaded_irq(gpadc->irq_sw, NULL, + ab8500_bm_gpadcconvend_handler, + IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-sw", + gpadc); + if (ret < 0) { + dev_err(gpadc->dev, + "Failed to register interrupt irq: %d\n", + gpadc->irq_sw); + goto fail; + } + } + + if (gpadc->irq_hw >= 0) { + ret = request_threaded_irq(gpadc->irq_hw, NULL, + ab8500_bm_gpadcconvend_handler, + IRQF_NO_SUSPEND | IRQF_SHARED, "ab8500-gpadc-hw", + gpadc); + if (ret < 0) { + dev_err(gpadc->dev, + "Failed to register interrupt irq: %d\n", + gpadc->irq_hw); + goto fail_irq; + } } /* VTVout LDO used to power up ab8500-GPADC */ @@ -669,11 +994,13 @@ static int ab8500_gpadc_probe(struct platform_device *pdev) ab8500_gpadc_read_calibration_data(gpadc); list_add_tail(&gpadc->node, &ab8500_gpadc_list); dev_dbg(gpadc->dev, "probe success\n"); + return 0; fail_enable: fail_irq: - free_irq(gpadc->irq, gpadc); + free_irq(gpadc->irq_sw, gpadc); + free_irq(gpadc->irq_hw, gpadc); fail: kfree(gpadc); gpadc = NULL; @@ -687,7 +1014,10 @@ static int ab8500_gpadc_remove(struct platform_device *pdev) /* remove this gpadc entry from the list */ list_del(&gpadc->node); /* remove interrupt - completion of Sw ADC conversion */ - free_irq(gpadc->irq, gpadc); + if (gpadc->irq_sw >= 0) + free_irq(gpadc->irq_sw, gpadc); + if (gpadc->irq_hw >= 0) + free_irq(gpadc->irq_hw, gpadc); pm_runtime_get_sync(gpadc->dev); pm_runtime_disable(gpadc->dev); @@ -707,6 +1037,9 @@ static const struct dev_pm_ops ab8500_gpadc_pm_ops = { SET_RUNTIME_PM_OPS(ab8500_gpadc_runtime_suspend, ab8500_gpadc_runtime_resume, ab8500_gpadc_runtime_idle) + SET_SYSTEM_SLEEP_PM_OPS(ab8500_gpadc_suspend, + ab8500_gpadc_resume) + }; static struct platform_driver ab8500_gpadc_driver = { @@ -729,10 +1062,30 @@ static void __exit ab8500_gpadc_exit(void) platform_driver_unregister(&ab8500_gpadc_driver); } +/** + * ab8540_gpadc_get_otp() - returns OTP values + * + */ +void ab8540_gpadc_get_otp(struct ab8500_gpadc *gpadc, + u16 *vmain_l, u16 *vmain_h, u16 *btemp_l, u16 *btemp_h, + u16 *vbat_l, u16 *vbat_h, u16 *ibat_l, u16 *ibat_h) +{ + *vmain_l = gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_lo; + *vmain_h = gpadc->cal_data[ADC_INPUT_VMAIN].otp_calib_hi; + *btemp_l = gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_lo; + *btemp_h = gpadc->cal_data[ADC_INPUT_BTEMP].otp_calib_hi; + *vbat_l = gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_lo; + *vbat_h = gpadc->cal_data[ADC_INPUT_VBAT].otp_calib_hi; + *ibat_l = gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_lo; + *ibat_h = gpadc->cal_data[ADC_INPUT_IBAT].otp_calib_hi; + return ; +} + subsys_initcall_sync(ab8500_gpadc_init); module_exit(ab8500_gpadc_exit); MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson"); +MODULE_AUTHOR("Arun R Murthy, Daniel Willerud, Johan Palsson," + "M'boumba Cedric Madianga"); MODULE_ALIAS("platform:ab8500_gpadc"); MODULE_DESCRIPTION("AB8500 GPADC driver"); diff --git a/drivers/mfd/ab8500-sysctrl.c b/drivers/mfd/ab8500-sysctrl.c index 108fd86552f..272479cdb10 100644 --- a/drivers/mfd/ab8500-sysctrl.c +++ b/drivers/mfd/ab8500-sysctrl.c @@ -15,19 +15,30 @@ #include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/abx500/ab8500-sysctrl.h> +/* RtcCtrl bits */ +#define AB8500_ALARM_MIN_LOW 0x08 +#define AB8500_ALARM_MIN_MID 0x09 +#define RTC_CTRL 0x0B +#define RTC_ALARM_ENABLE 0x4 + static struct device *sysctrl_dev; void ab8500_power_off(void) { sigset_t old; sigset_t all; - static char *pss[] = {"ab8500_ac", "ab8500_usb"}; + static char *pss[] = {"ab8500_ac", "pm2301", "ab8500_usb"}; int i; bool charger_present = false; union power_supply_propval val; struct power_supply *psy; int ret; + if (sysctrl_dev == NULL) { + pr_err("%s: sysctrl not initialized\n", __func__); + return; + } + /* * If we have a charger connected and we're powering off, * reboot into charge-only mode. @@ -74,6 +85,63 @@ shutdown: } } +/* + * Use the AB WD to reset the platform. It will perform a hard + * reset instead of a soft reset. Write the reset reason to + * the AB before reset, which can be read upon restart. + */ +void ab8500_restart(char mode, const char *cmd) +{ + struct ab8500_platform_data *plat; + struct ab8500_sysctrl_platform_data *pdata; + u16 reason = 0; + u8 val; + + if (sysctrl_dev == NULL) { + pr_err("%s: sysctrl not initialized\n", __func__); + return; + } + + plat = dev_get_platdata(sysctrl_dev->parent); + pdata = plat->sysctrl; + if (pdata->reboot_reason_code) + reason = pdata->reboot_reason_code(cmd); + else + pr_warn("[%s] No reboot reason set. Default reason %d\n", + __func__, reason); + + /* + * Disable RTC alarm, just a precaution so that no alarm + * is running when WD reset is executed. + */ + abx500_get_register_interruptible(sysctrl_dev, AB8500_RTC, + RTC_CTRL , &val); + abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC, + RTC_CTRL , (val & ~RTC_ALARM_ENABLE)); + + /* + * Android is not using the RTC alarm registers during reboot + * so we borrow them for writing the reason of reset + */ + + /* reason[8 LSB] */ + val = reason & 0xFF; + abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC, + AB8500_ALARM_MIN_LOW , val); + + /* reason[8 MSB] */ + val = (reason>>8) & 0xFF; + abx500_set_register_interruptible(sysctrl_dev, AB8500_RTC, + AB8500_ALARM_MIN_MID , val); + + /* Setting WD timeout to 0 */ + ab8500_sysctrl_write(AB8500_MAINWDOGTIMER, 0xFF, 0x0); + + /* Setting the parameters to AB8500 WD*/ + ab8500_sysctrl_write(AB8500_MAINWDOGCTRL, 0xFF, (AB8500_ENABLE_WD | + AB8500_WD_RESTART_ON_EXPIRE | AB8500_KICK_WD)); +} + static inline bool valid_bank(u8 bank) { return ((bank == AB8500_SYS_CTRL1_BLOCK) || @@ -85,7 +153,7 @@ int ab8500_sysctrl_read(u16 reg, u8 *value) u8 bank; if (sysctrl_dev == NULL) - return -EAGAIN; + return -EINVAL; bank = (reg >> 8); if (!valid_bank(bank)) @@ -101,7 +169,7 @@ int ab8500_sysctrl_write(u16 reg, u8 mask, u8 value) u8 bank; if (sysctrl_dev == NULL) - return -EAGAIN; + return -EINVAL; bank = (reg >> 8); if (!valid_bank(bank)) @@ -114,28 +182,36 @@ EXPORT_SYMBOL(ab8500_sysctrl_write); static int ab8500_sysctrl_probe(struct platform_device *pdev) { + struct ab8500 *ab8500 = dev_get_drvdata(pdev->dev.parent); struct ab8500_platform_data *plat; struct ab8500_sysctrl_platform_data *pdata; - sysctrl_dev = &pdev->dev; plat = dev_get_platdata(pdev->dev.parent); + + if (!(plat && plat->sysctrl)) + return -EINVAL; + if (plat->pm_power_off) pm_power_off = ab8500_power_off; pdata = plat->sysctrl; if (pdata) { - int ret, i, j; + int last, ret, i, j; + + if (is_ab8505(ab8500)) + last = AB8500_SYSCLKREQ4RFCLKBUF; + else + last = AB8500_SYSCLKREQ8RFCLKBUF; - for (i = AB8500_SYSCLKREQ1RFCLKBUF; - i <= AB8500_SYSCLKREQ8RFCLKBUF; i++) { + for (i = AB8500_SYSCLKREQ1RFCLKBUF; i <= last; i++) { j = i - AB8500_SYSCLKREQ1RFCLKBUF; ret = ab8500_sysctrl_write(i, 0xff, - pdata->initial_req_buf_config[j]); + pdata->initial_req_buf_config[j]); dev_dbg(&pdev->dev, - "Setting SysClkReq%dRfClkBuf 0x%X\n", - j + 1, - pdata->initial_req_buf_config[j]); + "Setting SysClkReq%dRfClkBuf 0x%X\n", + j + 1, + pdata->initial_req_buf_config[j]); if (ret < 0) { dev_err(&pdev->dev, "unable to set sysClkReq%dRfClkBuf: " diff --git a/drivers/platform/x86/sony-laptop.c b/drivers/platform/x86/sony-laptop.c index 14d4dced1de..d544e3aaf76 100644 --- a/drivers/platform/x86/sony-laptop.c +++ b/drivers/platform/x86/sony-laptop.c @@ -4121,7 +4121,7 @@ static int sony_pic_enable(struct acpi_device *device, resource->res3.data.irq.sharable = ACPI_SHARED; resource->res4.type = ACPI_RESOURCE_TYPE_END_TAG; - + resource->res4.length = sizeof(struct acpi_resource); } /* setup Type 2/3 resources */ else { @@ -4140,6 +4140,7 @@ static int sony_pic_enable(struct acpi_device *device, resource->res2.data.irq.sharable = ACPI_SHARED; resource->res3.type = ACPI_RESOURCE_TYPE_END_TAG; + resource->res3.length = sizeof(struct acpi_resource); } /* Attempt to set the resource */ diff --git a/drivers/pnp/isapnp/core.c b/drivers/pnp/isapnp/core.c index 918d5f04486..cf88f9b6244 100644 --- a/drivers/pnp/isapnp/core.c +++ b/drivers/pnp/isapnp/core.c @@ -379,10 +379,6 @@ static int __init isapnp_read_tag(unsigned char *type, unsigned short *size) *type = (tag >> 3) & 0x0f; *size = tag & 0x07; } -#if 0 - printk(KERN_DEBUG "tag = 0x%x, type = 0x%x, size = %i\n", tag, *type, - *size); -#endif if (*type == 0xff && *size == 0xffff) /* probably invalid data */ return -1; return 0; @@ -813,13 +809,6 @@ static int __init isapnp_build_device_list(void) if (!card) continue; -#if 0 - dev_info(&card->dev, - "vendor: %02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x:%02x\n", - header[0], header[1], header[2], header[3], header[4], - header[5], header[6], header[7], header[8]); - dev_info(&card->dev, "checksum = %#x\n", checksum); -#endif INIT_LIST_HEAD(&card->devices); card->serial = (header[7] << 24) | (header[6] << 16) | (header[5] << 8) | diff --git a/drivers/pnp/pnpacpi/rsparser.c b/drivers/pnp/pnpacpi/rsparser.c index b8f4ea7b27f..9847ab16382 100644 --- a/drivers/pnp/pnpacpi/rsparser.c +++ b/drivers/pnp/pnpacpi/rsparser.c @@ -634,6 +634,7 @@ int pnpacpi_build_resource_template(struct pnp_dev *dev, } /* resource will pointer the end resource now */ resource->type = ACPI_RESOURCE_TYPE_END_TAG; + resource->length = sizeof(struct acpi_resource); return 0; } diff --git a/drivers/pnp/pnpbios/proc.c b/drivers/pnp/pnpbios/proc.c index 63ddb017345..1c03ee822e5 100644 --- a/drivers/pnp/pnpbios/proc.c +++ b/drivers/pnp/pnpbios/proc.c @@ -185,10 +185,9 @@ static int pnp_devices_proc_show(struct seq_file *m, void *v) if (pnp_bios_get_dev_node(&nodenum, PNPMODE_DYNAMIC, node)) break; - seq_printf(m, "%02x\t%08x\t%02x:%02x:%02x\t%04x\n", + seq_printf(m, "%02x\t%08x\t%3phC\t%04x\n", node->handle, node->eisa_id, - node->type_code[0], node->type_code[1], - node->type_code[2], node->flags); + node->type_code, node->flags); if (nodenum <= thisnodenum) { printk(KERN_ERR "%s Node number 0x%x is out of sequence following node 0x%x. Aborting.\n", diff --git a/drivers/power/88pm860x_charger.c b/drivers/power/88pm860x_charger.c index 4b37a5af8de..36fb4b5a4b0 100644 --- a/drivers/power/88pm860x_charger.c +++ b/drivers/power/88pm860x_charger.c @@ -714,7 +714,6 @@ out_irq: while (--i >= 0) free_irq(info->irq[i], info); out: - kfree(info); return ret; } @@ -728,7 +727,6 @@ static int pm860x_charger_remove(struct platform_device *pdev) free_irq(info->irq[0], info); for (i = 0; i < info->irq_nums; i++) free_irq(info->irq[i], info); - kfree(info); return 0; } diff --git a/drivers/power/Kconfig b/drivers/power/Kconfig index ffe02fb7cbc..0d0b5d7d19d 100644 --- a/drivers/power/Kconfig +++ b/drivers/power/Kconfig @@ -340,6 +340,13 @@ config CHARGER_SMB347 Say Y to include support for Summit Microelectronics SMB347 Battery Charger. +config CHARGER_TPS65090 + tristate "TPS65090 battery charger driver" + depends on MFD_TPS65090 + help + Say Y here to enable support for battery charging with TPS65090 + PMIC chips. + config AB8500_BM bool "AB8500 Battery Management Driver" depends on AB8500_CORE && AB8500_GPADC @@ -353,13 +360,6 @@ config BATTERY_GOLDFISH Say Y to enable support for the battery and AC power in the Goldfish emulator. -config CHARGER_PM2301 - bool "PM2301 Battery Charger Driver" - depends on AB8500_BM - help - Say Y to include support for PM2301 charger driver. - Depends on AB8500 battery management core. - source "drivers/power/reset/Kconfig" endif # POWER_SUPPLY diff --git a/drivers/power/Makefile b/drivers/power/Makefile index 3f66436af45..653bf6ceff3 100644 --- a/drivers/power/Makefile +++ b/drivers/power/Makefile @@ -39,7 +39,7 @@ obj-$(CONFIG_CHARGER_PCF50633) += pcf50633-charger.o obj-$(CONFIG_BATTERY_JZ4740) += jz4740-battery.o obj-$(CONFIG_BATTERY_INTEL_MID) += intel_mid_battery.o obj-$(CONFIG_BATTERY_RX51) += rx51_battery.o -obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o +obj-$(CONFIG_AB8500_BM) += ab8500_bmdata.o ab8500_charger.o ab8500_fg.o ab8500_btemp.o abx500_chargalg.o pm2301_charger.o obj-$(CONFIG_CHARGER_ISP1704) += isp1704_charger.o obj-$(CONFIG_CHARGER_MAX8903) += max8903_charger.o obj-$(CONFIG_CHARGER_TWL4030) += twl4030_charger.o @@ -47,10 +47,10 @@ obj-$(CONFIG_CHARGER_LP8727) += lp8727_charger.o obj-$(CONFIG_CHARGER_LP8788) += lp8788-charger.o obj-$(CONFIG_CHARGER_GPIO) += gpio-charger.o obj-$(CONFIG_CHARGER_MANAGER) += charger-manager.o -obj-$(CONFIG_CHARGER_PM2301) += pm2301_charger.o obj-$(CONFIG_CHARGER_MAX8997) += max8997_charger.o obj-$(CONFIG_CHARGER_MAX8998) += max8998_charger.o obj-$(CONFIG_CHARGER_BQ2415X) += bq2415x_charger.o obj-$(CONFIG_POWER_AVS) += avs/ obj-$(CONFIG_CHARGER_SMB347) += smb347-charger.o +obj-$(CONFIG_CHARGER_TPS65090) += tps65090-charger.o obj-$(CONFIG_POWER_RESET) += reset/ diff --git a/drivers/power/ab8500_bmdata.c b/drivers/power/ab8500_bmdata.c index 7a96c0650fb..d2986453309 100644 --- a/drivers/power/ab8500_bmdata.c +++ b/drivers/power/ab8500_bmdata.c @@ -11,7 +11,7 @@ * Note that the res_to_temp table must be strictly sorted by falling resistance * values to work. */ -static struct abx500_res_to_temp temp_tbl_A_thermistor[] = { +const struct abx500_res_to_temp ab8500_temp_tbl_a_thermistor[] = { {-5, 53407}, { 0, 48594}, { 5, 43804}, @@ -28,8 +28,12 @@ static struct abx500_res_to_temp temp_tbl_A_thermistor[] = { {60, 13437}, {65, 12500}, }; +EXPORT_SYMBOL(ab8500_temp_tbl_a_thermistor); -static struct abx500_res_to_temp temp_tbl_B_thermistor[] = { +const int ab8500_temp_tbl_a_size = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor); +EXPORT_SYMBOL(ab8500_temp_tbl_a_size); + +const struct abx500_res_to_temp ab8500_temp_tbl_b_thermistor[] = { {-5, 200000}, { 0, 159024}, { 5, 151921}, @@ -46,8 +50,12 @@ static struct abx500_res_to_temp temp_tbl_B_thermistor[] = { {60, 85461}, {65, 82869}, }; +EXPORT_SYMBOL(ab8500_temp_tbl_b_thermistor); + +const int ab8500_temp_tbl_b_size = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor); +EXPORT_SYMBOL(ab8500_temp_tbl_b_size); -static struct abx500_v_to_cap cap_tbl_A_thermistor[] = { +static const struct abx500_v_to_cap cap_tbl_a_thermistor[] = { {4171, 100}, {4114, 95}, {4009, 83}, @@ -70,7 +78,7 @@ static struct abx500_v_to_cap cap_tbl_A_thermistor[] = { {3247, 0}, }; -static struct abx500_v_to_cap cap_tbl_B_thermistor[] = { +static const struct abx500_v_to_cap cap_tbl_b_thermistor[] = { {4161, 100}, {4124, 98}, {4044, 90}, @@ -93,7 +101,7 @@ static struct abx500_v_to_cap cap_tbl_B_thermistor[] = { {3250, 0}, }; -static struct abx500_v_to_cap cap_tbl[] = { +static const struct abx500_v_to_cap cap_tbl[] = { {4186, 100}, {4163, 99}, {4114, 95}, @@ -124,7 +132,7 @@ static struct abx500_v_to_cap cap_tbl[] = { * Note that the res_to_temp table must be strictly sorted by falling * resistance values to work. */ -static struct abx500_res_to_temp temp_tbl[] = { +static const struct abx500_res_to_temp temp_tbl[] = { {-5, 214834}, { 0, 162943}, { 5, 124820}, @@ -146,7 +154,7 @@ static struct abx500_res_to_temp temp_tbl[] = { * Note that the batres_vs_temp table must be strictly sorted by falling * temperature values to work. */ -static struct batres_vs_temp temp_to_batres_tbl_thermistor[] = { +static const struct batres_vs_temp temp_to_batres_tbl_thermistor[] = { { 40, 120}, { 30, 135}, { 20, 165}, @@ -160,7 +168,7 @@ static struct batres_vs_temp temp_to_batres_tbl_thermistor[] = { * Note that the batres_vs_temp table must be strictly sorted by falling * temperature values to work. */ -static struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = { +static const struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = { { 60, 300}, { 30, 300}, { 20, 300}, @@ -171,7 +179,7 @@ static struct batres_vs_temp temp_to_batres_tbl_ext_thermistor[] = { }; /* battery resistance table for LI ION 9100 battery */ -static struct batres_vs_temp temp_to_batres_tbl_9100[] = { +static const struct batres_vs_temp temp_to_batres_tbl_9100[] = { { 60, 180}, { 30, 180}, { 20, 180}, @@ -230,10 +238,10 @@ static struct abx500_battery_type bat_type_thermistor[] = { .maint_b_chg_timer_h = 200, .low_high_cur_lvl = 300, .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_A_thermistor), - .r_to_t_tbl = temp_tbl_A_thermistor, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_A_thermistor), - .v_to_cap_tbl = cap_tbl_A_thermistor, + .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_a_thermistor), + .r_to_t_tbl = ab8500_temp_tbl_a_thermistor, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_a_thermistor), + .v_to_cap_tbl = cap_tbl_a_thermistor, .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), .batres_tbl = temp_to_batres_tbl_thermistor, @@ -258,10 +266,10 @@ static struct abx500_battery_type bat_type_thermistor[] = { .maint_b_chg_timer_h = 200, .low_high_cur_lvl = 300, .low_high_vol_lvl = 4000, - .n_temp_tbl_elements = ARRAY_SIZE(temp_tbl_B_thermistor), - .r_to_t_tbl = temp_tbl_B_thermistor, - .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_B_thermistor), - .v_to_cap_tbl = cap_tbl_B_thermistor, + .n_temp_tbl_elements = ARRAY_SIZE(ab8500_temp_tbl_b_thermistor), + .r_to_t_tbl = ab8500_temp_tbl_b_thermistor, + .n_v_cap_tbl_elements = ARRAY_SIZE(cap_tbl_b_thermistor), + .v_to_cap_tbl = cap_tbl_b_thermistor, .n_batres_tbl_elements = ARRAY_SIZE(temp_to_batres_tbl_thermistor), .batres_tbl = temp_to_batres_tbl_thermistor, }, @@ -407,15 +415,27 @@ static const struct abx500_fg_parameters fg = { .battok_raising_th_sel1 = 2860, .maint_thres = 95, .user_cap_limit = 15, + .pcut_enable = 1, + .pcut_max_time = 127, + .pcut_flag_time = 112, + .pcut_max_restart = 15, + .pcut_debounce_time = 2, }; -static const struct abx500_maxim_parameters maxi_params = { +static const struct abx500_maxim_parameters ab8500_maxi_params = { .ena_maxi = true, .chg_curr = 910, .wait_cycles = 10, .charger_curr_step = 100, }; +static const struct abx500_maxim_parameters abx540_maxi_params = { + .ena_maxi = true, + .chg_curr = 3000, + .wait_cycles = 10, + .charger_curr_step = 200, +}; + static const struct abx500_bm_charger_parameters chg = { .usb_volt_max = 5500, .usb_curr_max = 1500, @@ -423,6 +443,46 @@ static const struct abx500_bm_charger_parameters chg = { .ac_curr_max = 1500, }; +/* + * This array maps the raw hex value to charger output current used by the + * AB8500 values + */ +static int ab8500_charge_output_curr_map[] = { + 100, 200, 300, 400, 500, 600, 700, 800, + 900, 1000, 1100, 1200, 1300, 1400, 1500, 1500, +}; + +static int ab8540_charge_output_curr_map[] = { + 0, 0, 0, 75, 100, 125, 150, 175, + 200, 225, 250, 275, 300, 325, 350, 375, + 400, 425, 450, 475, 500, 525, 550, 575, + 600, 625, 650, 675, 700, 725, 750, 775, + 800, 825, 850, 875, 900, 925, 950, 975, + 1000, 1025, 1050, 1075, 1100, 1125, 1150, 1175, + 1200, 1225, 1250, 1275, 1300, 1325, 1350, 1375, + 1400, 1425, 1450, 1500, 1600, 1700, 1900, 2000, +}; + +/* + * This array maps the raw hex value to charger input current used by the + * AB8500 values + */ +static int ab8500_charge_input_curr_map[] = { + 50, 98, 193, 290, 380, 450, 500, 600, + 700, 800, 900, 1000, 1100, 1300, 1400, 1500, +}; + +static int ab8540_charge_input_curr_map[] = { + 25, 50, 75, 100, 125, 150, 175, 200, + 225, 250, 275, 300, 325, 350, 375, 400, + 425, 450, 475, 500, 525, 550, 575, 600, + 625, 650, 675, 700, 725, 750, 775, 800, + 825, 850, 875, 900, 925, 950, 975, 1000, + 1025, 1050, 1075, 1100, 1125, 1150, 1175, 1200, + 1225, 1250, 1275, 1300, 1325, 1350, 1375, 1400, + 1425, 1450, 1475, 1500, 1500, 1500, 1500, 1500, +}; + struct abx500_bm_data ab8500_bm_data = { .temp_under = 3, .temp_low = 8, @@ -442,22 +502,60 @@ struct abx500_bm_data ab8500_bm_data = { .fg_res = 100, .cap_levels = &cap_levels, .bat_type = bat_type_thermistor, - .n_btypes = 3, + .n_btypes = ARRAY_SIZE(bat_type_thermistor), .batt_id = 0, .interval_charging = 5, .interval_not_charging = 120, .temp_hysteresis = 3, .gnd_lift_resistance = 34, - .maxi = &maxi_params, + .chg_output_curr = ab8500_charge_output_curr_map, + .n_chg_out_curr = ARRAY_SIZE(ab8500_charge_output_curr_map), + .maxi = &ab8500_maxi_params, .chg_params = &chg, .fg_params = &fg, + .chg_input_curr = ab8500_charge_input_curr_map, + .n_chg_in_curr = ARRAY_SIZE(ab8500_charge_input_curr_map), +}; + +struct abx500_bm_data ab8540_bm_data = { + .temp_under = 3, + .temp_low = 8, + .temp_high = 43, + .temp_over = 48, + .main_safety_tmr_h = 4, + .temp_interval_chg = 20, + .temp_interval_nochg = 120, + .usb_safety_tmr_h = 4, + .bkup_bat_v = BUP_VCH_SEL_2P6V, + .bkup_bat_i = BUP_ICH_SEL_150UA, + .no_maintenance = false, + .capacity_scaling = false, + .adc_therm = ABx500_ADC_THERM_BATCTRL, + .chg_unknown_bat = false, + .enable_overshoot = false, + .fg_res = 100, + .cap_levels = &cap_levels, + .bat_type = bat_type_thermistor, + .n_btypes = ARRAY_SIZE(bat_type_thermistor), + .batt_id = 0, + .interval_charging = 5, + .interval_not_charging = 120, + .temp_hysteresis = 3, + .gnd_lift_resistance = 0, + .maxi = &abx540_maxi_params, + .chg_params = &chg, + .fg_params = &fg, + .chg_output_curr = ab8540_charge_output_curr_map, + .n_chg_out_curr = ARRAY_SIZE(ab8540_charge_output_curr_map), + .chg_input_curr = ab8540_charge_input_curr_map, + .n_chg_in_curr = ARRAY_SIZE(ab8540_charge_input_curr_map), }; int ab8500_bm_of_probe(struct device *dev, struct device_node *np, struct abx500_bm_data *bm) { - struct batres_vs_temp *tmp_batres_tbl; + const struct batres_vs_temp *tmp_batres_tbl; struct device_node *battery_node; const char *btech; int i; diff --git a/drivers/power/ab8500_btemp.c b/drivers/power/ab8500_btemp.c index 07689064996..d412d34bf3d 100644 --- a/drivers/power/ab8500_btemp.c +++ b/drivers/power/ab8500_btemp.c @@ -42,6 +42,9 @@ #define BTEMP_BATCTRL_CURR_SRC_16UA 16 #define BTEMP_BATCTRL_CURR_SRC_18UA 18 +#define BTEMP_BATCTRL_CURR_SRC_60UA 60 +#define BTEMP_BATCTRL_CURR_SRC_120UA 120 + #define to_ab8500_btemp_device_info(x) container_of((x), \ struct ab8500_btemp, btemp_psy); @@ -76,8 +79,8 @@ struct ab8500_btemp_ranges { * @dev: Pointer to the structure device * @node: List of AB8500 BTEMPs, hence prepared for reentrance * @curr_source: What current source we use, in uA - * @bat_temp: Battery temperature in degree Celcius - * @prev_bat_temp Last dispatched battery temperature + * @bat_temp: Dispatched battery temperature in degree Celcius + * @prev_bat_temp Last measured battery temperature in degree Celcius * @parent: Pointer to the struct ab8500 * @gpadc: Pointer to the struct gpadc * @fg: Pointer to the struct fg @@ -128,6 +131,7 @@ struct ab8500_btemp *ab8500_btemp_get(void) return btemp; } +EXPORT_SYMBOL(ab8500_btemp_get); /** * ab8500_btemp_batctrl_volt_to_res() - convert batctrl voltage to resistance @@ -155,7 +159,7 @@ static int ab8500_btemp_batctrl_volt_to_res(struct ab8500_btemp *di, if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL) { /* * If the battery has internal NTC, we use the current - * source to calculate the resistance, 7uA or 20uA + * source to calculate the resistance. */ rbs = (v_batctrl * 1000 - di->bm->gnd_lift_resistance * inst_curr) @@ -216,7 +220,12 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, /* Only do this for batteries with internal NTC */ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && enable) { - if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + if (is_ab8540(di->parent)) { + if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_60UA) + curr = BAT_CTRL_60U_ENA; + else + curr = BAT_CTRL_120U_ENA; + } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { if (di->curr_source == BTEMP_BATCTRL_CURR_SRC_16UA) curr = BAT_CTRL_16U_ENA; else @@ -257,7 +266,14 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, } else if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && !enable) { dev_dbg(di->dev, "Disable BATCTRL curr source\n"); - if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + if (is_ab8540(di->parent)) { + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible( + di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA, + ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA)); + } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { /* Write 0 to the curr bits */ ret = abx500_mask_and_set_register_interruptible( di->dev, @@ -314,7 +330,13 @@ static int ab8500_btemp_curr_source_enable(struct ab8500_btemp *di, * if we got an error above */ disable_curr_source: - if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + if (is_ab8540(di->parent)) { + /* Write 0 to the curr bits */ + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, + BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA, + ~(BAT_CTRL_60U_ENA | BAT_CTRL_120U_ENA)); + } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { /* Write 0 to the curr bits */ ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_CHARGER, AB8500_BAT_CTRL_CURRENT_SOURCE, @@ -541,7 +563,9 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) { int res; u8 i; - if (is_ab9540(di->parent) || is_ab8505(di->parent)) + if (is_ab8540(di->parent)) + di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA; + else if (is_ab9540(di->parent) || is_ab8505(di->parent)) di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA; else di->curr_source = BTEMP_BATCTRL_CURR_SRC_7UA; @@ -579,12 +603,17 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) /* * We only have to change current source if the - * detected type is Type 1, else we use the 7uA source + * detected type is Type 1. */ if (di->bm->adc_therm == ABx500_ADC_THERM_BATCTRL && - di->bm->batt_id == 1) { - if (is_ab9540(di->parent) || is_ab8505(di->parent)) { - dev_dbg(di->dev, "Set BATCTRL current source to 16uA\n"); + di->bm->batt_id == 1) { + if (is_ab8540(di->parent)) { + dev_dbg(di->dev, + "Set BATCTRL current source to 60uA\n"); + di->curr_source = BTEMP_BATCTRL_CURR_SRC_60UA; + } else if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + dev_dbg(di->dev, + "Set BATCTRL current source to 16uA\n"); di->curr_source = BTEMP_BATCTRL_CURR_SRC_16UA; } else { dev_dbg(di->dev, "Set BATCTRL current source to 20uA\n"); @@ -604,22 +633,37 @@ static int ab8500_btemp_id(struct ab8500_btemp *di) static void ab8500_btemp_periodic_work(struct work_struct *work) { int interval; + int bat_temp; struct ab8500_btemp *di = container_of(work, struct ab8500_btemp, btemp_periodic_work.work); if (!di->initialized) { - di->initialized = true; /* Identify the battery */ if (ab8500_btemp_id(di) < 0) dev_warn(di->dev, "failed to identify the battery\n"); } - di->bat_temp = ab8500_btemp_measure_temp(di); - - if (di->bat_temp != di->prev_bat_temp) { - di->prev_bat_temp = di->bat_temp; + bat_temp = ab8500_btemp_measure_temp(di); + /* + * Filter battery temperature. + * Allow direct updates on temperature only if two samples result in + * same temperature. Else only allow 1 degree change from previous + * reported value in the direction of the new measurement. + */ + if ((bat_temp == di->prev_bat_temp) || !di->initialized) { + if ((di->bat_temp != di->prev_bat_temp) || !di->initialized) { + di->initialized = true; + di->bat_temp = bat_temp; + power_supply_changed(&di->btemp_psy); + } + } else if (bat_temp < di->prev_bat_temp) { + di->bat_temp--; + power_supply_changed(&di->btemp_psy); + } else if (bat_temp > di->prev_bat_temp) { + di->bat_temp++; power_supply_changed(&di->btemp_psy); } + di->prev_bat_temp = bat_temp; if (di->events.ac_conn || di->events.usb_conn) interval = di->bm->temp_interval_chg; @@ -772,7 +816,7 @@ static void ab8500_btemp_periodic(struct ab8500_btemp *di, * * Returns battery temperature */ -static int ab8500_btemp_get_temp(struct ab8500_btemp *di) +int ab8500_btemp_get_temp(struct ab8500_btemp *di) { int temp = 0; @@ -808,6 +852,7 @@ static int ab8500_btemp_get_temp(struct ab8500_btemp *di) } return temp; } +EXPORT_SYMBOL(ab8500_btemp_get_temp); /** * ab8500_btemp_get_batctrl_temp() - get the temperature @@ -819,6 +864,7 @@ int ab8500_btemp_get_batctrl_temp(struct ab8500_btemp *btemp) { return btemp->bat_temp * 1000; } +EXPORT_SYMBOL(ab8500_btemp_get_batctrl_temp); /** * ab8500_btemp_get_property() - get the btemp properties diff --git a/drivers/power/ab8500_charger.c b/drivers/power/ab8500_charger.c index 24b30b7ea5c..a558318b169 100644 --- a/drivers/power/ab8500_charger.c +++ b/drivers/power/ab8500_charger.c @@ -15,6 +15,7 @@ #include <linux/device.h> #include <linux/interrupt.h> #include <linux/delay.h> +#include <linux/notifier.h> #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/power_supply.h> @@ -52,10 +53,15 @@ #define VBUS_DET_DBNC100 0x02 #define VBUS_DET_DBNC1 0x01 #define OTP_ENABLE_WD 0x01 +#define DROP_COUNT_RESET 0x01 +#define USB_CH_DET 0x01 #define MAIN_CH_INPUT_CURR_SHIFT 4 #define VBUS_IN_CURR_LIM_SHIFT 4 +#define AB8540_VBUS_IN_CURR_LIM_SHIFT 2 #define AUTO_VBUS_IN_CURR_LIM_SHIFT 4 +#define AB8540_AUTO_VBUS_IN_CURR_MASK 0x3F +#define VBUS_IN_CURR_LIM_RETRY_SET_TIME 30 /* seconds */ #define LED_INDICATOR_PWM_ENA 0x01 #define LED_INDICATOR_PWM_DIS 0x00 @@ -77,7 +83,9 @@ /* UsbLineStatus register bit masks */ #define AB8500_USB_LINK_STATUS 0x78 +#define AB8505_USB_LINK_STATUS 0xF8 #define AB8500_STD_HOST_SUSP 0x18 +#define USB_LINK_STATUS_SHIFT 3 /* Watchdog timeout constant */ #define WD_TIMER 0x30 /* 4min */ @@ -96,6 +104,10 @@ #define AB8500_SW_CONTROL_FALLBACK 0x03 /* Wait for enumeration before charing in us */ #define WAIT_ACA_RID_ENUMERATION (5 * 1000) +/*External charger control*/ +#define AB8500_SYS_CHARGER_CONTROL_REG 0x52 +#define EXTERNAL_CHARGER_DISABLE_REG_VAL 0x03 +#define EXTERNAL_CHARGER_ENABLE_REG_VAL 0x07 /* UsbLineStatus register - usb types */ enum ab8500_charger_link_status { @@ -196,10 +208,15 @@ struct ab8500_charger_usb_state { spinlock_t usb_lock; }; +struct ab8500_charger_max_usb_in_curr { + int usb_type_max; + int set_max; + int calculated_max; +}; + /** * struct ab8500_charger - ab8500 Charger device information * @dev: Pointer to the structure device - * @max_usb_in_curr: Max USB charger input current * @vbus_detected: VBUS detected * @vbus_detected_start: * VBUS detected during startup @@ -214,7 +231,6 @@ struct ab8500_charger_usb_state { * @autopower Indicate if we should have automatic pwron after pwrloss * @autopower_cfg platform specific power config support for "pwron after pwrloss" * @invalid_charger_detect_state State when forcing AB to use invalid charger - * @is_usb_host: Indicate if last detected USB type is host * @is_aca_rid: Incicate if accessory is ACA type * @current_stepping_sessions: * Counter for current stepping sessions @@ -223,6 +239,7 @@ struct ab8500_charger_usb_state { * @bm: Platform specific battery management information * @flags: Structure for information about events triggered * @usb_state: Structure for usb stack information + * @max_usb_in_curr: Max USB charger input current * @ac_chg: AC charger power supply * @usb_chg: USB charger power supply * @ac: Structure that holds the AC charger properties @@ -254,7 +271,6 @@ struct ab8500_charger_usb_state { */ struct ab8500_charger { struct device *dev; - int max_usb_in_curr; bool vbus_detected; bool vbus_detected_start; bool ac_conn; @@ -266,7 +282,6 @@ struct ab8500_charger { bool autopower; bool autopower_cfg; int invalid_charger_detect_state; - bool is_usb_host; int is_aca_rid; atomic_t current_stepping_sessions; struct ab8500 *parent; @@ -274,6 +289,7 @@ struct ab8500_charger { struct abx500_bm_data *bm; struct ab8500_charger_event_flags flags; struct ab8500_charger_usb_state usb_state; + struct ab8500_charger_max_usb_in_curr max_usb_in_curr; struct ux500_charger ac_chg; struct ux500_charger usb_chg; struct ab8500_charger_info ac; @@ -415,13 +431,18 @@ static void ab8500_charger_set_usb_connected(struct ab8500_charger *di, if (connected != di->usb.charger_connected) { dev_dbg(di->dev, "USB connected:%i\n", connected); di->usb.charger_connected = connected; + + if (!connected) + di->flags.vbus_drop_end = false; + sysfs_notify(&di->usb_chg.psy.dev->kobj, NULL, "present"); if (connected) { mutex_lock(&di->charger_attached_mutex); mutex_unlock(&di->charger_attached_mutex); - queue_delayed_work(di->charger_wq, + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, &di->usb_charger_attached_work, HZ); } else { @@ -668,23 +689,19 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, case USB_STAT_STD_HOST_C_S: dev_dbg(di->dev, "USB Type - Standard host is " "detected through USB driver\n"); - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; - di->is_usb_host = true; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; di->is_aca_rid = 0; break; case USB_STAT_HOST_CHG_HS_CHIRP: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; - di->is_usb_host = true; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; di->is_aca_rid = 0; break; case USB_STAT_HOST_CHG_HS: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; - di->is_usb_host = true; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; di->is_aca_rid = 0; break; case USB_STAT_ACA_RID_C_HS: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P9; - di->is_usb_host = false; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P9; di->is_aca_rid = 0; break; case USB_STAT_ACA_RID_A: @@ -693,8 +710,7 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, * can consume (900mA). Closest level is 500mA */ dev_dbg(di->dev, "USB_STAT_ACA_RID_A detected\n"); - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; - di->is_usb_host = false; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; di->is_aca_rid = 1; break; case USB_STAT_ACA_RID_B: @@ -702,38 +718,35 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, * Dedicated charger level minus 120mA (20mA for ACA and * 100mA for potential accessory). Closest level is 1300mA */ - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P3; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P3; dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, - di->max_usb_in_curr); - di->is_usb_host = false; + di->max_usb_in_curr.usb_type_max); di->is_aca_rid = 1; break; case USB_STAT_HOST_CHG_NM: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; - di->is_usb_host = true; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; di->is_aca_rid = 0; break; case USB_STAT_DEDICATED_CHG: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; - di->is_usb_host = false; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5; di->is_aca_rid = 0; break; case USB_STAT_ACA_RID_C_HS_CHIRP: case USB_STAT_ACA_RID_C_NM: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; - di->is_usb_host = false; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_1P5; di->is_aca_rid = 1; break; case USB_STAT_NOT_CONFIGURED: if (di->vbus_detected) { di->usb_device_is_unrecognised = true; dev_dbg(di->dev, "USB Type - Legacy charger.\n"); - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_1P5; + di->max_usb_in_curr.usb_type_max = + USB_CH_IP_CUR_LVL_1P5; break; } case USB_STAT_HM_IDGND: dev_err(di->dev, "USB Type - Charging not allowed\n"); - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; ret = -ENXIO; break; case USB_STAT_RESERVED: @@ -743,12 +756,13 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, "VBUS has collapsed\n"); ret = -ENXIO; break; - } - if (is_ab9540(di->parent) || is_ab8505(di->parent)) { + } else { dev_dbg(di->dev, "USB Type - Charging not allowed\n"); - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + di->max_usb_in_curr.usb_type_max = + USB_CH_IP_CUR_LVL_0P05; dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", - link_status, di->max_usb_in_curr); + link_status, + di->max_usb_in_curr.usb_type_max); ret = -ENXIO; break; } @@ -757,23 +771,24 @@ static int ab8500_charger_max_usb_curr(struct ab8500_charger *di, case USB_STAT_CARKIT_2: case USB_STAT_ACA_DOCK_CHARGER: case USB_STAT_CHARGER_LINE_1: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", link_status, - di->max_usb_in_curr); + di->max_usb_in_curr.usb_type_max); case USB_STAT_NOT_VALID_LINK: dev_err(di->dev, "USB Type invalid - try charging anyway\n"); - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; break; default: dev_err(di->dev, "USB Type - Unknown\n"); - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; ret = -ENXIO; break; }; + di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; dev_dbg(di->dev, "USB Type - 0x%02x MaxCurr: %d", - link_status, di->max_usb_in_curr); + link_status, di->max_usb_in_curr.set_max); return ret; } @@ -796,21 +811,22 @@ static int ab8500_charger_read_usb_type(struct ab8500_charger *di) dev_err(di->dev, "%s ab8500 read failed\n", __func__); return ret; } - if (is_ab8500(di->parent)) { + if (is_ab8500(di->parent)) ret = abx500_get_register_interruptible(di->dev, AB8500_USB, - AB8500_USB_LINE_STAT_REG, &val); - } else { - if (is_ab9540(di->parent) || is_ab8505(di->parent)) - ret = abx500_get_register_interruptible(di->dev, - AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); - } + AB8500_USB_LINE_STAT_REG, &val); + else + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINK1_STAT_REG, &val); if (ret < 0) { dev_err(di->dev, "%s ab8500 read failed\n", __func__); return ret; } /* get the USB type */ - val = (val & AB8500_USB_LINK_STATUS) >> 3; + if (is_ab8500(di->parent)) + val = (val & AB8500_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; + else + val = (val & AB8505_USB_LINK_STATUS) >> USB_LINK_STATUS_SHIFT; ret = ab8500_charger_max_usb_curr(di, (enum ab8500_charger_link_status) val); @@ -865,7 +881,12 @@ static int ab8500_charger_detect_usb_type(struct ab8500_charger *di) */ /* get the USB type */ - val = (val & AB8500_USB_LINK_STATUS) >> 3; + if (is_ab8500(di->parent)) + val = (val & AB8500_USB_LINK_STATUS) >> + USB_LINK_STATUS_SHIFT; + else + val = (val & AB8505_USB_LINK_STATUS) >> + USB_LINK_STATUS_SHIFT; if (val) break; } @@ -960,51 +981,6 @@ static int ab8500_charger_voltage_map[] = { 4600 , }; -/* - * This array maps the raw hex value to charger current used by the AB8500 - * Values taken from the UM0836 - */ -static int ab8500_charger_current_map[] = { - 100 , - 200 , - 300 , - 400 , - 500 , - 600 , - 700 , - 800 , - 900 , - 1000 , - 1100 , - 1200 , - 1300 , - 1400 , - 1500 , -}; - -/* - * This array maps the raw hex value to VBUS input current used by the AB8500 - * Values taken from the UM0836 - */ -static int ab8500_charger_vbus_in_curr_map[] = { - USB_CH_IP_CUR_LVL_0P05, - USB_CH_IP_CUR_LVL_0P09, - USB_CH_IP_CUR_LVL_0P19, - USB_CH_IP_CUR_LVL_0P29, - USB_CH_IP_CUR_LVL_0P38, - USB_CH_IP_CUR_LVL_0P45, - USB_CH_IP_CUR_LVL_0P5, - USB_CH_IP_CUR_LVL_0P6, - USB_CH_IP_CUR_LVL_0P7, - USB_CH_IP_CUR_LVL_0P8, - USB_CH_IP_CUR_LVL_0P9, - USB_CH_IP_CUR_LVL_1P0, - USB_CH_IP_CUR_LVL_1P1, - USB_CH_IP_CUR_LVL_1P3, - USB_CH_IP_CUR_LVL_1P4, - USB_CH_IP_CUR_LVL_1P5, -}; - static int ab8500_voltage_to_regval(int voltage) { int i; @@ -1026,41 +1002,41 @@ static int ab8500_voltage_to_regval(int voltage) return -1; } -static int ab8500_current_to_regval(int curr) +static int ab8500_current_to_regval(struct ab8500_charger *di, int curr) { int i; - if (curr < ab8500_charger_current_map[0]) + if (curr < di->bm->chg_output_curr[0]) return 0; - for (i = 0; i < ARRAY_SIZE(ab8500_charger_current_map); i++) { - if (curr < ab8500_charger_current_map[i]) + for (i = 0; i < di->bm->n_chg_out_curr; i++) { + if (curr < di->bm->chg_output_curr[i]) return i - 1; } /* If not last element, return error */ - i = ARRAY_SIZE(ab8500_charger_current_map) - 1; - if (curr == ab8500_charger_current_map[i]) + i = di->bm->n_chg_out_curr - 1; + if (curr == di->bm->chg_output_curr[i]) return i; else return -1; } -static int ab8500_vbus_in_curr_to_regval(int curr) +static int ab8500_vbus_in_curr_to_regval(struct ab8500_charger *di, int curr) { int i; - if (curr < ab8500_charger_vbus_in_curr_map[0]) + if (curr < di->bm->chg_input_curr[0]) return 0; - for (i = 0; i < ARRAY_SIZE(ab8500_charger_vbus_in_curr_map); i++) { - if (curr < ab8500_charger_vbus_in_curr_map[i]) + for (i = 0; i < di->bm->n_chg_in_curr; i++) { + if (curr < di->bm->chg_input_curr[i]) return i - 1; } /* If not last element, return error */ - i = ARRAY_SIZE(ab8500_charger_vbus_in_curr_map) - 1; - if (curr == ab8500_charger_vbus_in_curr_map[i]) + i = di->bm->n_chg_in_curr - 1; + if (curr == di->bm->chg_input_curr[i]) return i; else return -1; @@ -1077,28 +1053,48 @@ static int ab8500_vbus_in_curr_to_regval(int curr) */ static int ab8500_charger_get_usb_cur(struct ab8500_charger *di) { + int ret = 0; switch (di->usb_state.usb_current) { case 100: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P09; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P09; break; case 200: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P19; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P19; break; case 300: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P29; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P29; break; case 400: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P38; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P38; break; case 500: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P5; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P5; break; default: - di->max_usb_in_curr = USB_CH_IP_CUR_LVL_0P05; - return -1; + di->max_usb_in_curr.usb_type_max = USB_CH_IP_CUR_LVL_0P05; + ret = -EPERM; break; }; - return 0; + di->max_usb_in_curr.set_max = di->max_usb_in_curr.usb_type_max; + return ret; +} + +/** + * ab8500_charger_check_continue_stepping() - Check to allow stepping + * @di: pointer to the ab8500_charger structure + * @reg: select what charger register to check + * + * Check if current stepping should be allowed to continue. + * Checks if charger source has not collapsed. If it has, further stepping + * is not allowed. + */ +static bool ab8500_charger_check_continue_stepping(struct ab8500_charger *di, + int reg) +{ + if (reg == AB8500_USBCH_IPT_CRNTLVL_REG) + return !di->flags.vbus_drop_end; + else + return true; } /** @@ -1118,7 +1114,7 @@ static int ab8500_charger_set_current(struct ab8500_charger *di, int ich, int reg) { int ret = 0; - int auto_curr_index, curr_index, prev_curr_index, shift_value, i; + int curr_index, prev_curr_index, shift_value, i; u8 reg_value; u32 step_udelay; bool no_stepping = false; @@ -1136,39 +1132,27 @@ static int ab8500_charger_set_current(struct ab8500_charger *di, case AB8500_MCH_IPT_CURLVL_REG: shift_value = MAIN_CH_INPUT_CURR_SHIFT; prev_curr_index = (reg_value >> shift_value); - curr_index = ab8500_current_to_regval(ich); + curr_index = ab8500_current_to_regval(di, ich); step_udelay = STEP_UDELAY; if (!di->ac.charger_connected) no_stepping = true; break; case AB8500_USBCH_IPT_CRNTLVL_REG: - shift_value = VBUS_IN_CURR_LIM_SHIFT; + if (is_ab8540(di->parent)) + shift_value = AB8540_VBUS_IN_CURR_LIM_SHIFT; + else + shift_value = VBUS_IN_CURR_LIM_SHIFT; prev_curr_index = (reg_value >> shift_value); - curr_index = ab8500_vbus_in_curr_to_regval(ich); + curr_index = ab8500_vbus_in_curr_to_regval(di, ich); step_udelay = STEP_UDELAY * 100; - ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_USBCH_STAT2_REG, ®_value); - if (ret < 0) { - dev_err(di->dev, "%s read failed\n", __func__); - goto exit_set_current; - } - auto_curr_index = - reg_value >> AUTO_VBUS_IN_CURR_LIM_SHIFT; - - dev_dbg(di->dev, "%s Auto VBUS curr is %d mA\n", - __func__, - ab8500_charger_vbus_in_curr_map[auto_curr_index]); - - prev_curr_index = min(prev_curr_index, auto_curr_index); - if (!di->usb.charger_connected) no_stepping = true; break; case AB8500_CH_OPT_CRNTLVL_REG: shift_value = 0; prev_curr_index = (reg_value >> shift_value); - curr_index = ab8500_current_to_regval(ich); + curr_index = ab8500_current_to_regval(di, ich); step_udelay = STEP_UDELAY; if (curr_index && (curr_index - prev_curr_index) > 1) step_udelay *= 100; @@ -1219,7 +1203,8 @@ static int ab8500_charger_set_current(struct ab8500_charger *di, usleep_range(step_udelay, step_udelay * 2); } } else { - for (i = prev_curr_index + 1; i <= curr_index; i++) { + bool allow = true; + for (i = prev_curr_index + 1; i <= curr_index && allow; i++) { dev_dbg(di->dev, "curr change_2 to: %x for 0x%02x\n", (u8)i << shift_value, reg); ret = abx500_set_register_interruptible(di->dev, @@ -1230,6 +1215,8 @@ static int ab8500_charger_set_current(struct ab8500_charger *di, } if (i != curr_index) usleep_range(step_udelay, step_udelay * 2); + + allow = ab8500_charger_check_continue_stepping(di, reg); } } @@ -1255,6 +1242,11 @@ static int ab8500_charger_set_vbus_in_curr(struct ab8500_charger *di, /* We should always use to lowest current limit */ min_value = min(di->bm->chg_params->usb_curr_max, ich_in); + if (di->max_usb_in_curr.set_max > 0) + min_value = min(di->max_usb_in_curr.set_max, min_value); + + if (di->usb_state.usb_current >= 0) + min_value = min(di->usb_state.usb_current, min_value); switch (min_value) { case 100: @@ -1400,8 +1392,8 @@ static int ab8500_charger_ac_en(struct ux500_charger *charger, /* Check if the requested voltage or current is valid */ volt_index = ab8500_voltage_to_regval(vset); - curr_index = ab8500_current_to_regval(iset); - input_curr_index = ab8500_current_to_regval( + curr_index = ab8500_current_to_regval(di, iset); + input_curr_index = ab8500_current_to_regval(di, di->bm->chg_params->ac_curr_max); if (volt_index < 0 || curr_index < 0 || input_curr_index < 0) { dev_err(di->dev, @@ -1572,7 +1564,7 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger, /* Check if the requested voltage or current is valid */ volt_index = ab8500_voltage_to_regval(vset); - curr_index = ab8500_current_to_regval(ich_out); + curr_index = ab8500_current_to_regval(di, ich_out); if (volt_index < 0 || curr_index < 0) { dev_err(di->dev, "Charger voltage or current too high, " @@ -1609,7 +1601,8 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger, di->usb.charger_online = 1; /* USBChInputCurr: current that can be drawn from the usb */ - ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); if (ret) { dev_err(di->dev, "setting USBChInputCurr failed\n"); return ret; @@ -1668,8 +1661,7 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger, dev_dbg(di->dev, "%s Disabled USB charging\n", __func__); /* Cancel any pending Vbat check work */ - if (delayed_work_pending(&di->check_vbat_work)) - cancel_delayed_work(&di->check_vbat_work); + cancel_delayed_work(&di->check_vbat_work); } ab8500_power_supply_changed(di, &di->usb_chg.psy); @@ -1677,6 +1669,128 @@ static int ab8500_charger_usb_en(struct ux500_charger *charger, return ret; } +static int ab8500_external_charger_prepare(struct notifier_block *charger_nb, + unsigned long event, void *data) +{ + int ret; + struct device *dev = data; + /*Toggle External charger control pin*/ + ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK, + AB8500_SYS_CHARGER_CONTROL_REG, + EXTERNAL_CHARGER_DISABLE_REG_VAL); + if (ret < 0) { + dev_err(dev, "write reg failed %d\n", ret); + goto out; + } + ret = abx500_set_register_interruptible(dev, AB8500_SYS_CTRL1_BLOCK, + AB8500_SYS_CHARGER_CONTROL_REG, + EXTERNAL_CHARGER_ENABLE_REG_VAL); + if (ret < 0) + dev_err(dev, "Write reg failed %d\n", ret); + +out: + return ret; +} + +/** + * ab8500_charger_usb_check_enable() - enable usb charging + * @charger: pointer to the ux500_charger structure + * @vset: charging voltage + * @iset: charger output current + * + * Check if the VBUS charger has been disconnected and reconnected without + * AB8500 rising an interrupt. Returns 0 on success. + */ +static int ab8500_charger_usb_check_enable(struct ux500_charger *charger, + int vset, int iset) +{ + u8 usbch_ctrl1 = 0; + int ret = 0; + + struct ab8500_charger *di = to_ab8500_charger_usb_device_info(charger); + + if (!di->usb.charger_connected) + return ret; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_USBCH_CTRL1_REG, &usbch_ctrl1); + if (ret < 0) { + dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); + return ret; + } + dev_dbg(di->dev, "USB charger ctrl: 0x%02x\n", usbch_ctrl1); + + if (!(usbch_ctrl1 & USB_CH_ENA)) { + dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n"); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CHARGER_CTRL, + DROP_COUNT_RESET, DROP_COUNT_RESET); + if (ret < 0) { + dev_err(di->dev, "ab8500 write failed %d\n", __LINE__); + return ret; + } + + ret = ab8500_charger_usb_en(&di->usb_chg, true, vset, iset); + if (ret < 0) { + dev_err(di->dev, "Failed to enable VBUS charger %d\n", + __LINE__); + return ret; + } + } + return ret; +} + +/** + * ab8500_charger_ac_check_enable() - enable usb charging + * @charger: pointer to the ux500_charger structure + * @vset: charging voltage + * @iset: charger output current + * + * Check if the AC charger has been disconnected and reconnected without + * AB8500 rising an interrupt. Returns 0 on success. + */ +static int ab8500_charger_ac_check_enable(struct ux500_charger *charger, + int vset, int iset) +{ + u8 mainch_ctrl1 = 0; + int ret = 0; + + struct ab8500_charger *di = to_ab8500_charger_ac_device_info(charger); + + if (!di->ac.charger_connected) + return ret; + + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_MCH_CTRL1, &mainch_ctrl1); + if (ret < 0) { + dev_err(di->dev, "ab8500 read failed %d\n", __LINE__); + return ret; + } + dev_dbg(di->dev, "AC charger ctrl: 0x%02x\n", mainch_ctrl1); + + if (!(mainch_ctrl1 & MAIN_CH_ENA)) { + dev_info(di->dev, "Charging has been disabled abnormally and will be re-enabled\n"); + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CHARGER_CTRL, + DROP_COUNT_RESET, DROP_COUNT_RESET); + + if (ret < 0) { + dev_err(di->dev, "ab8500 write failed %d\n", __LINE__); + return ret; + } + + ret = ab8500_charger_ac_en(&di->usb_chg, true, vset, iset); + if (ret < 0) { + dev_err(di->dev, "failed to enable AC charger %d\n", + __LINE__); + return ret; + } + } + return ret; +} + /** * ab8500_charger_watchdog_kick() - kick charger watchdog * @di: pointer to the ab8500_charger structure @@ -1734,8 +1848,68 @@ static int ab8500_charger_update_charger_current(struct ux500_charger *charger, /* Reset the main and usb drop input current measurement counter */ ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CHARGER_CTRL, - 0x1); + AB8500_CHARGER_CTRL, DROP_COUNT_RESET); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + return ret; +} + +/** + * ab8540_charger_power_path_enable() - enable usb power path mode + * @charger: pointer to the ux500_charger structure + * @enable: enable/disable flag + * + * Enable or disable the power path for usb mode + * Returns error code in case of failure else 0(on success) + */ +static int ab8540_charger_power_path_enable(struct ux500_charger *charger, + bool enable) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8540_USB_PP_MODE_REG, + BUS_POWER_PATH_MODE_ENA, enable); + if (ret) { + dev_err(di->dev, "%s write failed\n", __func__); + return ret; + } + + return ret; +} + + +/** + * ab8540_charger_usb_pre_chg_enable() - enable usb pre change + * @charger: pointer to the ux500_charger structure + * @enable: enable/disable flag + * + * Enable or disable the pre-chage for usb mode + * Returns error code in case of failure else 0(on success) + */ +static int ab8540_charger_usb_pre_chg_enable(struct ux500_charger *charger, + bool enable) +{ + int ret; + struct ab8500_charger *di; + + if (charger->psy.type == POWER_SUPPLY_TYPE_USB) + di = to_ab8500_charger_usb_device_info(charger); + else + return -ENXIO; + + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8540_USB_PP_CHR_REG, + BUS_POWER_PATH_PRECHG_ENA, enable); if (ret) { dev_err(di->dev, "%s write failed\n", __func__); return ret; @@ -1823,9 +1997,10 @@ static void ab8500_charger_check_vbat_work(struct work_struct *work) di->vbat > VBAT_TRESH_IP_CUR_RED))) { dev_dbg(di->dev, "Vbat did cross threshold, curr: %d, new: %d," - " old: %d\n", di->max_usb_in_curr, di->vbat, - di->old_vbat); - ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + " old: %d\n", di->max_usb_in_curr.usb_type_max, + di->vbat, di->old_vbat); + ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); power_supply_changed(&di->usb_chg.psy); } @@ -2105,7 +2280,8 @@ static void ab8500_charger_usb_link_attach_work(struct work_struct *work) /* Update maximum input current if USB enumeration is not detected */ if (!di->usb.charger_online) { - ret = ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + ret = ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); if (ret) return; } @@ -2125,6 +2301,7 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work) int detected_chargers; int ret; u8 val; + u8 link_status; struct ab8500_charger *di = container_of(work, struct ab8500_charger, usb_link_status_work); @@ -2144,38 +2321,61 @@ static void ab8500_charger_usb_link_status_work(struct work_struct *work) * to start the charging process. but by jumping * thru a few hoops it can be forced to start. */ - ret = abx500_get_register_interruptible(di->dev, AB8500_USB, - AB8500_USB_LINE_STAT_REG, &val); + if (is_ab8500(di->parent)) + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINE_STAT_REG, &val); + else + ret = abx500_get_register_interruptible(di->dev, AB8500_USB, + AB8500_USB_LINK1_STAT_REG, &val); + if (ret >= 0) dev_dbg(di->dev, "UsbLineStatus register = 0x%02x\n", val); else dev_dbg(di->dev, "Error reading USB link status\n"); + if (is_ab8500(di->parent)) + link_status = AB8500_USB_LINK_STATUS; + else + link_status = AB8505_USB_LINK_STATUS; + if (detected_chargers & USB_PW_CONN) { - if (((val & AB8500_USB_LINK_STATUS) >> 3) == USB_STAT_NOT_VALID_LINK && + if (((val & link_status) >> USB_LINK_STATUS_SHIFT) == + USB_STAT_NOT_VALID_LINK && di->invalid_charger_detect_state == 0) { - dev_dbg(di->dev, "Invalid charger detected, state= 0\n"); + dev_dbg(di->dev, + "Invalid charger detected, state= 0\n"); /*Enable charger*/ abx500_mask_and_set_register_interruptible(di->dev, - AB8500_CHARGER, AB8500_USBCH_CTRL1_REG, 0x01, 0x01); + AB8500_CHARGER, AB8500_USBCH_CTRL1_REG, + USB_CH_ENA, USB_CH_ENA); /*Enable charger detection*/ - abx500_mask_and_set_register_interruptible(di->dev, AB8500_USB, - AB8500_MCH_IPT_CURLVL_REG, 0x01, 0x01); + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINE_CTRL2_REG, + USB_CH_DET, USB_CH_DET); di->invalid_charger_detect_state = 1; /*exit and wait for new link status interrupt.*/ return; } if (di->invalid_charger_detect_state == 1) { - dev_dbg(di->dev, "Invalid charger detected, state= 1\n"); + dev_dbg(di->dev, + "Invalid charger detected, state= 1\n"); /*Stop charger detection*/ - abx500_mask_and_set_register_interruptible(di->dev, AB8500_USB, - AB8500_MCH_IPT_CURLVL_REG, 0x01, 0x00); + abx500_mask_and_set_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINE_CTRL2_REG, + USB_CH_DET, 0x00); /*Check link status*/ - ret = abx500_get_register_interruptible(di->dev, AB8500_USB, - AB8500_USB_LINE_STAT_REG, &val); + if (is_ab8500(di->parent)) + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINE_STAT_REG, + &val); + else + ret = abx500_get_register_interruptible(di->dev, + AB8500_USB, AB8500_USB_LINK1_STAT_REG, + &val); + dev_dbg(di->dev, "USB link status= 0x%02x\n", - (val & AB8500_USB_LINK_STATUS) >> 3); + (val & link_status) >> USB_LINK_STATUS_SHIFT); di->invalid_charger_detect_state = 2; } } else { @@ -2273,7 +2473,7 @@ static void ab8500_charger_usb_state_changed_work(struct work_struct *work) if (!ab8500_charger_get_usb_cur(di)) { /* Update maximum input current */ ret = ab8500_charger_set_vbus_in_curr(di, - di->max_usb_in_curr); + di->max_usb_in_curr.usb_type_max); if (ret) return; @@ -2422,7 +2622,9 @@ static irqreturn_t ab8500_charger_mainchplugdet_handler(int irq, void *_di) mutex_lock(&di->charger_attached_mutex); mutex_unlock(&di->charger_attached_mutex); - queue_delayed_work(di->charger_wq, + + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, &di->ac_charger_attached_work, HZ); return IRQ_HANDLED; @@ -2491,6 +2693,8 @@ static void ab8500_charger_vbus_drop_end_work(struct work_struct *work) { struct ab8500_charger *di = container_of(work, struct ab8500_charger, vbus_drop_end_work.work); + int ret, curr; + u8 reg_value; di->flags.vbus_drop_end = false; @@ -2498,8 +2702,45 @@ static void ab8500_charger_vbus_drop_end_work(struct work_struct *work) abx500_set_register_interruptible(di->dev, AB8500_CHARGER, AB8500_CHARGER_CTRL, 0x01); + if (is_ab8540(di->parent)) + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8540_CH_USBCH_STAT3_REG, ®_value); + else + ret = abx500_get_register_interruptible(di->dev, AB8500_CHARGER, + AB8500_CH_USBCH_STAT2_REG, ®_value); + if (ret < 0) { + dev_err(di->dev, "%s read failed\n", __func__); + return; + } + + if (is_ab8540(di->parent)) + curr = di->bm->chg_input_curr[ + reg_value & AB8540_AUTO_VBUS_IN_CURR_MASK]; + else + curr = di->bm->chg_input_curr[ + reg_value >> AUTO_VBUS_IN_CURR_LIM_SHIFT]; + + if (di->max_usb_in_curr.calculated_max != curr) { + /* USB source is collapsing */ + di->max_usb_in_curr.calculated_max = curr; + dev_dbg(di->dev, + "VBUS input current limiting to %d mA\n", + di->max_usb_in_curr.calculated_max); + } else { + /* + * USB source can not give more than this amount. + * Taking more will collapse the source. + */ + di->max_usb_in_curr.set_max = + di->max_usb_in_curr.calculated_max; + dev_dbg(di->dev, + "VBUS input current limited to %d mA\n", + di->max_usb_in_curr.set_max); + } + if (di->usb.charger_connected) - ab8500_charger_set_vbus_in_curr(di, di->max_usb_in_curr); + ab8500_charger_set_vbus_in_curr(di, + di->max_usb_in_curr.usb_type_max); } /** @@ -2654,8 +2895,13 @@ static irqreturn_t ab8500_charger_vbuschdropend_handler(int irq, void *_di) dev_dbg(di->dev, "VBUS charger drop ended\n"); di->flags.vbus_drop_end = true; + + /* + * VBUS might have dropped due to bad connection. + * Schedule a new input limit set to the value SW requests. + */ queue_delayed_work(di->charger_wq, &di->vbus_drop_end_work, - round_jiffies(30 * HZ)); + round_jiffies(VBUS_IN_CURR_LIM_RETRY_SET_TIME * HZ)); return IRQ_HANDLED; } @@ -2836,6 +3082,7 @@ static int ab8500_charger_usb_get_property(struct power_supply *psy, static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) { int ret = 0; + u8 bup_vch_range = 0, vbup33_vrtcn = 0; /* Setup maximum charger current and voltage for ABB cut2.0 */ if (!is_ab8500_1p1_or_earlier(di->parent)) { @@ -2848,9 +3095,14 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) goto out; } - ret = abx500_set_register_interruptible(di->dev, - AB8500_CHARGER, - AB8500_CH_OPT_CRNTLVL_MAX_REG, CH_OP_CUR_LVL_1P6); + if (is_ab8540(di->parent)) + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG, + CH_OP_CUR_LVL_2P); + else + ret = abx500_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8500_CH_OPT_CRNTLVL_MAX_REG, + CH_OP_CUR_LVL_1P6); if (ret) { dev_err(di->dev, "failed to set CH_OPT_CRNTLVL_MAX_REG\n"); @@ -2858,7 +3110,8 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) } } - if (is_ab9540_2p0(di->parent) || is_ab8505_2p0(di->parent)) + if (is_ab9540_2p0(di->parent) || is_ab9540_3p0(di->parent) + || is_ab8505_2p0(di->parent) || is_ab8540(di->parent)) ret = abx500_mask_and_set_register_interruptible(di->dev, AB8500_CHARGER, AB8500_USBCH_CTRL2_REG, @@ -2930,14 +3183,6 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) goto out; } - /* Set charger watchdog timeout */ - ret = abx500_set_register_interruptible(di->dev, AB8500_CHARGER, - AB8500_CH_WD_TIMER_REG, WD_TIMER); - if (ret) { - dev_err(di->dev, "failed to set charger watchdog timeout\n"); - goto out; - } - ret = ab8500_charger_led_en(di, false); if (ret < 0) { dev_err(di->dev, "failed to disable LED\n"); @@ -2945,15 +3190,30 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) } /* Backup battery voltage and current */ + if (di->bm->bkup_bat_v > BUP_VCH_SEL_3P1V) + bup_vch_range = BUP_VCH_RANGE; + if (di->bm->bkup_bat_v == BUP_VCH_SEL_3P3V) + vbup33_vrtcn = VBUP33_VRTCN; + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, AB8500_RTC_BACKUP_CHG_REG, - di->bm->bkup_bat_v | - di->bm->bkup_bat_i); + (di->bm->bkup_bat_v & 0x3) | di->bm->bkup_bat_i); if (ret) { dev_err(di->dev, "failed to setup backup battery charging\n"); goto out; } + if (is_ab8540(di->parent)) { + ret = abx500_set_register_interruptible(di->dev, + AB8500_RTC, + AB8500_RTC_CTRL1_REG, + bup_vch_range | vbup33_vrtcn); + if (ret) { + dev_err(di->dev, + "failed to setup backup battery charging\n"); + goto out; + } + } /* Enable backup battery charging */ abx500_mask_and_set_register_interruptible(di->dev, @@ -2962,6 +3222,25 @@ static int ab8500_charger_init_hw_registers(struct ab8500_charger *di) if (ret < 0) dev_err(di->dev, "%s mask and set failed\n", __func__); + if (is_ab8540(di->parent)) { + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8540_USB_PP_MODE_REG, + BUS_VSYS_VOL_SELECT_MASK, BUS_VSYS_VOL_SELECT_3P6V); + if (ret) { + dev_err(di->dev, + "failed to setup usb power path vsys voltage\n"); + goto out; + } + ret = abx500_mask_and_set_register_interruptible(di->dev, + AB8500_CHARGER, AB8540_USB_PP_CHR_REG, + BUS_PP_PRECHG_CURRENT_MASK, 0); + if (ret) { + dev_err(di->dev, + "failed to setup usb power path prechage current\n"); + goto out; + } + } + out: return ret; } @@ -3055,11 +3334,8 @@ static int ab8500_charger_resume(struct platform_device *pdev) dev_err(di->dev, "Failed to kick WD!\n"); /* If not already pending start a new timer */ - if (!delayed_work_pending( - &di->kick_wd_work)) { - queue_delayed_work(di->charger_wq, &di->kick_wd_work, - round_jiffies(WD_KICK_INTERVAL)); - } + queue_delayed_work(di->charger_wq, &di->kick_wd_work, + round_jiffies(WD_KICK_INTERVAL)); } /* If we still have a HW failure, schedule a new check */ @@ -3079,12 +3355,9 @@ static int ab8500_charger_suspend(struct platform_device *pdev, { struct ab8500_charger *di = platform_get_drvdata(pdev); - /* Cancel any pending HW failure check */ - if (delayed_work_pending(&di->check_hw_failure_work)) - cancel_delayed_work(&di->check_hw_failure_work); - - if (delayed_work_pending(&di->vbus_drop_end_work)) - cancel_delayed_work(&di->vbus_drop_end_work); + /* Cancel any pending jobs */ + cancel_delayed_work(&di->check_hw_failure_work); + cancel_delayed_work(&di->vbus_drop_end_work); flush_delayed_work(&di->attach_work); flush_delayed_work(&di->usb_charger_attached_work); @@ -3107,6 +3380,10 @@ static int ab8500_charger_suspend(struct platform_device *pdev, #define ab8500_charger_resume NULL #endif +static struct notifier_block charger_nb = { + .notifier_call = ab8500_external_charger_prepare, +}; + static int ab8500_charger_remove(struct platform_device *pdev) { struct ab8500_charger *di = platform_get_drvdata(pdev); @@ -3136,13 +3413,18 @@ static int ab8500_charger_remove(struct platform_device *pdev) /* Delete the work queue */ destroy_workqueue(di->charger_wq); + /* Unregister external charger enable notifier */ + if (!di->ac_chg.enabled) + blocking_notifier_chain_unregister( + &charger_notifier_list, &charger_nb); + flush_scheduled_work(); - if(di->usb_chg.enabled) + if (di->usb_chg.enabled) power_supply_unregister(&di->usb_chg.psy); -#if !defined(CONFIG_CHARGER_PM2301) - if(di->ac_chg.enabled) + + if (di->ac_chg.enabled && !di->ac_chg.external) power_supply_unregister(&di->ac_chg.psy); -#endif + platform_set_drvdata(pdev, NULL); return 0; @@ -3206,16 +3488,22 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->ac_chg.psy.num_supplicants = ARRAY_SIZE(supply_interface), /* ux500_charger sub-class */ di->ac_chg.ops.enable = &ab8500_charger_ac_en; + di->ac_chg.ops.check_enable = &ab8500_charger_ac_check_enable; di->ac_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; di->ac_chg.ops.update_curr = &ab8500_charger_update_charger_current; di->ac_chg.max_out_volt = ab8500_charger_voltage_map[ ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; - di->ac_chg.max_out_curr = ab8500_charger_current_map[ - ARRAY_SIZE(ab8500_charger_current_map) - 1]; + di->ac_chg.max_out_curr = + di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1]; di->ac_chg.wdt_refresh = CHG_WD_INTERVAL; di->ac_chg.enabled = di->bm->ac_enabled; di->ac_chg.external = false; + /*notifier for external charger enabling*/ + if (!di->ac_chg.enabled) + blocking_notifier_chain_register( + &charger_notifier_list, &charger_nb); + /* USB supply */ /* power_supply base class */ di->usb_chg.psy.name = "ab8500_usb"; @@ -3227,15 +3515,20 @@ static int ab8500_charger_probe(struct platform_device *pdev) di->usb_chg.psy.num_supplicants = ARRAY_SIZE(supply_interface), /* ux500_charger sub-class */ di->usb_chg.ops.enable = &ab8500_charger_usb_en; + di->usb_chg.ops.check_enable = &ab8500_charger_usb_check_enable; di->usb_chg.ops.kick_wd = &ab8500_charger_watchdog_kick; di->usb_chg.ops.update_curr = &ab8500_charger_update_charger_current; + di->usb_chg.ops.pp_enable = &ab8540_charger_power_path_enable; + di->usb_chg.ops.pre_chg_enable = &ab8540_charger_usb_pre_chg_enable; di->usb_chg.max_out_volt = ab8500_charger_voltage_map[ ARRAY_SIZE(ab8500_charger_voltage_map) - 1]; - di->usb_chg.max_out_curr = ab8500_charger_current_map[ - ARRAY_SIZE(ab8500_charger_current_map) - 1]; + di->usb_chg.max_out_curr = + di->bm->chg_output_curr[di->bm->n_chg_out_curr - 1]; di->usb_chg.wdt_refresh = CHG_WD_INTERVAL; di->usb_chg.enabled = di->bm->usb_enabled; di->usb_chg.external = false; + di->usb_chg.power_path = di->bm->usb_power_path; + di->usb_state.usb_current = -1; /* Create a work queue for the charger */ di->charger_wq = @@ -3316,7 +3609,7 @@ static int ab8500_charger_probe(struct platform_device *pdev) } /* Register AC charger class */ - if(di->ac_chg.enabled) { + if (di->ac_chg.enabled) { ret = power_supply_register(di->dev, &di->ac_chg.psy); if (ret) { dev_err(di->dev, "failed to register AC charger\n"); @@ -3325,7 +3618,7 @@ static int ab8500_charger_probe(struct platform_device *pdev) } /* Register USB charger class */ - if(di->usb_chg.enabled) { + if (di->usb_chg.enabled) { ret = power_supply_register(di->dev, &di->usb_chg.psy); if (ret) { dev_err(di->dev, "failed to register USB charger\n"); @@ -3385,14 +3678,16 @@ static int ab8500_charger_probe(struct platform_device *pdev) ch_stat = ab8500_charger_detect_chargers(di, false); if ((ch_stat & AC_PW_CONN) == AC_PW_CONN) { - queue_delayed_work(di->charger_wq, - &di->ac_charger_attached_work, - HZ); + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->ac_charger_attached_work, + HZ); } if ((ch_stat & USB_PW_CONN) == USB_PW_CONN) { - queue_delayed_work(di->charger_wq, - &di->usb_charger_attached_work, - HZ); + if (is_ab8500(di->parent)) + queue_delayed_work(di->charger_wq, + &di->usb_charger_attached_work, + HZ); } mutex_unlock(&di->charger_attached_mutex); @@ -3410,10 +3705,10 @@ free_irq: put_usb_phy: usb_put_phy(di->usb_phy); free_usb: - if(di->usb_chg.enabled) + if (di->usb_chg.enabled) power_supply_unregister(&di->usb_chg.psy); free_ac: - if(di->ac_chg.enabled) + if (di->ac_chg.enabled) power_supply_unregister(&di->ac_chg.psy); free_charger_wq: destroy_workqueue(di->charger_wq); diff --git a/drivers/power/ab8500_fg.c b/drivers/power/ab8500_fg.c index 25dae4c4b0e..c5391f5c372 100644 --- a/drivers/power/ab8500_fg.c +++ b/drivers/power/ab8500_fg.c @@ -36,7 +36,7 @@ #define MILLI_TO_MICRO 1000 #define FG_LSB_IN_MA 1627 -#define QLSB_NANO_AMP_HOURS_X10 1129 +#define QLSB_NANO_AMP_HOURS_X10 1071 #define INS_CURR_TIMEOUT (3 * HZ) #define SEC_TO_SAMPLE(S) (S * 4) @@ -672,11 +672,11 @@ int ab8500_fg_inst_curr_finalize(struct ab8500_fg *di, int *res) /* * Convert to unit value in mA * Full scale input voltage is - * 66.660mV => LSB = 66.660mV/(4096*res) = 1.627mA + * 63.160mV => LSB = 63.160mV/(4096*res) = 1.542mA * Given a 250ms conversion cycle time the LSB corresponds - * to 112.9 nAh. Convert to current by dividing by the conversion + * to 107.1 nAh. Convert to current by dividing by the conversion * time in hours (250ms = 1 / (3600 * 4)h) - * 112.9nAh assumes 10mOhm, but fg_res is in 0.1mOhm + * 107.1nAh assumes 10mOhm, but fg_res is in 0.1mOhm */ val = (val * QLSB_NANO_AMP_HOURS_X10 * 36 * 4) / (1000 * di->bm->fg_res); @@ -863,7 +863,7 @@ static int ab8500_fg_bat_voltage(struct ab8500_fg *di) static int ab8500_fg_volt_to_capacity(struct ab8500_fg *di, int voltage) { int i, tbl_size; - struct abx500_v_to_cap *tbl; + const struct abx500_v_to_cap *tbl; int cap = 0; tbl = di->bm->bat_type[di->bm->batt_id].v_to_cap_tbl, @@ -915,7 +915,7 @@ static int ab8500_fg_uncomp_volt_to_capacity(struct ab8500_fg *di) static int ab8500_fg_battery_resistance(struct ab8500_fg *di) { int i, tbl_size; - struct batres_vs_temp *tbl; + const struct batres_vs_temp *tbl; int resist = 0; tbl = di->bm->bat_type[di->bm->batt_id].batres_tbl; @@ -1354,9 +1354,6 @@ static void ab8500_fg_check_capacity_limits(struct ab8500_fg *di, bool init) * algorithm says. */ di->bat_cap.prev_percent = 1; - di->bat_cap.permille = 1; - di->bat_cap.prev_mah = 1; - di->bat_cap.mah = 1; percent = 1; changed = true; @@ -1683,7 +1680,6 @@ static void ab8500_fg_algorithm_discharging(struct ab8500_fg *di) break; case AB8500_FG_DISCHARGE_WAKEUP: - ab8500_fg_coulomb_counter(di, true); ab8500_fg_calc_cap_discharge_voltage(di, true); di->fg_samples = SEC_TO_SAMPLE( @@ -1768,9 +1764,10 @@ static void ab8500_fg_algorithm(struct ab8500_fg *di) ab8500_fg_algorithm_discharging(di); } - dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d " + dev_dbg(di->dev, "[FG_DATA] %d %d %d %d %d %d %d %d %d %d " "%d %d %d %d %d %d %d\n", di->bat_cap.max_mah_design, + di->bat_cap.max_mah, di->bat_cap.mah, di->bat_cap.permille, di->bat_cap.level, @@ -1982,7 +1979,7 @@ static void ab8500_fg_instant_work(struct work_struct *work) } /** - * ab8500_fg_cc_data_end_handler() - isr to get battery avg current. + * ab8500_fg_cc_data_end_handler() - end of data conversion isr. * @irq: interrupt number * @_di: pointer to the ab8500_fg structure * @@ -2002,7 +1999,7 @@ static irqreturn_t ab8500_fg_cc_data_end_handler(int irq, void *_di) } /** - * ab8500_fg_cc_convend_handler() - isr to get battery avg current. + * ab8500_fg_cc_int_calib_handler () - end of calibration isr. * @irq: interrupt number * @_di: pointer to the ab8500_fg structure * @@ -2153,9 +2150,7 @@ static int ab8500_fg_get_property(struct power_supply *psy, val->intval = di->bat_cap.prev_mah; break; case POWER_SUPPLY_PROP_CAPACITY: - if (di->bm->capacity_scaling) - val->intval = di->bat_cap.cap_scale.scaled_cap; - else if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && + if (di->flags.batt_unknown && !di->bm->chg_unknown_bat && di->flags.batt_id_received) val->intval = 100; else @@ -2344,6 +2339,50 @@ static int ab8500_fg_init_hw_registers(struct ab8500_fg *di) dev_err(di->dev, "BattOk init write failed.\n"); goto out; } + + if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(di->dev) >= AB8500_CUT2P0) + || is_ab8540(di->parent)) { + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, di->bm->fg_params->pcut_max_time); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_MAX_TIME_REG\n", __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, di->bm->fg_params->pcut_flag_time); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_FLAG_TIME_REG\n", __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, di->bm->fg_params->pcut_max_restart); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_RESTART_REG\n", __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, di->bm->fg_params->pcut_debounce_time); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_DEBOUNCE_REG\n", __func__); + goto out; + }; + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, di->bm->fg_params->pcut_enable); + + if (ret) { + dev_err(di->dev, "%s write failed AB8505_RTC_PCUT_CTL_STATUS_REG\n", __func__); + goto out; + }; + } out: return ret; } @@ -2546,6 +2585,428 @@ static int ab8500_fg_sysfs_init(struct ab8500_fg *di) return ret; } + +static ssize_t ab8505_powercut_flagtime_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_FLAG_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_flagtime_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + long unsigned reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + + if (reg_value > 0x7F) { + dev_err(dev, "Incorrect parameter, echo 0 (1.98s) - 127 (15.625ms) for flagtime\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_FLAG_TIME_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_FLAG_TIME_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_maxtime_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_MAX_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; + +} + +static ssize_t ab8505_powercut_maxtime_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + if (reg_value > 0x7F) { + dev_err(dev, "Incorrect parameter, echo 0 (0.0s) - 127 (1.98s) for maxtime\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_MAX_TIME_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_MAX_TIME_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_restart_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_restart_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + if (reg_value > 0xF) { + dev_err(dev, "Incorrect parameter, echo 0 - 15 for number of restart\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_RESTART_REG\n"); + +fail: + return count; + +} + +static ssize_t ab8505_powercut_timer_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_TIME_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_TIME_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7F)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_restart_counter_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_RESTART_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_RESTART_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0xF0) >> 4); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) + goto fail; + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x1)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + if (reg_value > 0x1) { + dev_err(dev, "Incorrect parameter, echo 0/1 to disable/enable Pcut feature\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_flag_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x10) >> 4)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_debounce_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_DEBOUNCE_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", (reg_value & 0x7)); + +fail: + return ret; +} + +static ssize_t ab8505_powercut_debounce_write(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int ret; + int reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + reg_value = simple_strtoul(buf, NULL, 10); + if (reg_value > 0x7) { + dev_err(dev, "Incorrect parameter, echo 0 to 7 for debounce setting\n"); + goto fail; + } + + ret = abx500_set_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_DEBOUNCE_REG, (u8)reg_value); + + if (ret < 0) + dev_err(dev, "Failed to set AB8505_RTC_PCUT_DEBOUNCE_REG\n"); + +fail: + return count; +} + +static ssize_t ab8505_powercut_enable_status_read(struct device *dev, + struct device_attribute *attr, + char *buf) +{ + int ret; + u8 reg_value; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + ret = abx500_get_register_interruptible(di->dev, AB8500_RTC, + AB8505_RTC_PCUT_CTL_STATUS_REG, ®_value); + + if (ret < 0) { + dev_err(dev, "Failed to read AB8505_RTC_PCUT_CTL_STATUS_REG\n"); + goto fail; + } + + return scnprintf(buf, PAGE_SIZE, "%d\n", ((reg_value & 0x20) >> 5)); + +fail: + return ret; +} + +static struct device_attribute ab8505_fg_sysfs_psy_attrs[] = { + __ATTR(powercut_flagtime, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_flagtime_read, ab8505_powercut_flagtime_write), + __ATTR(powercut_maxtime, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_maxtime_read, ab8505_powercut_maxtime_write), + __ATTR(powercut_restart_max, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_restart_read, ab8505_powercut_restart_write), + __ATTR(powercut_timer, S_IRUGO, ab8505_powercut_timer_read, NULL), + __ATTR(powercut_restart_counter, S_IRUGO, + ab8505_powercut_restart_counter_read, NULL), + __ATTR(powercut_enable, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_read, ab8505_powercut_write), + __ATTR(powercut_flag, S_IRUGO, ab8505_powercut_flag_read, NULL), + __ATTR(powercut_debounce_time, (S_IRUGO | S_IWUSR | S_IWGRP), + ab8505_powercut_debounce_read, ab8505_powercut_debounce_write), + __ATTR(powercut_enable_status, S_IRUGO, + ab8505_powercut_enable_status_read, NULL), +}; + +static int ab8500_fg_sysfs_psy_create_attrs(struct device *dev) +{ + unsigned int i, j; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0) + || is_ab8540(di->parent)) { + for (j = 0; j < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); j++) + if (device_create_file(dev, &ab8505_fg_sysfs_psy_attrs[j])) + goto sysfs_psy_create_attrs_failed_ab8505; + } + return 0; +sysfs_psy_create_attrs_failed_ab8505: + dev_err(dev, "Failed creating sysfs psy attrs for ab8505.\n"); + while (j--) + device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]); + + return -EIO; +} + +static void ab8500_fg_sysfs_psy_remove_attrs(struct device *dev) +{ + unsigned int i; + struct power_supply *psy = dev_get_drvdata(dev); + struct ab8500_fg *di; + + di = to_ab8500_fg_device_info(psy); + + if (((is_ab8505(di->parent) || is_ab9540(di->parent)) && + abx500_get_chip_id(dev->parent) >= AB8500_CUT2P0) + || is_ab8540(di->parent)) { + for (i = 0; i < ARRAY_SIZE(ab8505_fg_sysfs_psy_attrs); i++) + (void)device_remove_file(dev, &ab8505_fg_sysfs_psy_attrs[i]); + } +} + /* Exposure to the sysfs interface <<END>> */ #if defined(CONFIG_PM) @@ -2607,6 +3068,7 @@ static int ab8500_fg_remove(struct platform_device *pdev) ab8500_fg_sysfs_exit(di); flush_scheduled_work(); + ab8500_fg_sysfs_psy_remove_attrs(di->fg_psy.dev); power_supply_unregister(&di->fg_psy); platform_set_drvdata(pdev, NULL); return ret; @@ -2772,6 +3234,13 @@ static int ab8500_fg_probe(struct platform_device *pdev) goto free_irq; } + ret = ab8500_fg_sysfs_psy_create_attrs(di->fg_psy.dev); + if (ret) { + dev_err(di->dev, "failed to create FG psy\n"); + ab8500_fg_sysfs_exit(di); + goto free_irq; + } + /* Calibrate the fg first time */ di->flags.calibrate = true; di->calib_state = AB8500_FG_CALIB_INIT; diff --git a/drivers/power/abx500_chargalg.c b/drivers/power/abx500_chargalg.c index f043c0851a7..9863e423602 100644 --- a/drivers/power/abx500_chargalg.c +++ b/drivers/power/abx500_chargalg.c @@ -1,5 +1,6 @@ /* * Copyright (C) ST-Ericsson SA 2012 + * Copyright (c) 2012 Sony Mobile Communications AB * * Charging algorithm driver for abx500 variants * @@ -8,11 +9,13 @@ * Johan Palsson <johan.palsson@stericsson.com> * Karl Komierowski <karl.komierowski@stericsson.com> * Arun R Murthy <arun.murthy@stericsson.com> + * Author: Imre Sunyi <imre.sunyi@sonymobile.com> */ #include <linux/init.h> #include <linux/module.h> #include <linux/device.h> +#include <linux/hrtimer.h> #include <linux/interrupt.h> #include <linux/delay.h> #include <linux/slab.h> @@ -24,8 +27,10 @@ #include <linux/of.h> #include <linux/mfd/core.h> #include <linux/mfd/abx500.h> +#include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/abx500/ux500_chargalg.h> #include <linux/mfd/abx500/ab8500-bm.h> +#include <linux/notifier.h> /* Watchdog kick interval */ #define CHG_WD_INTERVAL (6 * HZ) @@ -33,6 +38,18 @@ /* End-of-charge criteria counter */ #define EOC_COND_CNT 10 +/* One hour expressed in seconds */ +#define ONE_HOUR_IN_SECONDS 3600 + +/* Five minutes expressed in seconds */ +#define FIVE_MINUTES_IN_SECONDS 300 + +/* Plus margin for the low battery threshold */ +#define BAT_PLUS_MARGIN (100) + +#define CHARGALG_CURR_STEP_LOW 0 +#define CHARGALG_CURR_STEP_HIGH 100 + #define to_abx500_chargalg_device_info(x) container_of((x), \ struct abx500_chargalg, chargalg_psy); @@ -66,6 +83,11 @@ struct abx500_chargalg_suspension_status { bool usb_suspended; }; +struct abx500_chargalg_current_step_status { + bool curr_step_change; + int curr_step; +}; + struct abx500_chargalg_battery_data { int temp; int volt; @@ -82,6 +104,7 @@ enum abx500_chargalg_states { STATE_HW_TEMP_PROTECT_INIT, STATE_HW_TEMP_PROTECT, STATE_NORMAL_INIT, + STATE_USB_PP_PRE_CHARGE, STATE_NORMAL, STATE_WAIT_FOR_RECHARGE_INIT, STATE_WAIT_FOR_RECHARGE, @@ -113,6 +136,7 @@ static const char *states[] = { "HW_TEMP_PROTECT_INIT", "HW_TEMP_PROTECT", "NORMAL_INIT", + "USB_PP_PRE_CHARGE", "NORMAL", "WAIT_FOR_RECHARGE_INIT", "WAIT_FOR_RECHARGE", @@ -204,6 +228,8 @@ enum maxim_ret { * @batt_data: data of the battery * @susp_status: current charger suspension status * @bm: Platform specific battery management information + * @curr_status: Current step status for over-current protection + * @parent: pointer to the struct abx500 * @chargalg_psy: structure that holds the battery properties exposed by * the charging algorithm * @events: structure for information about events triggered @@ -227,6 +253,8 @@ struct abx500_chargalg { struct abx500_chargalg_charger_info chg_info; struct abx500_chargalg_battery_data batt_data; struct abx500_chargalg_suspension_status susp_status; + struct ab8500 *parent; + struct abx500_chargalg_current_step_status curr_status; struct abx500_bm_data *bm; struct power_supply chargalg_psy; struct ux500_charger *ac_chg; @@ -236,51 +264,69 @@ struct abx500_chargalg { struct delayed_work chargalg_periodic_work; struct delayed_work chargalg_wd_work; struct work_struct chargalg_work; - struct timer_list safety_timer; - struct timer_list maintenance_timer; + struct hrtimer safety_timer; + struct hrtimer maintenance_timer; struct kobject chargalg_kobject; }; +/*External charger prepare notifier*/ +BLOCKING_NOTIFIER_HEAD(charger_notifier_list); + /* Main battery properties */ static enum power_supply_property abx500_chargalg_props[] = { POWER_SUPPLY_PROP_STATUS, POWER_SUPPLY_PROP_HEALTH, }; +struct abx500_chargalg_sysfs_entry { + struct attribute attr; + ssize_t (*show)(struct abx500_chargalg *, char *); + ssize_t (*store)(struct abx500_chargalg *, const char *, size_t); +}; + /** * abx500_chargalg_safety_timer_expired() - Expiration of the safety timer - * @data: pointer to the abx500_chargalg structure + * @timer: pointer to the hrtimer structure * * This function gets called when the safety timer for the charger * expires */ -static void abx500_chargalg_safety_timer_expired(unsigned long data) +static enum hrtimer_restart +abx500_chargalg_safety_timer_expired(struct hrtimer *timer) { - struct abx500_chargalg *di = (struct abx500_chargalg *) data; + struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg, + safety_timer); dev_err(di->dev, "Safety timer expired\n"); di->events.safety_timer_expired = true; /* Trigger execution of the algorithm instantly */ queue_work(di->chargalg_wq, &di->chargalg_work); + + return HRTIMER_NORESTART; } /** * abx500_chargalg_maintenance_timer_expired() - Expiration of * the maintenance timer - * @i: pointer to the abx500_chargalg structure + * @timer: pointer to the timer structure * * This function gets called when the maintenence timer * expires */ -static void abx500_chargalg_maintenance_timer_expired(unsigned long data) +static enum hrtimer_restart +abx500_chargalg_maintenance_timer_expired(struct hrtimer *timer) { - struct abx500_chargalg *di = (struct abx500_chargalg *) data; + struct abx500_chargalg *di = container_of(timer, struct abx500_chargalg, + maintenance_timer); + dev_dbg(di->dev, "Maintenance timer expired\n"); di->events.maintenance_timer_expired = true; /* Trigger execution of the algorithm instantly */ queue_work(di->chargalg_wq, &di->chargalg_work); + + return HRTIMER_NORESTART; } /** @@ -303,6 +349,30 @@ static void abx500_chargalg_state_to(struct abx500_chargalg *di, di->charge_state = state; } +static int abx500_chargalg_check_charger_enable(struct abx500_chargalg *di) +{ + switch (di->charge_state) { + case STATE_NORMAL: + case STATE_MAINTENANCE_A: + case STATE_MAINTENANCE_B: + break; + default: + return 0; + } + + if (di->chg_info.charger_type & USB_CHG) { + return di->usb_chg->ops.check_enable(di->usb_chg, + di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, + di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); + } else if ((di->chg_info.charger_type & AC_CHG) && + !(di->ac_chg->external)) { + return di->ac_chg->ops.check_enable(di->ac_chg, + di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, + di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); + } + return 0; +} + /** * abx500_chargalg_check_charger_connection() - Check charger connection change * @di: pointer to the abx500_chargalg structure @@ -348,6 +418,22 @@ static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di) } /** + * abx500_chargalg_check_current_step_status() - Check charging current + * step status. + * @di: pointer to the abx500_chargalg structure + * + * This function will check if there is a change in the charging current step + * and change charge state accordingly. + */ +static void abx500_chargalg_check_current_step_status + (struct abx500_chargalg *di) +{ + if (di->curr_status.curr_step_change) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + di->curr_status.curr_step_change = false; +} + +/** * abx500_chargalg_start_safety_timer() - Start charging safety timer * @di: pointer to the abx500_chargalg structure * @@ -356,19 +442,16 @@ static int abx500_chargalg_check_charger_connection(struct abx500_chargalg *di) */ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) { - unsigned long timer_expiration = 0; + /* Charger-dependent expiration time in hours*/ + int timer_expiration = 0; switch (di->chg_info.charger_type) { case AC_CHG: - timer_expiration = - round_jiffies(jiffies + - (di->bm->main_safety_tmr_h * 3600 * HZ)); + timer_expiration = di->bm->main_safety_tmr_h; break; case USB_CHG: - timer_expiration = - round_jiffies(jiffies + - (di->bm->usb_safety_tmr_h * 3600 * HZ)); + timer_expiration = di->bm->usb_safety_tmr_h; break; default: @@ -377,11 +460,10 @@ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) } di->events.safety_timer_expired = false; - di->safety_timer.expires = timer_expiration; - if (!timer_pending(&di->safety_timer)) - add_timer(&di->safety_timer); - else - mod_timer(&di->safety_timer, timer_expiration); + hrtimer_set_expires_range(&di->safety_timer, + ktime_set(timer_expiration * ONE_HOUR_IN_SECONDS, 0), + ktime_set(FIVE_MINUTES_IN_SECONDS, 0)); + hrtimer_start_expires(&di->safety_timer, HRTIMER_MODE_REL); } /** @@ -392,8 +474,8 @@ static void abx500_chargalg_start_safety_timer(struct abx500_chargalg *di) */ static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di) { - di->events.safety_timer_expired = false; - del_timer(&di->safety_timer); + if (hrtimer_try_to_cancel(&di->safety_timer) >= 0) + di->events.safety_timer_expired = false; } /** @@ -408,17 +490,11 @@ static void abx500_chargalg_stop_safety_timer(struct abx500_chargalg *di) static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di, int duration) { - unsigned long timer_expiration; - - /* Convert from hours to jiffies */ - timer_expiration = round_jiffies(jiffies + (duration * 3600 * HZ)); - + hrtimer_set_expires_range(&di->maintenance_timer, + ktime_set(duration * ONE_HOUR_IN_SECONDS, 0), + ktime_set(FIVE_MINUTES_IN_SECONDS, 0)); di->events.maintenance_timer_expired = false; - di->maintenance_timer.expires = timer_expiration; - if (!timer_pending(&di->maintenance_timer)) - add_timer(&di->maintenance_timer); - else - mod_timer(&di->maintenance_timer, timer_expiration); + hrtimer_start_expires(&di->maintenance_timer, HRTIMER_MODE_REL); } /** @@ -430,8 +506,8 @@ static void abx500_chargalg_start_maintenance_timer(struct abx500_chargalg *di, */ static void abx500_chargalg_stop_maintenance_timer(struct abx500_chargalg *di) { - di->events.maintenance_timer_expired = false; - del_timer(&di->maintenance_timer); + if (hrtimer_try_to_cancel(&di->maintenance_timer) >= 0) + di->events.maintenance_timer_expired = false; } /** @@ -477,6 +553,8 @@ static int abx500_chargalg_kick_watchdog(struct abx500_chargalg *di) static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable, int vset, int iset) { + static int abx500_chargalg_ex_ac_enable_toggle; + if (!di->ac_chg || !di->ac_chg->ops.enable) return -ENXIO; @@ -489,6 +567,14 @@ static int abx500_chargalg_ac_en(struct abx500_chargalg *di, int enable, di->chg_info.ac_iset = iset; di->chg_info.ac_vset = vset; + /* Enable external charger */ + if (enable && di->ac_chg->external && + !abx500_chargalg_ex_ac_enable_toggle) { + blocking_notifier_call_chain(&charger_notifier_list, + 0, di->dev); + abx500_chargalg_ex_ac_enable_toggle++; + } + return di->ac_chg->ops.enable(di->ac_chg, enable, vset, iset); } @@ -520,6 +606,37 @@ static int abx500_chargalg_usb_en(struct abx500_chargalg *di, int enable, return di->usb_chg->ops.enable(di->usb_chg, enable, vset, iset); } + /** + * ab8540_chargalg_usb_pp_en() - Enable/ disable USB power path + * @di: pointer to the abx500_chargalg structure + * @enable: power path enable/disable + * + * The USB power path will be enable/ disable + */ +static int ab8540_chargalg_usb_pp_en(struct abx500_chargalg *di, bool enable) +{ + if (!di->usb_chg || !di->usb_chg->ops.pp_enable) + return -ENXIO; + + return di->usb_chg->ops.pp_enable(di->usb_chg, enable); +} + +/** + * ab8540_chargalg_usb_pre_chg_en() - Enable/ disable USB pre-charge + * @di: pointer to the abx500_chargalg structure + * @enable: USB pre-charge enable/disable + * + * The USB USB pre-charge will be enable/ disable + */ +static int ab8540_chargalg_usb_pre_chg_en(struct abx500_chargalg *di, + bool enable) +{ + if (!di->usb_chg || !di->usb_chg->ops.pre_chg_enable) + return -ENXIO; + + return di->usb_chg->ops.pre_chg_enable(di->usb_chg, enable); +} + /** * abx500_chargalg_update_chg_curr() - Update charger current * @di: pointer to the abx500_chargalg structure @@ -613,8 +730,6 @@ static void abx500_chargalg_hold_charging(struct abx500_chargalg *di) static void abx500_chargalg_start_charging(struct abx500_chargalg *di, int vset, int iset) { - bool start_chargalg_wd = true; - switch (di->chg_info.charger_type) { case AC_CHG: dev_dbg(di->dev, @@ -632,12 +747,8 @@ static void abx500_chargalg_start_charging(struct abx500_chargalg *di, default: dev_err(di->dev, "Unknown charger to charge from\n"); - start_chargalg_wd = false; break; } - - if (start_chargalg_wd && !delayed_work_pending(&di->chargalg_wd_work)) - queue_delayed_work(di->chargalg_wq, &di->chargalg_wd_work, 0); } /** @@ -725,6 +836,9 @@ static void abx500_chargalg_end_of_charge(struct abx500_chargalg *di) di->batt_data.avg_curr > 0) { if (++di->eoc_cnt >= EOC_COND_CNT) { di->eoc_cnt = 0; + if ((di->chg_info.charger_type & USB_CHG) && + (di->usb_chg->power_path)) + ab8540_chargalg_usb_pp_en(di, true); di->charge_status = POWER_SUPPLY_STATUS_FULL; di->maintenance_chg = true; dev_dbg(di->dev, "EOC reached!\n"); @@ -1217,6 +1331,8 @@ static void abx500_chargalg_external_power_changed(struct power_supply *psy) static void abx500_chargalg_algorithm(struct abx500_chargalg *di) { int charger_status; + int ret; + int curr_step_lvl; /* Collect data from all power_supply class devices */ class_for_each_device(power_supply_class, NULL, @@ -1227,6 +1343,15 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di) abx500_chargalg_check_charger_voltage(di); charger_status = abx500_chargalg_check_charger_connection(di); + abx500_chargalg_check_current_step_status(di); + + if (is_ab8500(di->parent)) { + ret = abx500_chargalg_check_charger_enable(di); + if (ret < 0) + dev_err(di->dev, "Checking charger is enabled error" + ": Returned Value %d\n", ret); + } + /* * First check if we have a charger connected. * Also we don't allow charging of unknown batteries if configured @@ -1416,9 +1541,34 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di) break; case STATE_NORMAL_INIT: - abx500_chargalg_start_charging(di, - di->bm->bat_type[di->bm->batt_id].normal_vol_lvl, - di->bm->bat_type[di->bm->batt_id].normal_cur_lvl); + if ((di->chg_info.charger_type & USB_CHG) && + di->usb_chg->power_path) { + if (di->batt_data.volt > + (di->bm->fg_params->lowbat_threshold + + BAT_PLUS_MARGIN)) { + ab8540_chargalg_usb_pre_chg_en(di, false); + ab8540_chargalg_usb_pp_en(di, false); + } else { + ab8540_chargalg_usb_pp_en(di, true); + ab8540_chargalg_usb_pre_chg_en(di, true); + abx500_chargalg_state_to(di, + STATE_USB_PP_PRE_CHARGE); + break; + } + } + + if (di->curr_status.curr_step == CHARGALG_CURR_STEP_LOW) + abx500_chargalg_stop_charging(di); + else { + curr_step_lvl = di->bm->bat_type[ + di->bm->batt_id].normal_cur_lvl + * di->curr_status.curr_step + / CHARGALG_CURR_STEP_HIGH; + abx500_chargalg_start_charging(di, + di->bm->bat_type[di->bm->batt_id] + .normal_vol_lvl, curr_step_lvl); + } + abx500_chargalg_state_to(di, STATE_NORMAL); abx500_chargalg_start_safety_timer(di); abx500_chargalg_stop_maintenance_timer(di); @@ -1430,6 +1580,13 @@ static void abx500_chargalg_algorithm(struct abx500_chargalg *di) break; + case STATE_USB_PP_PRE_CHARGE: + if (di->batt_data.volt > + (di->bm->fg_params->lowbat_threshold + + BAT_PLUS_MARGIN)) + abx500_chargalg_state_to(di, STATE_NORMAL_INIT); + break; + case STATE_NORMAL: handle_maxim_chg_curr(di); if (di->charge_status == POWER_SUPPLY_STATUS_FULL && @@ -1653,99 +1810,134 @@ static int abx500_chargalg_get_property(struct power_supply *psy, /* Exposure to the sysfs interface */ -/** - * abx500_chargalg_sysfs_show() - sysfs show operations - * @kobj: pointer to the struct kobject - * @attr: pointer to the struct attribute - * @buf: buffer that holds the parameter to send to userspace - * - * Returns a buffer to be displayed in user space - */ -static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj, - struct attribute *attr, char *buf) +static ssize_t abx500_chargalg_curr_step_show(struct abx500_chargalg *di, + char *buf) { - struct abx500_chargalg *di = container_of(kobj, - struct abx500_chargalg, chargalg_kobject); + return sprintf(buf, "%d\n", di->curr_status.curr_step); +} + +static ssize_t abx500_chargalg_curr_step_store(struct abx500_chargalg *di, + const char *buf, size_t length) +{ + long int param; + int ret; + + ret = kstrtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + di->curr_status.curr_step = param; + if (di->curr_status.curr_step >= CHARGALG_CURR_STEP_LOW && + di->curr_status.curr_step <= CHARGALG_CURR_STEP_HIGH) { + di->curr_status.curr_step_change = true; + queue_work(di->chargalg_wq, &di->chargalg_work); + } else + dev_info(di->dev, "Wrong current step\n" + "Enter 0. Disable AC/USB Charging\n" + "1--100. Set AC/USB charging current step\n" + "100. Enable AC/USB Charging\n"); + + return strlen(buf); +} + +static ssize_t abx500_chargalg_en_show(struct abx500_chargalg *di, + char *buf) +{ return sprintf(buf, "%d\n", di->susp_status.ac_suspended && di->susp_status.usb_suspended); } -/** - * abx500_chargalg_sysfs_charger() - sysfs store operations - * @kobj: pointer to the struct kobject - * @attr: pointer to the struct attribute - * @buf: buffer that holds the parameter passed from userspace - * @length: length of the parameter passed - * - * Returns length of the buffer(input taken from user space) on success - * else error code on failure - * The operation to be performed on passing the parameters from the user space. - */ -static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, - struct attribute *attr, const char *buf, size_t length) +static ssize_t abx500_chargalg_en_store(struct abx500_chargalg *di, + const char *buf, size_t length) { - struct abx500_chargalg *di = container_of(kobj, - struct abx500_chargalg, chargalg_kobject); long int param; int ac_usb; int ret; - char entry = *attr->name; - switch (entry) { - case 'c': - ret = strict_strtol(buf, 10, ¶m); - if (ret < 0) - return ret; - - ac_usb = param; - switch (ac_usb) { - case 0: - /* Disable charging */ - di->susp_status.ac_suspended = true; - di->susp_status.usb_suspended = true; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - case 1: - /* Enable AC Charging */ - di->susp_status.ac_suspended = false; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - case 2: - /* Enable USB charging */ - di->susp_status.usb_suspended = false; - di->susp_status.suspended_change = true; - /* Trigger a state change */ - queue_work(di->chargalg_wq, - &di->chargalg_work); - break; - default: - dev_info(di->dev, "Wrong input\n" - "Enter 0. Disable AC/USB Charging\n" - "1. Enable AC charging\n" - "2. Enable USB Charging\n"); - }; + ret = kstrtol(buf, 10, ¶m); + if (ret < 0) + return ret; + + ac_usb = param; + switch (ac_usb) { + case 0: + /* Disable charging */ + di->susp_status.ac_suspended = true; + di->susp_status.usb_suspended = true; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 1: + /* Enable AC Charging */ + di->susp_status.ac_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); + break; + case 2: + /* Enable USB charging */ + di->susp_status.usb_suspended = false; + di->susp_status.suspended_change = true; + /* Trigger a state change */ + queue_work(di->chargalg_wq, + &di->chargalg_work); break; + default: + dev_info(di->dev, "Wrong input\n" + "Enter 0. Disable AC/USB Charging\n" + "1. Enable AC charging\n" + "2. Enable USB Charging\n"); }; return strlen(buf); } -static struct attribute abx500_chargalg_en_charger = \ +static struct abx500_chargalg_sysfs_entry abx500_chargalg_en_charger = + __ATTR(chargalg, 0644, abx500_chargalg_en_show, + abx500_chargalg_en_store); + +static struct abx500_chargalg_sysfs_entry abx500_chargalg_curr_step = + __ATTR(chargalg_curr_step, 0644, abx500_chargalg_curr_step_show, + abx500_chargalg_curr_step_store); + +static ssize_t abx500_chargalg_sysfs_show(struct kobject *kobj, + struct attribute *attr, char *buf) { - .name = "chargalg", - .mode = S_IRUGO | S_IWUSR, -}; + struct abx500_chargalg_sysfs_entry *entry = container_of(attr, + struct abx500_chargalg_sysfs_entry, attr); + + struct abx500_chargalg *di = container_of(kobj, + struct abx500_chargalg, chargalg_kobject); + + if (!entry->show) + return -EIO; + + return entry->show(di, buf); +} + +static ssize_t abx500_chargalg_sysfs_charger(struct kobject *kobj, + struct attribute *attr, const char *buf, size_t length) +{ + struct abx500_chargalg_sysfs_entry *entry = container_of(attr, + struct abx500_chargalg_sysfs_entry, attr); + + struct abx500_chargalg *di = container_of(kobj, + struct abx500_chargalg, chargalg_kobject); + + if (!entry->store) + return -EIO; + + return entry->store(di, buf, length); +} static struct attribute *abx500_chargalg_chg[] = { - &abx500_chargalg_en_charger, - NULL + &abx500_chargalg_en_charger.attr, + &abx500_chargalg_curr_step.attr, + NULL, }; static const struct sysfs_ops abx500_chargalg_sysfs_ops = { @@ -1832,10 +2024,16 @@ static int abx500_chargalg_remove(struct platform_device *pdev) /* sysfs interface to enable/disbale charging from user space */ abx500_chargalg_sysfs_exit(di); + hrtimer_cancel(&di->safety_timer); + hrtimer_cancel(&di->maintenance_timer); + + cancel_delayed_work_sync(&di->chargalg_periodic_work); + cancel_delayed_work_sync(&di->chargalg_wd_work); + cancel_work_sync(&di->chargalg_work); + /* Delete the work queue */ destroy_workqueue(di->chargalg_wq); - flush_scheduled_work(); power_supply_unregister(&di->chargalg_psy); platform_set_drvdata(pdev, NULL); @@ -1873,8 +2071,9 @@ static int abx500_chargalg_probe(struct platform_device *pdev) } } - /* get device struct */ + /* get device struct and parent */ di->dev = &pdev->dev; + di->parent = dev_get_drvdata(pdev->dev.parent); /* chargalg supply */ di->chargalg_psy.name = "abx500_chargalg"; @@ -1888,15 +2087,13 @@ static int abx500_chargalg_probe(struct platform_device *pdev) abx500_chargalg_external_power_changed; /* Initilialize safety timer */ - init_timer(&di->safety_timer); + hrtimer_init(&di->safety_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); di->safety_timer.function = abx500_chargalg_safety_timer_expired; - di->safety_timer.data = (unsigned long) di; /* Initilialize maintenance timer */ - init_timer(&di->maintenance_timer); + hrtimer_init(&di->maintenance_timer, CLOCK_REALTIME, HRTIMER_MODE_ABS); di->maintenance_timer.function = abx500_chargalg_maintenance_timer_expired; - di->maintenance_timer.data = (unsigned long) di; /* Create a work queue for the chargalg */ di->chargalg_wq = @@ -1933,6 +2130,7 @@ static int abx500_chargalg_probe(struct platform_device *pdev) dev_err(di->dev, "failed to create sysfs entry\n"); goto free_psy; } + di->curr_status.curr_step = CHARGALG_CURR_STEP_HIGH; /* Run the charging algorithm */ queue_delayed_work(di->chargalg_wq, &di->chargalg_periodic_work, 0); @@ -1964,18 +2162,7 @@ static struct platform_driver abx500_chargalg_driver = { }, }; -static int __init abx500_chargalg_init(void) -{ - return platform_driver_register(&abx500_chargalg_driver); -} - -static void __exit abx500_chargalg_exit(void) -{ - platform_driver_unregister(&abx500_chargalg_driver); -} - -module_init(abx500_chargalg_init); -module_exit(abx500_chargalg_exit); +module_platform_driver(abx500_chargalg_driver); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Johan Palsson, Karl Komierowski"); diff --git a/drivers/power/charger-manager.c b/drivers/power/charger-manager.c index 8acc3f8d303..fefc39fe42b 100644 --- a/drivers/power/charger-manager.c +++ b/drivers/power/charger-manager.c @@ -1485,13 +1485,12 @@ static int charger_manager_probe(struct platform_device *pdev) /* Basic Values. Unspecified are Null or 0 */ cm->dev = &pdev->dev; - cm->desc = kzalloc(sizeof(struct charger_desc), GFP_KERNEL); + cm->desc = kmemdup(desc, sizeof(struct charger_desc), GFP_KERNEL); if (!cm->desc) { dev_err(&pdev->dev, "Cannot allocate memory.\n"); ret = -ENOMEM; goto err_alloc_desc; } - memcpy(cm->desc, desc, sizeof(struct charger_desc)); cm->last_temp_mC = INT_MIN; /* denotes "unmeasured, yet" */ /* diff --git a/drivers/power/da9030_battery.c b/drivers/power/da9030_battery.c index e8c5a391a49..ae6c41835ee 100644 --- a/drivers/power/da9030_battery.c +++ b/drivers/power/da9030_battery.c @@ -505,7 +505,7 @@ static int da9030_battery_probe(struct platform_device *pdev) pdata->charge_millivolt > 4350) return -EINVAL; - charger = kzalloc(sizeof(*charger), GFP_KERNEL); + charger = devm_kzalloc(&pdev->dev, sizeof(*charger), GFP_KERNEL); if (charger == NULL) return -ENOMEM; @@ -557,8 +557,6 @@ err_notifier: cancel_delayed_work(&charger->work); err_charger_init: - kfree(charger); - return ret; } @@ -575,8 +573,6 @@ static int da9030_battery_remove(struct platform_device *dev) da9030_set_charge(charger, 0); power_supply_unregister(&charger->psy); - kfree(charger); - return 0; } diff --git a/drivers/power/da9052-battery.c b/drivers/power/da9052-battery.c index 08193feb3b0..f8f4c0f7c17 100644 --- a/drivers/power/da9052-battery.c +++ b/drivers/power/da9052-battery.c @@ -594,7 +594,8 @@ static s32 da9052_bat_probe(struct platform_device *pdev) int ret; int i; - bat = kzalloc(sizeof(struct da9052_battery), GFP_KERNEL); + bat = devm_kzalloc(&pdev->dev, sizeof(struct da9052_battery), + GFP_KERNEL); if (!bat) return -ENOMEM; @@ -635,7 +636,6 @@ err: while (--i >= 0) da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat); - kfree(bat); return ret; } static int da9052_bat_remove(struct platform_device *pdev) @@ -647,7 +647,6 @@ static int da9052_bat_remove(struct platform_device *pdev) da9052_free_irq(bat->da9052, da9052_bat_irq_bits[i], bat); power_supply_unregister(&bat->psy); - kfree(bat); return 0; } diff --git a/drivers/power/ds2760_battery.c b/drivers/power/ds2760_battery.c index 704e652072b..85b4e6eca0b 100644 --- a/drivers/power/ds2760_battery.c +++ b/drivers/power/ds2760_battery.c @@ -512,7 +512,7 @@ static int ds2760_battery_probe(struct platform_device *pdev) int retval = 0; struct ds2760_device_info *di; - di = kzalloc(sizeof(*di), GFP_KERNEL); + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); if (!di) { retval = -ENOMEM; goto di_alloc_failed; @@ -576,7 +576,6 @@ static int ds2760_battery_probe(struct platform_device *pdev) workqueue_failed: power_supply_unregister(&di->bat); batt_failed: - kfree(di); di_alloc_failed: success: return retval; @@ -590,7 +589,6 @@ static int ds2760_battery_remove(struct platform_device *pdev) cancel_delayed_work_sync(&di->set_charged_work); destroy_workqueue(di->monitor_wqueue); power_supply_unregister(&di->bat); - kfree(di); return 0; } diff --git a/drivers/power/ds2780_battery.c b/drivers/power/ds2780_battery.c index 8b6c4539e7f..9f418fa879e 100644 --- a/drivers/power/ds2780_battery.c +++ b/drivers/power/ds2780_battery.c @@ -760,7 +760,7 @@ static int ds2780_battery_probe(struct platform_device *pdev) int ret = 0; struct ds2780_device_info *dev_info; - dev_info = kzalloc(sizeof(*dev_info), GFP_KERNEL); + dev_info = devm_kzalloc(&pdev->dev, sizeof(*dev_info), GFP_KERNEL); if (!dev_info) { ret = -ENOMEM; goto fail; @@ -779,7 +779,7 @@ static int ds2780_battery_probe(struct platform_device *pdev) ret = power_supply_register(&pdev->dev, &dev_info->bat); if (ret) { dev_err(dev_info->dev, "failed to register battery\n"); - goto fail_free_info; + goto fail; } ret = sysfs_create_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); @@ -813,8 +813,6 @@ fail_remove_group: sysfs_remove_group(&dev_info->bat.dev->kobj, &ds2780_attr_group); fail_unregister: power_supply_unregister(&dev_info->bat); -fail_free_info: - kfree(dev_info); fail: return ret; } @@ -828,7 +826,6 @@ static int ds2780_battery_remove(struct platform_device *pdev) power_supply_unregister(&dev_info->bat); - kfree(dev_info); return 0; } diff --git a/drivers/power/ds2782_battery.c b/drivers/power/ds2782_battery.c index c09e7726c96..563174891c9 100644 --- a/drivers/power/ds2782_battery.c +++ b/drivers/power/ds2782_battery.c @@ -332,32 +332,32 @@ static int ds278x_battery_remove(struct i2c_client *client) return 0; } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP -static int ds278x_suspend(struct i2c_client *client, - pm_message_t state) +static int ds278x_suspend(struct device *dev) { + struct i2c_client *client = to_i2c_client(dev); struct ds278x_info *info = i2c_get_clientdata(client); cancel_delayed_work(&info->bat_work); return 0; } -static int ds278x_resume(struct i2c_client *client) +static int ds278x_resume(struct device *dev) { + struct i2c_client *client = to_i2c_client(dev); struct ds278x_info *info = i2c_get_clientdata(client); schedule_delayed_work(&info->bat_work, DS278x_DELAY); return 0; } -#else - -#define ds278x_suspend NULL -#define ds278x_resume NULL - -#endif /* CONFIG_PM */ +static SIMPLE_DEV_PM_OPS(ds278x_battery_pm_ops, ds278x_suspend, ds278x_resume); +#define DS278X_BATTERY_PM_OPS (&ds278x_battery_pm_ops) +#else +#define DS278X_BATTERY_PM_OPS NULL +#endif /* CONFIG_PM_SLEEP */ enum ds278x_num_id { DS2782 = 0, @@ -460,11 +460,10 @@ MODULE_DEVICE_TABLE(i2c, ds278x_id); static struct i2c_driver ds278x_battery_driver = { .driver = { .name = "ds2782-battery", + .pm = DS278X_BATTERY_PM_OPS, }, .probe = ds278x_battery_probe, .remove = ds278x_battery_remove, - .suspend = ds278x_suspend, - .resume = ds278x_resume, .id_table = ds278x_id, }; module_i2c_driver(ds278x_battery_driver); diff --git a/drivers/power/goldfish_battery.c b/drivers/power/goldfish_battery.c index c10f460f986..29eba88a296 100644 --- a/drivers/power/goldfish_battery.c +++ b/drivers/power/goldfish_battery.c @@ -178,7 +178,7 @@ static int goldfish_battery_probe(struct platform_device *pdev) return -ENODEV; } - data->reg_base = devm_ioremap(&pdev->dev, r->start, r->end - r->start + 1); + data->reg_base = devm_ioremap(&pdev->dev, r->start, resource_size(r)); if (data->reg_base == NULL) { dev_err(&pdev->dev, "unable to remap MMIO\n"); return -ENOMEM; diff --git a/drivers/power/gpio-charger.c b/drivers/power/gpio-charger.c index e3e40a9f3af..e9883eeeee7 100644 --- a/drivers/power/gpio-charger.c +++ b/drivers/power/gpio-charger.c @@ -86,7 +86,8 @@ static int gpio_charger_probe(struct platform_device *pdev) return -EINVAL; } - gpio_charger = kzalloc(sizeof(*gpio_charger), GFP_KERNEL); + gpio_charger = devm_kzalloc(&pdev->dev, sizeof(*gpio_charger), + GFP_KERNEL); if (!gpio_charger) { dev_err(&pdev->dev, "Failed to alloc driver structure\n"); return -ENOMEM; @@ -140,7 +141,6 @@ static int gpio_charger_probe(struct platform_device *pdev) err_gpio_free: gpio_free(pdata->gpio); err_free: - kfree(gpio_charger); return ret; } @@ -156,7 +156,6 @@ static int gpio_charger_remove(struct platform_device *pdev) gpio_free(gpio_charger->pdata->gpio); platform_set_drvdata(pdev, NULL); - kfree(gpio_charger); return 0; } diff --git a/drivers/power/isp1704_charger.c b/drivers/power/isp1704_charger.c index 176ad59d99f..fc04d191579 100644 --- a/drivers/power/isp1704_charger.c +++ b/drivers/power/isp1704_charger.c @@ -411,7 +411,7 @@ static int isp1704_charger_probe(struct platform_device *pdev) struct isp1704_charger *isp; int ret = -ENODEV; - isp = kzalloc(sizeof *isp, GFP_KERNEL); + isp = devm_kzalloc(&pdev->dev, sizeof(*isp), GFP_KERNEL); if (!isp) return -ENOMEM; @@ -477,8 +477,6 @@ fail1: isp1704_charger_set_power(isp, 0); usb_put_phy(isp->phy); fail0: - kfree(isp); - dev_err(&pdev->dev, "failed to register isp1704 with error %d\n", ret); return ret; @@ -492,7 +490,6 @@ static int isp1704_charger_remove(struct platform_device *pdev) power_supply_unregister(&isp->psy); usb_put_phy(isp->phy); isp1704_charger_set_power(isp, 0); - kfree(isp); return 0; } diff --git a/drivers/power/lp8788-charger.c b/drivers/power/lp8788-charger.c index 6d1f452810b..ed49b50b220 100644 --- a/drivers/power/lp8788-charger.c +++ b/drivers/power/lp8788-charger.c @@ -49,7 +49,6 @@ #define LP8788_CHG_START 0x11 #define LP8788_CHG_END 0x1C -#define LP8788_BUF_SIZE 40 #define LP8788_ISEL_MAX 23 #define LP8788_ISEL_STEP 50 #define LP8788_VTERM_MIN 4100 @@ -633,7 +632,7 @@ static ssize_t lp8788_show_charger_status(struct device *dev, lp8788_read_byte(pchg->lp, LP8788_CHG_STATUS, &data); state = (data & LP8788_CHG_STATE_M) >> LP8788_CHG_STATE_S; - return scnprintf(buf, LP8788_BUF_SIZE, "%s\n", desc[state]); + return scnprintf(buf, PAGE_SIZE, "%s\n", desc[state]); } static ssize_t lp8788_show_eoc_time(struct device *dev, @@ -647,7 +646,7 @@ static ssize_t lp8788_show_eoc_time(struct device *dev, lp8788_read_byte(pchg->lp, LP8788_CHG_EOC, &val); val = (val & LP8788_CHG_EOC_TIME_M) >> LP8788_CHG_EOC_TIME_S; - return scnprintf(buf, LP8788_BUF_SIZE, "End Of Charge Time: %s\n", + return scnprintf(buf, PAGE_SIZE, "End Of Charge Time: %s\n", stime[val]); } @@ -667,8 +666,7 @@ static ssize_t lp8788_show_eoc_level(struct device *dev, val = (val & LP8788_CHG_EOC_LEVEL_M) >> LP8788_CHG_EOC_LEVEL_S; level = mode ? abs_level[val] : relative_level[val]; - return scnprintf(buf, LP8788_BUF_SIZE, "End Of Charge Level: %s\n", - level); + return scnprintf(buf, PAGE_SIZE, "End Of Charge Level: %s\n", level); } static DEVICE_ATTR(charger_status, S_IRUSR, lp8788_show_charger_status, NULL); diff --git a/drivers/power/max17040_battery.c b/drivers/power/max17040_battery.c index 74a0bd9bc16..c7ff6d67f15 100644 --- a/drivers/power/max17040_battery.c +++ b/drivers/power/max17040_battery.c @@ -246,31 +246,34 @@ static int max17040_remove(struct i2c_client *client) return 0; } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP -static int max17040_suspend(struct i2c_client *client, - pm_message_t state) +static int max17040_suspend(struct device *dev) { + struct i2c_client *client = to_i2c_client(dev); struct max17040_chip *chip = i2c_get_clientdata(client); cancel_delayed_work(&chip->work); return 0; } -static int max17040_resume(struct i2c_client *client) +static int max17040_resume(struct device *dev) { + struct i2c_client *client = to_i2c_client(dev); struct max17040_chip *chip = i2c_get_clientdata(client); schedule_delayed_work(&chip->work, MAX17040_DELAY); return 0; } +static SIMPLE_DEV_PM_OPS(max17040_pm_ops, max17040_suspend, max17040_resume); +#define MAX17040_PM_OPS (&max17040_pm_ops) + #else -#define max17040_suspend NULL -#define max17040_resume NULL +#define MAX17040_PM_OPS NULL -#endif /* CONFIG_PM */ +#endif /* CONFIG_PM_SLEEP */ static const struct i2c_device_id max17040_id[] = { { "max17040", 0 }, @@ -281,11 +284,10 @@ MODULE_DEVICE_TABLE(i2c, max17040_id); static struct i2c_driver max17040_i2c_driver = { .driver = { .name = "max17040", + .pm = MAX17040_PM_OPS, }, .probe = max17040_probe, .remove = max17040_remove, - .suspend = max17040_suspend, - .resume = max17040_resume, .id_table = max17040_id, }; module_i2c_driver(max17040_i2c_driver); diff --git a/drivers/power/max8903_charger.c b/drivers/power/max8903_charger.c index 14e2b96d93b..08f0d7909b6 100644 --- a/drivers/power/max8903_charger.c +++ b/drivers/power/max8903_charger.c @@ -189,7 +189,7 @@ static int max8903_probe(struct platform_device *pdev) int ta_in = 0; int usb_in = 0; - data = kzalloc(sizeof(struct max8903_data), GFP_KERNEL); + data = devm_kzalloc(dev, sizeof(struct max8903_data), GFP_KERNEL); if (data == NULL) { dev_err(dev, "Cannot allocate memory.\n"); return -ENOMEM; @@ -341,7 +341,6 @@ err_dc_irq: err_psy: power_supply_unregister(&data->psy); err: - kfree(data); return ret; } @@ -359,7 +358,6 @@ static int max8903_remove(struct platform_device *pdev) if (pdata->dc_valid) free_irq(gpio_to_irq(pdata->dok), data); power_supply_unregister(&data->psy); - kfree(data); } return 0; diff --git a/drivers/power/max8925_power.c b/drivers/power/max8925_power.c index 665cdc76c26..0ee1e14f76e 100644 --- a/drivers/power/max8925_power.c +++ b/drivers/power/max8925_power.c @@ -489,7 +489,8 @@ static int max8925_power_probe(struct platform_device *pdev) return -EINVAL; } - info = kzalloc(sizeof(struct max8925_power_info), GFP_KERNEL); + info = devm_kzalloc(&pdev->dev, sizeof(struct max8925_power_info), + GFP_KERNEL); if (!info) return -ENOMEM; info->chip = chip; @@ -546,7 +547,6 @@ out_battery: out_usb: power_supply_unregister(&info->ac); out: - kfree(info); return ret; } @@ -559,7 +559,6 @@ static int max8925_power_remove(struct platform_device *pdev) power_supply_unregister(&info->usb); power_supply_unregister(&info->battery); max8925_deinit_charger(info); - kfree(info); } return 0; } diff --git a/drivers/power/max8997_charger.c b/drivers/power/max8997_charger.c index e757885b620..4bdedfed936 100644 --- a/drivers/power/max8997_charger.c +++ b/drivers/power/max8997_charger.c @@ -138,7 +138,8 @@ static int max8997_battery_probe(struct platform_device *pdev) return ret; } - charger = kzalloc(sizeof(struct charger_data), GFP_KERNEL); + charger = devm_kzalloc(&pdev->dev, sizeof(struct charger_data), + GFP_KERNEL); if (charger == NULL) { dev_err(&pdev->dev, "Cannot allocate memory.\n"); return -ENOMEM; @@ -158,13 +159,10 @@ static int max8997_battery_probe(struct platform_device *pdev) ret = power_supply_register(&pdev->dev, &charger->battery); if (ret) { dev_err(&pdev->dev, "failed: power supply register\n"); - goto err; + return ret; } return 0; -err: - kfree(charger); - return ret; } static int max8997_battery_remove(struct platform_device *pdev) @@ -172,7 +170,6 @@ static int max8997_battery_remove(struct platform_device *pdev) struct charger_data *charger = platform_get_drvdata(pdev); power_supply_unregister(&charger->battery); - kfree(charger); return 0; } diff --git a/drivers/power/max8998_charger.c b/drivers/power/max8998_charger.c index bf677e3daec..5017470c2fc 100644 --- a/drivers/power/max8998_charger.c +++ b/drivers/power/max8998_charger.c @@ -88,7 +88,8 @@ static int max8998_battery_probe(struct platform_device *pdev) return -ENODEV; } - max8998 = kzalloc(sizeof(struct max8998_battery_data), GFP_KERNEL); + max8998 = devm_kzalloc(&pdev->dev, sizeof(struct max8998_battery_data), + GFP_KERNEL); if (!max8998) return -ENOMEM; @@ -174,7 +175,6 @@ static int max8998_battery_probe(struct platform_device *pdev) return 0; err: - kfree(max8998); return ret; } @@ -183,7 +183,6 @@ static int max8998_battery_remove(struct platform_device *pdev) struct max8998_battery_data *max8998 = platform_get_drvdata(pdev); power_supply_unregister(&max8998->battery); - kfree(max8998); return 0; } diff --git a/drivers/power/pcf50633-charger.c b/drivers/power/pcf50633-charger.c index c2122a7ad06..17fd77f24b2 100644 --- a/drivers/power/pcf50633-charger.c +++ b/drivers/power/pcf50633-charger.c @@ -373,7 +373,7 @@ static int pcf50633_mbc_probe(struct platform_device *pdev) int i; u8 mbcs1; - mbc = kzalloc(sizeof(*mbc), GFP_KERNEL); + mbc = devm_kzalloc(&pdev->dev, sizeof(*mbc), GFP_KERNEL); if (!mbc) return -ENOMEM; @@ -413,7 +413,6 @@ static int pcf50633_mbc_probe(struct platform_device *pdev) ret = power_supply_register(&pdev->dev, &mbc->adapter); if (ret) { dev_err(mbc->pcf->dev, "failed to register adapter\n"); - kfree(mbc); return ret; } @@ -421,7 +420,6 @@ static int pcf50633_mbc_probe(struct platform_device *pdev) if (ret) { dev_err(mbc->pcf->dev, "failed to register usb\n"); power_supply_unregister(&mbc->adapter); - kfree(mbc); return ret; } @@ -430,7 +428,6 @@ static int pcf50633_mbc_probe(struct platform_device *pdev) dev_err(mbc->pcf->dev, "failed to register ac\n"); power_supply_unregister(&mbc->adapter); power_supply_unregister(&mbc->usb); - kfree(mbc); return ret; } @@ -461,8 +458,6 @@ static int pcf50633_mbc_remove(struct platform_device *pdev) power_supply_unregister(&mbc->adapter); power_supply_unregister(&mbc->ac); - kfree(mbc); - return 0; } diff --git a/drivers/power/pm2301_charger.c b/drivers/power/pm2301_charger.c index ee346d44357..a44175139bb 100644 --- a/drivers/power/pm2301_charger.c +++ b/drivers/power/pm2301_charger.c @@ -16,24 +16,25 @@ #include <linux/slab.h> #include <linux/platform_device.h> #include <linux/power_supply.h> -#include <linux/completion.h> #include <linux/regulator/consumer.h> #include <linux/err.h> #include <linux/i2c.h> #include <linux/workqueue.h> -#include <linux/kobject.h> -#include <linux/mfd/abx500.h> #include <linux/mfd/abx500/ab8500.h> #include <linux/mfd/abx500/ab8500-bm.h> -#include <linux/mfd/abx500/ab8500-gpadc.h> #include <linux/mfd/abx500/ux500_chargalg.h> #include <linux/pm2301_charger.h> #include <linux/gpio.h> +#include <linux/pm_runtime.h> +#include <linux/pm.h> #include "pm2301_charger.h" #define to_pm2xxx_charger_ac_device_info(x) container_of((x), \ struct pm2xxx_charger, ac_chg) +#define SLEEP_MIN 50 +#define SLEEP_MAX 100 +#define PM2XXX_AUTOSUSPEND_DELAY 500 static int pm2xxx_interrupt_registers[] = { PM2XXX_REG_INT1, @@ -113,33 +114,24 @@ static const struct i2c_device_id pm2xxx_ident[] = { static void set_lpn_pin(struct pm2xxx_charger *pm2) { - if (pm2->ac.charger_connected) - return; - gpio_set_value(pm2->lpn_pin, 1); - - return; + if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) { + gpio_set_value(pm2->lpn_pin, 1); + usleep_range(SLEEP_MIN, SLEEP_MAX); + } } static void clear_lpn_pin(struct pm2xxx_charger *pm2) { - if (pm2->ac.charger_connected) - return; - gpio_set_value(pm2->lpn_pin, 0); - - return; + if (!pm2->ac.charger_connected && gpio_is_valid(pm2->lpn_pin)) + gpio_set_value(pm2->lpn_pin, 0); } static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val) { int ret; - /* - * When AC adaptor is unplugged, the host - * must put LPN high to be able to - * communicate by I2C with PM2301 - * and receive I2C "acknowledge" from PM2301. - */ - mutex_lock(&pm2->lock); - set_lpn_pin(pm2); + + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); ret = i2c_smbus_read_i2c_block_data(pm2->config.pm2xxx_i2c, reg, 1, val); @@ -147,8 +139,8 @@ static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val) dev_err(pm2->dev, "Error reading register at 0x%x\n", reg); else ret = 0; - clear_lpn_pin(pm2); - mutex_unlock(&pm2->lock); + + pm_runtime_put_sync(pm2->dev); return ret; } @@ -156,14 +148,9 @@ static int pm2xxx_reg_read(struct pm2xxx_charger *pm2, int reg, u8 *val) static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val) { int ret; - /* - * When AC adaptor is unplugged, the host - * must put LPN high to be able to - * communicate by I2C with PM2301 - * and receive I2C "acknowledge" from PM2301. - */ - mutex_lock(&pm2->lock); - set_lpn_pin(pm2); + + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); ret = i2c_smbus_write_i2c_block_data(pm2->config.pm2xxx_i2c, reg, 1, &val); @@ -171,8 +158,8 @@ static int pm2xxx_reg_write(struct pm2xxx_charger *pm2, int reg, u8 val) dev_err(pm2->dev, "Error writing register at 0x%x\n", reg); else ret = 0; - clear_lpn_pin(pm2); - mutex_unlock(&pm2->lock); + + pm_runtime_put_sync(pm2->dev); return ret; } @@ -192,11 +179,22 @@ static int pm2xxx_charging_disable_mngt(struct pm2xxx_charger *pm2) { int ret; + /* Disable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, PM2XXX_SWCTRL_HW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } + /* Disable charging */ ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_CTRL_REG2, (PM2XXX_CH_AUTO_RESUME_DIS | PM2XXX_CHARGER_DIS)); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } - return ret; + return 0; } static int pm2xxx_charger_batt_therm_mngt(struct pm2xxx_charger *pm2, int val) @@ -216,21 +214,14 @@ int pm2xxx_charger_die_therm_mngt(struct pm2xxx_charger *pm2, int val) static int pm2xxx_charger_ovv_mngt(struct pm2xxx_charger *pm2, int val) { - int ret = 0; + dev_err(pm2->dev, "Overvoltage detected\n"); + pm2->flags.ovv = true; + power_supply_changed(&pm2->ac_chg.psy); - pm2->failure_input_ovv++; - if (pm2->failure_input_ovv < 4) { - ret = pm2xxx_charging_enable_mngt(pm2); - goto out; - } else { - pm2->failure_input_ovv = 0; - dev_err(pm2->dev, "Overvoltage detected\n"); - pm2->flags.ovv = true; - power_supply_changed(&pm2->ac_chg.psy); - } + /* Schedule a new HW failure check */ + queue_delayed_work(pm2->charger_wq, &pm2->check_hw_failure_work, 0); -out: - return ret; + return 0; } static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val) @@ -245,13 +236,29 @@ static int pm2xxx_charger_wd_exp_mngt(struct pm2xxx_charger *pm2, int val) static int pm2xxx_charger_vbat_lsig_mngt(struct pm2xxx_charger *pm2, int val) { + int ret; + switch (val) { case PM2XXX_INT1_ITVBATLOWR: dev_dbg(pm2->dev, "VBAT grows above VBAT_LOW level\n"); + /* Enable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, + PM2XXX_SWCTRL_SW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } break; case PM2XXX_INT1_ITVBATLOWF: dev_dbg(pm2->dev, "VBAT drops below VBAT_LOW level\n"); + /* Disable SW EOC ctrl */ + ret = pm2xxx_reg_write(pm2, PM2XXX_SW_CTRL_REG, + PM2XXX_SWCTRL_HW); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx write failed\n", __func__); + return ret; + } break; default: @@ -322,16 +329,27 @@ static int pm2_int_reg0(void *pm2_data, int val) struct pm2xxx_charger *pm2 = pm2_data; int ret = 0; - if (val & (PM2XXX_INT1_ITVBATLOWR | PM2XXX_INT1_ITVBATLOWF)) { - ret = pm2xxx_charger_vbat_lsig_mngt(pm2, val & - (PM2XXX_INT1_ITVBATLOWR | PM2XXX_INT1_ITVBATLOWF)); + if (val & PM2XXX_INT1_ITVBATLOWR) { + ret = pm2xxx_charger_vbat_lsig_mngt(pm2, + PM2XXX_INT1_ITVBATLOWR); + if (ret < 0) + goto out; + } + + if (val & PM2XXX_INT1_ITVBATLOWF) { + ret = pm2xxx_charger_vbat_lsig_mngt(pm2, + PM2XXX_INT1_ITVBATLOWF); + if (ret < 0) + goto out; } if (val & PM2XXX_INT1_ITVBATDISCONNECT) { ret = pm2xxx_charger_bat_disc_mngt(pm2, PM2XXX_INT1_ITVBATDISCONNECT); + if (ret < 0) + goto out; } - +out: return ret; } @@ -447,7 +465,6 @@ static int pm2_int_reg5(void *pm2_data, int val) struct pm2xxx_charger *pm2 = pm2_data; int ret = 0; - if (val & (PM2XXX_INT6_ITVPWR2DROP | PM2XXX_INT6_ITVPWR1DROP)) { dev_dbg(pm2->dev, "VMPWR drop to VBAT level\n"); } @@ -468,14 +485,22 @@ static irqreturn_t pm2xxx_irq_int(int irq, void *data) struct pm2xxx_interrupts *interrupt = pm2->pm2_int; int i; - for (i = 0; i < PM2XXX_NUM_INT_REG; i++) { - pm2xxx_reg_read(pm2, + /* wake up the device */ + pm_runtime_get_sync(pm2->dev); + + do { + for (i = 0; i < PM2XXX_NUM_INT_REG; i++) { + pm2xxx_reg_read(pm2, pm2xxx_interrupt_registers[i], &(interrupt->reg[i])); - if (interrupt->reg[i] > 0) - interrupt->handler[i](pm2, interrupt->reg[i]); - } + if (interrupt->reg[i] > 0) + interrupt->handler[i](pm2, interrupt->reg[i]); + } + } while (gpio_get_value(pm2->pdata->gpio_irq_number) == 0); + + pm_runtime_mark_last_busy(pm2->dev); + pm_runtime_put_autosuspend(pm2->dev); return IRQ_HANDLED; } @@ -592,6 +617,8 @@ static int pm2xxx_charger_ac_get_property(struct power_supply *psy, val->intval = POWER_SUPPLY_HEALTH_DEAD; else if (pm2->flags.main_thermal_prot) val->intval = POWER_SUPPLY_HEALTH_OVERHEAT; + else if (pm2->flags.ovv) + val->intval = POWER_SUPPLY_HEALTH_OVERVOLTAGE; else val->intval = POWER_SUPPLY_HEALTH_GOOD; break; @@ -674,10 +701,6 @@ static int pm2xxx_charging_init(struct pm2xxx_charger *pm2) ret = pm2xxx_reg_write(pm2, PM2XXX_BATT_LOW_LEV_COMP_REG, PM2XXX_VBAT_LOW_MONITORING_ENA); - /* Disable LED */ - ret = pm2xxx_reg_write(pm2, PM2XXX_LED_CTRL_REG, - PM2XXX_LED_SELECT_DIS); - return ret; } @@ -822,10 +845,54 @@ static void pm2xxx_charger_ac_work(struct work_struct *work) sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present"); }; +static void pm2xxx_charger_check_hw_failure_work(struct work_struct *work) +{ + u8 reg_value; + + struct pm2xxx_charger *pm2 = container_of(work, + struct pm2xxx_charger, check_hw_failure_work.work); + + if (pm2->flags.ovv) { + pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT4, ®_value); + + if (!(reg_value & (PM2XXX_INT4_S_ITVPWR1OVV | + PM2XXX_INT4_S_ITVPWR2OVV))) { + pm2->flags.ovv = false; + power_supply_changed(&pm2->ac_chg.psy); + } + } + + /* If we still have a failure, schedule a new check */ + if (pm2->flags.ovv) { + queue_delayed_work(pm2->charger_wq, + &pm2->check_hw_failure_work, round_jiffies(HZ)); + } +} + static void pm2xxx_charger_check_main_thermal_prot_work( struct work_struct *work) { -}; + int ret; + u8 val; + + struct pm2xxx_charger *pm2 = container_of(work, struct pm2xxx_charger, + check_main_thermal_prot_work); + + /* Check if die temp warning is still active */ + ret = pm2xxx_reg_read(pm2, PM2XXX_SRCE_REG_INT5, &val); + if (ret < 0) { + dev_err(pm2->dev, "%s pm2xxx read failed\n", __func__); + return; + } + if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGRISE + | PM2XXX_INT5_S_ITTHERMALSHUTDOWNRISE)) + pm2->flags.main_thermal_prot = true; + else if (val & (PM2XXX_INT5_S_ITTHERMALWARNINGFALL + | PM2XXX_INT5_S_ITTHERMALSHUTDOWNFALL)) + pm2->flags.main_thermal_prot = false; + + power_supply_changed(&pm2->ac_chg.psy); +} static struct pm2xxx_interrupts pm2xxx_int = { .handler[0] = pm2_int_reg0, @@ -840,24 +907,105 @@ static struct pm2xxx_irq pm2xxx_charger_irq[] = { {"PM2XXX_IRQ_INT", pm2xxx_irq_int}, }; -static int pm2xxx_wall_charger_resume(struct i2c_client *i2c_client) +#ifdef CONFIG_PM + +#ifdef CONFIG_PM_SLEEP + +static int pm2xxx_wall_charger_resume(struct device *dev) { + struct i2c_client *i2c_client = to_i2c_client(dev); + struct pm2xxx_charger *pm2; + + pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client); + set_lpn_pin(pm2); + + /* If we still have a HW failure, schedule a new check */ + if (pm2->flags.ovv) + queue_delayed_work(pm2->charger_wq, + &pm2->check_hw_failure_work, 0); + return 0; } -static int pm2xxx_wall_charger_suspend(struct i2c_client *i2c_client, - pm_message_t state) +static int pm2xxx_wall_charger_suspend(struct device *dev) { + struct i2c_client *i2c_client = to_i2c_client(dev); + struct pm2xxx_charger *pm2; + + pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(i2c_client); + clear_lpn_pin(pm2); + + /* Cancel any pending HW failure check */ + if (delayed_work_pending(&pm2->check_hw_failure_work)) + cancel_delayed_work(&pm2->check_hw_failure_work); + + flush_work(&pm2->ac_work); + flush_work(&pm2->check_main_thermal_prot_work); + return 0; } -static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, +#endif + +#ifdef CONFIG_PM_RUNTIME + +static int pm2xxx_runtime_suspend(struct device *dev) +{ + struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); + struct pm2xxx_charger *pm2; + int ret = 0; + + pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); + if (!pm2) { + dev_err(pm2->dev, "no pm2xxx_charger data supplied\n"); + ret = -EINVAL; + return ret; + } + + clear_lpn_pin(pm2); + + return ret; +} + +static int pm2xxx_runtime_resume(struct device *dev) +{ + struct i2c_client *pm2xxx_i2c_client = to_i2c_client(dev); + struct pm2xxx_charger *pm2; + int ret = 0; + + pm2 = (struct pm2xxx_charger *)i2c_get_clientdata(pm2xxx_i2c_client); + if (!pm2) { + dev_err(pm2->dev, "no pm2xxx_charger data supplied\n"); + ret = -EINVAL; + return ret; + } + + if (gpio_is_valid(pm2->lpn_pin) && gpio_get_value(pm2->lpn_pin) == 0) + set_lpn_pin(pm2); + + return ret; +} + +#endif + +static const struct dev_pm_ops pm2xxx_pm_ops = { + SET_SYSTEM_SLEEP_PM_OPS(pm2xxx_wall_charger_suspend, + pm2xxx_wall_charger_resume) + SET_RUNTIME_PM_OPS(pm2xxx_runtime_suspend, pm2xxx_runtime_resume, NULL) +}; +#define PM2XXX_PM_OPS (&pm2xxx_pm_ops) +#else +#define PM2XXX_PM_OPS NULL +#endif + +static int pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, const struct i2c_device_id *id) { struct pm2xxx_platform_data *pl_data = i2c_client->dev.platform_data; struct pm2xxx_charger *pm2; int ret = 0; u8 val; + int i; pm2 = kzalloc(sizeof(struct pm2xxx_charger), GFP_KERNEL); if (!pm2) { @@ -867,7 +1015,6 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, /* get parent data */ pm2->dev = &i2c_client->dev; - pm2->gpadc = ab8500_gpadc_get("ab8500-gpadc.0"); pm2->pm2_int = &pm2xxx_int; @@ -889,14 +1036,6 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, pm2->bat = pl_data->battery; - /*get lpn GPIO from platform data*/ - if (!pm2->pdata->lpn_gpio) { - dev_err(pm2->dev, "no lpn gpio data supplied\n"); - ret = -EINVAL; - goto free_device_info; - } - pm2->lpn_pin = pm2->pdata->lpn_gpio; - if (!i2c_check_functionality(i2c_client->adapter, I2C_FUNC_SMBUS_BYTE_DATA | I2C_FUNC_SMBUS_READ_WORD_DATA)) { @@ -945,6 +1084,10 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, INIT_WORK(&pm2->check_main_thermal_prot_work, pm2xxx_charger_check_main_thermal_prot_work); + /* Init work for HW failure check */ + INIT_DEFERRABLE_WORK(&pm2->check_hw_failure_work, + pm2xxx_charger_check_hw_failure_work); + /* * VDD ADC supply needs to be enabled from this driver when there * is a charger connected to avoid erroneous BTEMP_HIGH/LOW @@ -965,40 +1108,72 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, } /* Register interrupts */ - ret = request_threaded_irq(pm2->pdata->irq_number, NULL, + ret = request_threaded_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), + NULL, pm2xxx_charger_irq[0].isr, pm2->pdata->irq_type, pm2xxx_charger_irq[0].name, pm2); if (ret != 0) { dev_err(pm2->dev, "failed to request %s IRQ %d: %d\n", - pm2xxx_charger_irq[0].name, pm2->pdata->irq_number, ret); + pm2xxx_charger_irq[0].name, + gpio_to_irq(pm2->pdata->gpio_irq_number), ret); goto unregister_pm2xxx_charger; } - /*Initialize lock*/ - mutex_init(&pm2->lock); + ret = pm_runtime_set_active(pm2->dev); + if (ret) + dev_err(pm2->dev, "set active Error\n"); - /* - * Charger detection mechanism requires pulling up the LPN pin - * while i2c communication if Charger is not connected - * LPN pin of PM2301 is GPIO60 of AB9540 - */ - ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio"); - if (ret < 0) { - dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n"); - goto unregister_pm2xxx_charger; + pm_runtime_enable(pm2->dev); + pm_runtime_set_autosuspend_delay(pm2->dev, PM2XXX_AUTOSUSPEND_DELAY); + pm_runtime_use_autosuspend(pm2->dev); + pm_runtime_resume(pm2->dev); + + /* pm interrupt can wake up system */ + ret = enable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); + if (ret) { + dev_err(pm2->dev, "failed to set irq wake\n"); + goto unregister_pm2xxx_interrupt; } - ret = gpio_direction_output(pm2->lpn_pin, 0); - if (ret < 0) { - dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n"); - goto free_gpio; + + mutex_init(&pm2->lock); + + if (gpio_is_valid(pm2->pdata->lpn_gpio)) { + /* get lpn GPIO from platform data */ + pm2->lpn_pin = pm2->pdata->lpn_gpio; + + /* + * Charger detection mechanism requires pulling up the LPN pin + * while i2c communication if Charger is not connected + * LPN pin of PM2301 is GPIO60 of AB9540 + */ + ret = gpio_request(pm2->lpn_pin, "pm2301_lpm_gpio"); + + if (ret < 0) { + dev_err(pm2->dev, "pm2301_lpm_gpio request failed\n"); + goto disable_pm2_irq_wake; + } + ret = gpio_direction_output(pm2->lpn_pin, 0); + if (ret < 0) { + dev_err(pm2->dev, "pm2301_lpm_gpio direction failed\n"); + goto free_gpio; + } + set_lpn_pin(pm2); } + /* read interrupt registers */ + for (i = 0; i < PM2XXX_NUM_INT_REG; i++) + pm2xxx_reg_read(pm2, + pm2xxx_interrupt_registers[i], + &val); + ret = pm2xxx_charger_detection(pm2, &val); if ((ret == 0) && val) { pm2->ac.charger_connected = 1; + ab8500_override_turn_on_stat(~AB8500_POW_KEY_1_ON, + AB8500_MAIN_CH_DET); pm2->ac_conn = true; power_supply_changed(&pm2->ac_chg.psy); sysfs_notify(&pm2->ac_chg.psy.dev->kobj, NULL, "present"); @@ -1007,7 +1182,13 @@ static int __devinit pm2xxx_wall_charger_probe(struct i2c_client *i2c_client, return 0; free_gpio: - gpio_free(pm2->lpn_pin); + if (gpio_is_valid(pm2->lpn_pin)) + gpio_free(pm2->lpn_pin); +disable_pm2_irq_wake: + disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); +unregister_pm2xxx_interrupt: + /* disable interrupt */ + free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2); unregister_pm2xxx_charger: /* unregister power supply */ power_supply_unregister(&pm2->ac_chg.psy); @@ -1018,18 +1199,24 @@ free_charger_wq: destroy_workqueue(pm2->charger_wq); free_device_info: kfree(pm2); + return ret; } -static int __devexit pm2xxx_wall_charger_remove(struct i2c_client *i2c_client) +static int pm2xxx_wall_charger_remove(struct i2c_client *i2c_client) { struct pm2xxx_charger *pm2 = i2c_get_clientdata(i2c_client); + /* Disable pm_runtime */ + pm_runtime_disable(pm2->dev); /* Disable AC charging */ pm2xxx_charger_ac_en(&pm2->ac_chg, false, 0, 0); + /* Disable wake by pm interrupt */ + disable_irq_wake(gpio_to_irq(pm2->pdata->gpio_irq_number)); + /* Disable interrupts */ - free_irq(pm2->pdata->irq_number, pm2); + free_irq(gpio_to_irq(pm2->pdata->gpio_irq_number), pm2); /* Delete the work queue */ destroy_workqueue(pm2->charger_wq); @@ -1041,8 +1228,8 @@ static int __devexit pm2xxx_wall_charger_remove(struct i2c_client *i2c_client) power_supply_unregister(&pm2->ac_chg.psy); - /*Free GPIO60*/ - gpio_free(pm2->lpn_pin); + if (gpio_is_valid(pm2->lpn_pin)) + gpio_free(pm2->lpn_pin); kfree(pm2); @@ -1058,12 +1245,11 @@ MODULE_DEVICE_TABLE(i2c, pm2xxx_id); static struct i2c_driver pm2xxx_charger_driver = { .probe = pm2xxx_wall_charger_probe, - .remove = __devexit_p(pm2xxx_wall_charger_remove), - .suspend = pm2xxx_wall_charger_suspend, - .resume = pm2xxx_wall_charger_resume, + .remove = pm2xxx_wall_charger_remove, .driver = { .name = "pm2xxx-wall_charger", .owner = THIS_MODULE, + .pm = PM2XXX_PM_OPS, }, .id_table = pm2xxx_id, }; @@ -1078,11 +1264,10 @@ static void __exit pm2xxx_charger_exit(void) i2c_del_driver(&pm2xxx_charger_driver); } -subsys_initcall_sync(pm2xxx_charger_init); +device_initcall_sync(pm2xxx_charger_init); module_exit(pm2xxx_charger_exit); MODULE_LICENSE("GPL v2"); MODULE_AUTHOR("Rajkumar kasirajan, Olivier Launay"); MODULE_ALIAS("platform:pm2xxx-charger"); MODULE_DESCRIPTION("PM2xxx charger management driver"); - diff --git a/drivers/power/pm2301_charger.h b/drivers/power/pm2301_charger.h index e6319cdbc94..8ce3cc0195d 100644 --- a/drivers/power/pm2301_charger.h +++ b/drivers/power/pm2301_charger.h @@ -9,27 +9,6 @@ #ifndef PM2301_CHARGER_H #define PM2301_CHARGER_H -#define MAIN_WDOG_ENA 0x01 -#define MAIN_WDOG_KICK 0x02 -#define MAIN_WDOG_DIS 0x00 -#define CHARG_WD_KICK 0x01 -#define MAIN_CH_ENA 0x01 -#define MAIN_CH_NO_OVERSHOOT_ENA_N 0x02 -#define MAIN_CH_DET 0x01 -#define MAIN_CH_CV_ON 0x04 -#define OTP_ENABLE_WD 0x01 - -#define MAIN_CH_INPUT_CURR_SHIFT 4 - -#define LED_INDICATOR_PWM_ENA 0x01 -#define LED_INDICATOR_PWM_DIS 0x00 -#define LED_IND_CUR_5MA 0x04 -#define LED_INDICATOR_PWM_DUTY_252_256 0xBF - -/* HW failure constants */ -#define MAIN_CH_TH_PROT 0x02 -#define MAIN_CH_NOK 0x01 - /* Watchdog timeout constant */ #define WD_TIMER 0x30 /* 4min */ #define WD_KICK_INTERVAL (30 * HZ) @@ -495,7 +474,6 @@ struct pm2xxx_charger { int failure_input_ovv; unsigned int lpn_pin; struct pm2xxx_interrupts *pm2_int; - struct ab8500_gpadc *gpadc; struct regulator *regu; struct pm2xxx_bm_data *bat; struct mutex lock; @@ -506,6 +484,7 @@ struct pm2xxx_charger { struct delayed_work check_vbat_work; struct work_struct ac_work; struct work_struct check_main_thermal_prot_work; + struct delayed_work check_hw_failure_work; struct ux500_charger ac_chg; struct pm2xxx_charger_event_flags flags; }; diff --git a/drivers/power/power_supply_core.c b/drivers/power/power_supply_core.c index 5deac432e2a..1c517c34e4b 100644 --- a/drivers/power/power_supply_core.c +++ b/drivers/power/power_supply_core.c @@ -26,17 +26,42 @@ EXPORT_SYMBOL_GPL(power_supply_class); static struct device_type power_supply_dev_type; +static bool __power_supply_is_supplied_by(struct power_supply *supplier, + struct power_supply *supply) +{ + int i; + + if (!supply->supplied_from && !supplier->supplied_to) + return false; + + /* Support both supplied_to and supplied_from modes */ + if (supply->supplied_from) { + if (!supplier->name) + return false; + for (i = 0; i < supply->num_supplies; i++) + if (!strcmp(supplier->name, supply->supplied_from[i])) + return true; + } else { + if (!supply->name) + return false; + for (i = 0; i < supplier->num_supplicants; i++) + if (!strcmp(supplier->supplied_to[i], supply->name)) + return true; + } + + return false; +} + static int __power_supply_changed_work(struct device *dev, void *data) { struct power_supply *psy = (struct power_supply *)data; struct power_supply *pst = dev_get_drvdata(dev); - int i; - for (i = 0; i < psy->num_supplicants; i++) - if (!strcmp(psy->supplied_to[i], pst->name)) { - if (pst->external_power_changed) - pst->external_power_changed(pst); - } + if (__power_supply_is_supplied_by(psy, pst)) { + if (pst->external_power_changed) + pst->external_power_changed(pst); + } + return 0; } @@ -63,22 +88,151 @@ void power_supply_changed(struct power_supply *psy) } EXPORT_SYMBOL_GPL(power_supply_changed); +#ifdef CONFIG_OF +#include <linux/of.h> + +static int __power_supply_populate_supplied_from(struct device *dev, + void *data) +{ + struct power_supply *psy = (struct power_supply *)data; + struct power_supply *epsy = dev_get_drvdata(dev); + struct device_node *np; + int i = 0; + + do { + np = of_parse_phandle(psy->of_node, "power-supplies", i++); + if (!np) + continue; + + if (np == epsy->of_node) { + dev_info(psy->dev, "%s: Found supply : %s\n", + psy->name, epsy->name); + psy->supplied_from[i-1] = (char *)epsy->name; + psy->num_supplies++; + break; + } + } while (np); + + return 0; +} + +static int power_supply_populate_supplied_from(struct power_supply *psy) +{ + int error; + + error = class_for_each_device(power_supply_class, NULL, psy, + __power_supply_populate_supplied_from); + + dev_dbg(psy->dev, "%s %d\n", __func__, error); + + return error; +} + +static int __power_supply_find_supply_from_node(struct device *dev, + void *data) +{ + struct device_node *np = (struct device_node *)data; + struct power_supply *epsy = dev_get_drvdata(dev); + + /* return error breaks out of class_for_each_device loop */ + if (epsy->of_node == np) + return -EINVAL; + + return 0; +} + +static int power_supply_find_supply_from_node(struct device_node *supply_node) +{ + int error; + struct device *dev; + struct class_dev_iter iter; + + /* + * Use iterator to see if any other device is registered. + * This is required since class_for_each_device returns 0 + * if there are no devices registered. + */ + class_dev_iter_init(&iter, power_supply_class, NULL, NULL); + dev = class_dev_iter_next(&iter); + + if (!dev) + return -EPROBE_DEFER; + + /* + * We have to treat the return value as inverted, because if + * we return error on not found, then it won't continue looking. + * So we trick it by returning error on success to stop looking + * once the matching device is found. + */ + error = class_for_each_device(power_supply_class, NULL, supply_node, + __power_supply_find_supply_from_node); + + return error ? 0 : -EPROBE_DEFER; +} + +static int power_supply_check_supplies(struct power_supply *psy) +{ + struct device_node *np; + int cnt = 0; + + /* If there is already a list honor it */ + if (psy->supplied_from && psy->num_supplies > 0) + return 0; + + /* No device node found, nothing to do */ + if (!psy->of_node) + return 0; + + do { + int ret; + + np = of_parse_phandle(psy->of_node, "power-supplies", cnt++); + if (!np) + continue; + + ret = power_supply_find_supply_from_node(np); + if (ret) { + dev_dbg(psy->dev, "Failed to find supply, defer!\n"); + return -EPROBE_DEFER; + } + } while (np); + + /* All supplies found, allocate char ** array for filling */ + psy->supplied_from = devm_kzalloc(psy->dev, sizeof(psy->supplied_from), + GFP_KERNEL); + if (!psy->supplied_from) { + dev_err(psy->dev, "Couldn't allocate memory for supply list\n"); + return -ENOMEM; + } + + *psy->supplied_from = devm_kzalloc(psy->dev, sizeof(char *) * cnt, + GFP_KERNEL); + if (!*psy->supplied_from) { + dev_err(psy->dev, "Couldn't allocate memory for supply list\n"); + return -ENOMEM; + } + + return power_supply_populate_supplied_from(psy); +} +#else +static inline int power_supply_check_supplies(struct power_supply *psy) +{ + return 0; +} +#endif + static int __power_supply_am_i_supplied(struct device *dev, void *data) { union power_supply_propval ret = {0,}; struct power_supply *psy = (struct power_supply *)data; struct power_supply *epsy = dev_get_drvdata(dev); - int i; - for (i = 0; i < epsy->num_supplicants; i++) { - if (!strcmp(epsy->supplied_to[i], psy->name)) { - if (epsy->get_property(epsy, - POWER_SUPPLY_PROP_ONLINE, &ret)) - continue; + if (__power_supply_is_supplied_by(epsy, psy)) + if (!epsy->get_property(epsy, POWER_SUPPLY_PROP_ONLINE, &ret)) { if (ret.intval) return ret.intval; } - } + return 0; } @@ -336,6 +490,12 @@ int power_supply_register(struct device *parent, struct power_supply *psy) INIT_WORK(&psy->changed_work, power_supply_changed_work); + rc = power_supply_check_supplies(psy); + if (rc) { + dev_info(dev, "Not all required supplies found, defer probe\n"); + goto check_supplies_failed; + } + rc = kobject_set_name(&dev->kobj, "%s", psy->name); if (rc) goto kobject_set_name_failed; @@ -368,6 +528,7 @@ register_thermal_failed: device_del(dev); kobject_set_name_failed: device_add_failed: +check_supplies_failed: put_device(dev); success: return rc; diff --git a/drivers/power/reset/Kconfig b/drivers/power/reset/Kconfig index 1ae65b82286..349e9ae8090 100644 --- a/drivers/power/reset/Kconfig +++ b/drivers/power/reset/Kconfig @@ -30,3 +30,10 @@ config POWER_RESET_RESTART Some boards don't actually have the ability to power off. Instead they restart, and u-boot holds the SoC until the user presses a key. u-boot then boots into Linux. + +config POWER_RESET_VEXPRESS + bool + depends on POWER_RESET + help + Power off and reset support for the ARM Ltd. Versatile + Express boards. diff --git a/drivers/power/reset/Makefile b/drivers/power/reset/Makefile index 0f317f50c56..372807fd83f 100644 --- a/drivers/power/reset/Makefile +++ b/drivers/power/reset/Makefile @@ -1,3 +1,4 @@ obj-$(CONFIG_POWER_RESET_GPIO) += gpio-poweroff.o obj-$(CONFIG_POWER_RESET_QNAP) += qnap-poweroff.o -obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o
\ No newline at end of file +obj-$(CONFIG_POWER_RESET_RESTART) += restart-poweroff.o +obj-$(CONFIG_POWER_RESET_VEXPRESS) += vexpress-poweroff.o diff --git a/drivers/power/reset/vexpress-poweroff.c b/drivers/power/reset/vexpress-poweroff.c new file mode 100644 index 00000000000..469e6962b2c --- /dev/null +++ b/drivers/power/reset/vexpress-poweroff.c @@ -0,0 +1,146 @@ +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * 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. + * + * Copyright (C) 2012 ARM Limited + */ + +#include <linux/jiffies.h> +#include <linux/of.h> +#include <linux/of_device.h> +#include <linux/platform_device.h> +#include <linux/stat.h> +#include <linux/vexpress.h> + +#include <asm/system_misc.h> + +static void vexpress_reset_do(struct device *dev, const char *what) +{ + int err = -ENOENT; + struct vexpress_config_func *func = + vexpress_config_func_get_by_dev(dev); + + if (func) { + unsigned long timeout; + + err = vexpress_config_write(func, 0, 0); + + timeout = jiffies + HZ; + while (time_before(jiffies, timeout)) + cpu_relax(); + } + + dev_emerg(dev, "Unable to %s (%d)\n", what, err); +} + +static struct device *vexpress_power_off_device; + +static void vexpress_power_off(void) +{ + vexpress_reset_do(vexpress_power_off_device, "power off"); +} + +static struct device *vexpress_restart_device; + +static void vexpress_restart(char str, const char *cmd) +{ + vexpress_reset_do(vexpress_restart_device, "restart"); +} + +static ssize_t vexpress_reset_active_show(struct device *dev, + struct device_attribute *attr, char *buf) +{ + return sprintf(buf, "%d\n", vexpress_restart_device == dev); +} + +static ssize_t vexpress_reset_active_store(struct device *dev, + struct device_attribute *attr, const char *buf, size_t count) +{ + long value; + int err = kstrtol(buf, 0, &value); + + if (!err && value) + vexpress_restart_device = dev; + + return err ? err : count; +} + +DEVICE_ATTR(active, S_IRUGO | S_IWUSR, vexpress_reset_active_show, + vexpress_reset_active_store); + + +enum vexpress_reset_func { FUNC_RESET, FUNC_SHUTDOWN, FUNC_REBOOT }; + +static struct of_device_id vexpress_reset_of_match[] = { + { + .compatible = "arm,vexpress-reset", + .data = (void *)FUNC_RESET, + }, { + .compatible = "arm,vexpress-shutdown", + .data = (void *)FUNC_SHUTDOWN + }, { + .compatible = "arm,vexpress-reboot", + .data = (void *)FUNC_REBOOT + }, + {} +}; + +static int vexpress_reset_probe(struct platform_device *pdev) +{ + enum vexpress_reset_func func; + const struct of_device_id *match = + of_match_device(vexpress_reset_of_match, &pdev->dev); + + if (match) + func = (enum vexpress_reset_func)match->data; + else + func = pdev->id_entry->driver_data; + + switch (func) { + case FUNC_SHUTDOWN: + vexpress_power_off_device = &pdev->dev; + pm_power_off = vexpress_power_off; + break; + case FUNC_RESET: + if (!vexpress_restart_device) + vexpress_restart_device = &pdev->dev; + arm_pm_restart = vexpress_restart; + device_create_file(&pdev->dev, &dev_attr_active); + break; + case FUNC_REBOOT: + vexpress_restart_device = &pdev->dev; + arm_pm_restart = vexpress_restart; + device_create_file(&pdev->dev, &dev_attr_active); + break; + }; + + return 0; +} + +static const struct platform_device_id vexpress_reset_id_table[] = { + { .name = "vexpress-reset", .driver_data = FUNC_RESET, }, + { .name = "vexpress-shutdown", .driver_data = FUNC_SHUTDOWN, }, + { .name = "vexpress-reboot", .driver_data = FUNC_REBOOT, }, + {} +}; + +static struct platform_driver vexpress_reset_driver = { + .probe = vexpress_reset_probe, + .driver = { + .name = "vexpress-reset", + .of_match_table = vexpress_reset_of_match, + }, + .id_table = vexpress_reset_id_table, +}; + +static int __init vexpress_reset_init(void) +{ + return platform_driver_register(&vexpress_reset_driver); +} +device_initcall(vexpress_reset_init); diff --git a/drivers/power/rx51_battery.c b/drivers/power/rx51_battery.c index 8208888b844..1a1dcb831a1 100644 --- a/drivers/power/rx51_battery.c +++ b/drivers/power/rx51_battery.c @@ -119,7 +119,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di) /* First check for temperature in first direct table */ if (raw < ARRAY_SIZE(rx51_temp_table1)) - return rx51_temp_table1[raw] * 100; + return rx51_temp_table1[raw] * 10; /* Binary search RAW value in second inverse table */ while (max - min > 1) { @@ -132,7 +132,7 @@ static int rx51_battery_read_temperature(struct rx51_device_info *di) break; } - return (rx51_temp_table2_first - min) * 100; + return (rx51_temp_table2_first - min) * 10; } /* @@ -202,7 +202,7 @@ static int rx51_battery_probe(struct platform_device *pdev) struct rx51_device_info *di; int ret; - di = kzalloc(sizeof(*di), GFP_KERNEL); + di = devm_kzalloc(&pdev->dev, sizeof(*di), GFP_KERNEL); if (!di) return -ENOMEM; @@ -217,7 +217,6 @@ static int rx51_battery_probe(struct platform_device *pdev) ret = power_supply_register(di->dev, &di->bat); if (ret) { platform_set_drvdata(pdev, NULL); - kfree(di); return ret; } @@ -230,7 +229,6 @@ static int rx51_battery_remove(struct platform_device *pdev) power_supply_unregister(&di->bat); platform_set_drvdata(pdev, NULL); - kfree(di); return 0; } diff --git a/drivers/power/s3c_adc_battery.c b/drivers/power/s3c_adc_battery.c index d2ca989dcbd..5948ce058bd 100644 --- a/drivers/power/s3c_adc_battery.c +++ b/drivers/power/s3c_adc_battery.c @@ -145,14 +145,17 @@ static int s3c_adc_bat_get_property(struct power_supply *psy, int new_level; int full_volt; - const struct s3c_adc_bat_thresh *lut = bat->pdata->lut_noac; - unsigned int lut_size = bat->pdata->lut_noac_cnt; + const struct s3c_adc_bat_thresh *lut; + unsigned int lut_size; if (!bat) { dev_err(psy->dev, "no battery infos ?!\n"); return -EINVAL; } + lut = bat->pdata->lut_noac; + lut_size = bat->pdata->lut_noac_cnt; + if (bat->volt_value < 0 || bat->cur_value < 0 || jiffies_to_msecs(jiffies - bat->timestamp) > BAT_POLL_INTERVAL) { diff --git a/drivers/power/sbs-battery.c b/drivers/power/sbs-battery.c index 3960f0b2afe..c8c78a74e75 100644 --- a/drivers/power/sbs-battery.c +++ b/drivers/power/sbs-battery.c @@ -27,6 +27,7 @@ #include <linux/slab.h> #include <linux/interrupt.h> #include <linux/gpio.h> +#include <linux/of.h> #include <linux/power/sbs-battery.h> @@ -667,7 +668,6 @@ of_out: return pdata; } #else -#define sbs_dt_ids NULL static struct sbs_platform_data *sbs_of_populate_pdata( struct i2c_client *client) { @@ -820,10 +820,11 @@ static int sbs_remove(struct i2c_client *client) return 0; } -#if defined CONFIG_PM -static int sbs_suspend(struct i2c_client *client, - pm_message_t state) +#if defined CONFIG_PM_SLEEP + +static int sbs_suspend(struct device *dev) { + struct i2c_client *client = to_i2c_client(dev); struct sbs_info *chip = i2c_get_clientdata(client); s32 ret; @@ -838,11 +839,13 @@ static int sbs_suspend(struct i2c_client *client, return 0; } + +static SIMPLE_DEV_PM_OPS(sbs_pm_ops, sbs_suspend, NULL); +#define SBS_PM_OPS (&sbs_pm_ops) + #else -#define sbs_suspend NULL +#define SBS_PM_OPS NULL #endif -/* any smbus transaction will wake up sbs */ -#define sbs_resume NULL static const struct i2c_device_id sbs_id[] = { { "bq20z75", 0 }, @@ -854,12 +857,11 @@ MODULE_DEVICE_TABLE(i2c, sbs_id); static struct i2c_driver sbs_battery_driver = { .probe = sbs_probe, .remove = sbs_remove, - .suspend = sbs_suspend, - .resume = sbs_resume, .id_table = sbs_id, .driver = { .name = "sbs-battery", - .of_match_table = sbs_dt_ids, + .of_match_table = of_match_ptr(sbs_dt_ids), + .pm = SBS_PM_OPS, }, }; module_i2c_driver(sbs_battery_driver); diff --git a/drivers/power/test_power.c b/drivers/power/test_power.c index b99a452a4fd..0152f35dca5 100644 --- a/drivers/power/test_power.c +++ b/drivers/power/test_power.c @@ -30,6 +30,8 @@ static int battery_technology = POWER_SUPPLY_TECHNOLOGY_LION; static int battery_capacity = 50; static int battery_voltage = 3300; +static bool module_initialized; + static int test_power_get_ac_property(struct power_supply *psy, enum power_supply_property psp, union power_supply_propval *val) @@ -185,6 +187,7 @@ static int __init test_power_init(void) } } + module_initialized = true; return 0; failed: while (--i >= 0) @@ -209,6 +212,8 @@ static void __exit test_power_exit(void) for (i = 0; i < ARRAY_SIZE(test_power_supplies); i++) power_supply_unregister(&test_power_supplies[i]); + + module_initialized = false; } module_exit(test_power_exit); @@ -221,8 +226,8 @@ struct battery_property_map { }; static struct battery_property_map map_ac_online[] = { - { 0, "on" }, - { 1, "off" }, + { 0, "off" }, + { 1, "on" }, { -1, NULL }, }; @@ -295,10 +300,16 @@ static const char *map_get_key(struct battery_property_map *map, int value, return def_key; } +static inline void signal_power_supply_changed(struct power_supply *psy) +{ + if (module_initialized) + power_supply_changed(psy); +} + static int param_set_ac_online(const char *key, const struct kernel_param *kp) { ac_online = map_get_value(map_ac_online, key, ac_online); - power_supply_changed(&test_power_supplies[0]); + signal_power_supply_changed(&test_power_supplies[0]); return 0; } @@ -311,7 +322,7 @@ static int param_get_ac_online(char *buffer, const struct kernel_param *kp) static int param_set_usb_online(const char *key, const struct kernel_param *kp) { usb_online = map_get_value(map_ac_online, key, usb_online); - power_supply_changed(&test_power_supplies[2]); + signal_power_supply_changed(&test_power_supplies[2]); return 0; } @@ -325,7 +336,7 @@ static int param_set_battery_status(const char *key, const struct kernel_param *kp) { battery_status = map_get_value(map_status, key, battery_status); - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); return 0; } @@ -339,7 +350,7 @@ static int param_set_battery_health(const char *key, const struct kernel_param *kp) { battery_health = map_get_value(map_health, key, battery_health); - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); return 0; } @@ -353,7 +364,7 @@ static int param_set_battery_present(const char *key, const struct kernel_param *kp) { battery_present = map_get_value(map_present, key, battery_present); - power_supply_changed(&test_power_supplies[0]); + signal_power_supply_changed(&test_power_supplies[0]); return 0; } @@ -369,7 +380,7 @@ static int param_set_battery_technology(const char *key, { battery_technology = map_get_value(map_technology, key, battery_technology); - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); return 0; } @@ -390,7 +401,7 @@ static int param_set_battery_capacity(const char *key, return -EINVAL; battery_capacity = tmp; - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); return 0; } @@ -405,7 +416,7 @@ static int param_set_battery_voltage(const char *key, return -EINVAL; battery_voltage = tmp; - power_supply_changed(&test_power_supplies[1]); + signal_power_supply_changed(&test_power_supplies[1]); return 0; } diff --git a/drivers/power/tps65090-charger.c b/drivers/power/tps65090-charger.c new file mode 100644 index 00000000000..9fbca310a2a --- /dev/null +++ b/drivers/power/tps65090-charger.c @@ -0,0 +1,320 @@ +/* + * Battery charger driver for TI's tps65090 + * + * Copyright (c) 2013, NVIDIA CORPORATION. All rights reserved. + + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + + * This program is distributed in the hope 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, see <http://www.gnu.org/licenses/>. + */ +#include <linux/err.h> +#include <linux/init.h> +#include <linux/interrupt.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/slab.h> +#include <linux/delay.h> +#include <linux/platform_device.h> +#include <linux/power_supply.h> +#include <linux/mfd/tps65090.h> + +#define TPS65090_REG_INTR_STS 0x00 +#define TPS65090_REG_CG_CTRL0 0x04 +#define TPS65090_REG_CG_CTRL1 0x05 +#define TPS65090_REG_CG_CTRL2 0x06 +#define TPS65090_REG_CG_CTRL3 0x07 +#define TPS65090_REG_CG_CTRL4 0x08 +#define TPS65090_REG_CG_CTRL5 0x09 +#define TPS65090_REG_CG_STATUS1 0x0a +#define TPS65090_REG_CG_STATUS2 0x0b + +#define TPS65090_CHARGER_ENABLE BIT(0) +#define TPS65090_VACG BIT(1) +#define TPS65090_NOITERM BIT(5) + +struct tps65090_charger { + struct device *dev; + int ac_online; + int prev_ac_online; + int irq; + struct power_supply ac; + struct tps65090_platform_data *pdata; +}; + +static enum power_supply_property tps65090_ac_props[] = { + POWER_SUPPLY_PROP_ONLINE, +}; + +static int tps65090_low_chrg_current(struct tps65090_charger *charger) +{ + int ret; + + ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL5, + TPS65090_NOITERM); + if (ret < 0) { + dev_err(charger->dev, "%s(): error reading in register 0x%x\n", + __func__, TPS65090_REG_CG_CTRL5); + return ret; + } + return 0; +} + +static int tps65090_enable_charging(struct tps65090_charger *charger, + uint8_t enable) +{ + int ret; + uint8_t ctrl0 = 0; + + ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_CTRL0, + &ctrl0); + if (ret < 0) { + dev_err(charger->dev, "%s(): error reading in register 0x%x\n", + __func__, TPS65090_REG_CG_CTRL0); + return ret; + } + + ret = tps65090_write(charger->dev->parent, TPS65090_REG_CG_CTRL0, + (ctrl0 | TPS65090_CHARGER_ENABLE)); + if (ret < 0) { + dev_err(charger->dev, "%s(): error reading in register 0x%x\n", + __func__, TPS65090_REG_CG_CTRL0); + return ret; + } + return 0; +} + +static int tps65090_config_charger(struct tps65090_charger *charger) +{ + int ret; + + if (charger->pdata->enable_low_current_chrg) { + ret = tps65090_low_chrg_current(charger); + if (ret < 0) { + dev_err(charger->dev, + "error configuring low charge current\n"); + return ret; + } + } + + return 0; +} + +static int tps65090_ac_get_property(struct power_supply *psy, + enum power_supply_property psp, + union power_supply_propval *val) +{ + struct tps65090_charger *charger = container_of(psy, + struct tps65090_charger, ac); + + if (psp == POWER_SUPPLY_PROP_ONLINE) { + val->intval = charger->ac_online; + charger->prev_ac_online = charger->ac_online; + return 0; + } + return -EINVAL; +} + +static irqreturn_t tps65090_charger_isr(int irq, void *dev_id) +{ + struct tps65090_charger *charger = dev_id; + int ret; + uint8_t status1 = 0; + uint8_t intrsts = 0; + + ret = tps65090_read(charger->dev->parent, TPS65090_REG_CG_STATUS1, + &status1); + if (ret < 0) { + dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n", + __func__, TPS65090_REG_CG_STATUS1); + return IRQ_HANDLED; + } + msleep(75); + ret = tps65090_read(charger->dev->parent, TPS65090_REG_INTR_STS, + &intrsts); + if (ret < 0) { + dev_err(charger->dev, "%s(): Error in reading reg 0x%x\n", + __func__, TPS65090_REG_INTR_STS); + return IRQ_HANDLED; + } + + if (intrsts & TPS65090_VACG) { + ret = tps65090_enable_charging(charger, 1); + if (ret < 0) + return IRQ_HANDLED; + charger->ac_online = 1; + } else { + charger->ac_online = 0; + } + + if (charger->prev_ac_online != charger->ac_online) + power_supply_changed(&charger->ac); + + return IRQ_HANDLED; +} + +#if defined(CONFIG_OF) + +#include <linux/of_device.h> + +static struct tps65090_platform_data * + tps65090_parse_dt_charger_data(struct platform_device *pdev) +{ + struct tps65090_platform_data *pdata; + struct device_node *np = pdev->dev.of_node; + unsigned int prop; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) { + dev_err(&pdev->dev, "Memory alloc for tps65090_pdata failed\n"); + return NULL; + } + + prop = of_property_read_bool(np, "ti,enable-low-current-chrg"); + pdata->enable_low_current_chrg = prop; + + pdata->irq_base = -1; + + return pdata; + +} +#else +static struct tps65090_platform_data * + tps65090_parse_dt_charger_data(struct platform_device *pdev) +{ + return NULL; +} +#endif + +static int tps65090_charger_probe(struct platform_device *pdev) +{ + struct tps65090_charger *cdata; + struct tps65090_platform_data *pdata; + uint8_t status1 = 0; + int ret; + int irq; + + pdata = dev_get_platdata(pdev->dev.parent); + + if (!pdata && pdev->dev.of_node) + pdata = tps65090_parse_dt_charger_data(pdev); + + if (!pdata) { + dev_err(&pdev->dev, "%s():no platform data available\n", + __func__); + return -ENODEV; + } + + cdata = devm_kzalloc(&pdev->dev, sizeof(*cdata), GFP_KERNEL); + if (!cdata) { + dev_err(&pdev->dev, "failed to allocate memory status\n"); + return -ENOMEM; + } + + dev_set_drvdata(&pdev->dev, cdata); + + cdata->dev = &pdev->dev; + cdata->pdata = pdata; + + cdata->ac.name = "tps65090-ac"; + cdata->ac.type = POWER_SUPPLY_TYPE_MAINS; + cdata->ac.get_property = tps65090_ac_get_property; + cdata->ac.properties = tps65090_ac_props; + cdata->ac.num_properties = ARRAY_SIZE(tps65090_ac_props); + cdata->ac.supplied_to = pdata->supplied_to; + cdata->ac.num_supplicants = pdata->num_supplicants; + + ret = power_supply_register(&pdev->dev, &cdata->ac); + if (ret) { + dev_err(&pdev->dev, "failed: power supply register\n"); + return ret; + } + + irq = platform_get_irq(pdev, 0); + if (irq <= 0) { + dev_warn(&pdev->dev, "Unable to get charger irq = %d\n", irq); + ret = irq; + goto fail_unregister_supply; + } + + cdata->irq = irq; + + ret = devm_request_threaded_irq(&pdev->dev, irq, NULL, + tps65090_charger_isr, 0, "tps65090-charger", cdata); + if (ret) { + dev_err(cdata->dev, "Unable to register irq %d err %d\n", irq, + ret); + goto fail_free_irq; + } + + ret = tps65090_config_charger(cdata); + if (ret < 0) { + dev_err(&pdev->dev, "charger config failed, err %d\n", ret); + goto fail_free_irq; + } + + /* Check for charger presence */ + ret = tps65090_read(cdata->dev->parent, TPS65090_REG_CG_STATUS1, + &status1); + if (ret < 0) { + dev_err(cdata->dev, "%s(): Error in reading reg 0x%x", __func__, + TPS65090_REG_CG_STATUS1); + goto fail_free_irq; + } + + if (status1 != 0) { + ret = tps65090_enable_charging(cdata, 1); + if (ret < 0) { + dev_err(cdata->dev, "error enabling charger\n"); + goto fail_free_irq; + } + cdata->ac_online = 1; + power_supply_changed(&cdata->ac); + } + + return 0; + +fail_free_irq: + devm_free_irq(cdata->dev, irq, cdata); +fail_unregister_supply: + power_supply_unregister(&cdata->ac); + + return ret; +} + +static int tps65090_charger_remove(struct platform_device *pdev) +{ + struct tps65090_charger *cdata = dev_get_drvdata(&pdev->dev); + + devm_free_irq(cdata->dev, cdata->irq, cdata); + power_supply_unregister(&cdata->ac); + + return 0; +} + +static struct of_device_id of_tps65090_charger_match[] = { + { .compatible = "ti,tps65090-charger", }, + { /* end */ } +}; + +static struct platform_driver tps65090_charger_driver = { + .driver = { + .name = "tps65090-charger", + .of_match_table = of_tps65090_charger_match, + .owner = THIS_MODULE, + }, + .probe = tps65090_charger_probe, + .remove = tps65090_charger_remove, +}; +module_platform_driver(tps65090_charger_driver); + +MODULE_LICENSE("GPL v2"); +MODULE_AUTHOR("Syed Rafiuddin <srafiuddin@nvidia.com>"); +MODULE_DESCRIPTION("tps65090 battery charger driver"); diff --git a/drivers/power/twl4030_charger.c b/drivers/power/twl4030_charger.c index a69d0d11b54..bed458172dd 100644 --- a/drivers/power/twl4030_charger.c +++ b/drivers/power/twl4030_charger.c @@ -636,17 +636,7 @@ static struct platform_driver twl4030_bci_driver = { .remove = __exit_p(twl4030_bci_remove), }; -static int __init twl4030_bci_init(void) -{ - return platform_driver_probe(&twl4030_bci_driver, twl4030_bci_probe); -} -module_init(twl4030_bci_init); - -static void __exit twl4030_bci_exit(void) -{ - platform_driver_unregister(&twl4030_bci_driver); -} -module_exit(twl4030_bci_exit); +module_platform_driver_probe(twl4030_bci_driver, twl4030_bci_probe); MODULE_AUTHOR("Gražvydas Ignotas"); MODULE_DESCRIPTION("TWL4030 Battery Charger Interface driver"); diff --git a/drivers/power/wm831x_backup.c b/drivers/power/wm831x_backup.c index d9cc169f142..58cbb009b74 100644 --- a/drivers/power/wm831x_backup.c +++ b/drivers/power/wm831x_backup.c @@ -169,7 +169,8 @@ static int wm831x_backup_probe(struct platform_device *pdev) struct power_supply *backup; int ret; - devdata = kzalloc(sizeof(struct wm831x_backup), GFP_KERNEL); + devdata = devm_kzalloc(&pdev->dev, sizeof(struct wm831x_backup), + GFP_KERNEL); if (devdata == NULL) return -ENOMEM; @@ -197,14 +198,8 @@ static int wm831x_backup_probe(struct platform_device *pdev) backup->num_properties = ARRAY_SIZE(wm831x_backup_props); backup->get_property = wm831x_backup_get_prop; ret = power_supply_register(&pdev->dev, backup); - if (ret) - goto err_kmalloc; return ret; - -err_kmalloc: - kfree(devdata); - return ret; } static int wm831x_backup_remove(struct platform_device *pdev) @@ -213,7 +208,6 @@ static int wm831x_backup_remove(struct platform_device *pdev) power_supply_unregister(&devdata->backup); kfree(devdata->backup.name); - kfree(devdata); return 0; } diff --git a/drivers/scsi/aacraid/aacraid.h b/drivers/scsi/aacraid/aacraid.h index a6f7190c09a..9323d058706 100644 --- a/drivers/scsi/aacraid/aacraid.h +++ b/drivers/scsi/aacraid/aacraid.h @@ -12,7 +12,7 @@ *----------------------------------------------------------------------------*/ #ifndef AAC_DRIVER_BUILD -# define AAC_DRIVER_BUILD 30000 +# define AAC_DRIVER_BUILD 30200 # define AAC_DRIVER_BRANCH "-ms" #endif #define MAXIMUM_NUM_CONTAINERS 32 @@ -1918,6 +1918,10 @@ extern struct aac_common aac_config; #define MONITOR_PANIC 0x00000020 #define KERNEL_UP_AND_RUNNING 0x00000080 #define KERNEL_PANIC 0x00000100 +#define FLASH_UPD_PENDING 0x00002000 +#define FLASH_UPD_SUCCESS 0x00004000 +#define FLASH_UPD_FAILED 0x00008000 +#define FWUPD_TIMEOUT (5 * 60) /* * Doorbell bit defines diff --git a/drivers/scsi/aacraid/comminit.c b/drivers/scsi/aacraid/comminit.c index 3f759957f4b..177b094c779 100644 --- a/drivers/scsi/aacraid/comminit.c +++ b/drivers/scsi/aacraid/comminit.c @@ -214,7 +214,7 @@ int aac_send_shutdown(struct aac_dev * dev) cmd = (struct aac_close *) fib_data(fibctx); cmd->command = cpu_to_le32(VM_CloseAll); - cmd->cid = cpu_to_le32(0xffffffff); + cmd->cid = cpu_to_le32(0xfffffffe); status = aac_fib_send(ContainerCommand, fibctx, diff --git a/drivers/scsi/aacraid/src.c b/drivers/scsi/aacraid/src.c index e2e349204e7..0f56d8d7524 100644 --- a/drivers/scsi/aacraid/src.c +++ b/drivers/scsi/aacraid/src.c @@ -703,6 +703,28 @@ int aac_srcv_init(struct aac_dev *dev) !aac_src_restart_adapter(dev, 0)) ++restart; /* + * Check to see if flash update is running. + * Wait for the adapter to be up and running. Wait up to 5 minutes + */ + status = src_readl(dev, MUnit.OMR); + if (status & FLASH_UPD_PENDING) { + start = jiffies; + do { + status = src_readl(dev, MUnit.OMR); + if (time_after(jiffies, start+HZ*FWUPD_TIMEOUT)) { + printk(KERN_ERR "%s%d: adapter flash update failed.\n", + dev->name, instance); + goto error_iounmap; + } + } while (!(status & FLASH_UPD_SUCCESS) && + !(status & FLASH_UPD_FAILED)); + /* Delay 10 seconds. + * Because right now FW is doing a soft reset, + * do not read scratch pad register at this time + */ + ssleep(10); + } + /* * Check to see if the board panic'd while booting. */ status = src_readl(dev, MUnit.OMR); @@ -730,7 +752,9 @@ int aac_srcv_init(struct aac_dev *dev) /* * Wait for the adapter to be up and running. Wait up to 3 minutes */ - while (!((status = src_readl(dev, MUnit.OMR)) & KERNEL_UP_AND_RUNNING)) { + while (!((status = src_readl(dev, MUnit.OMR)) & + KERNEL_UP_AND_RUNNING) || + status == 0xffffffff) { if ((restart && (status & (KERNEL_PANIC|SELF_TEST_FAILED|MONITOR_PANIC))) || time_after(jiffies, start+HZ*startup_timeout)) { diff --git a/drivers/scsi/csiostor/csio_hw.c b/drivers/scsi/csiostor/csio_hw.c index bdd78fb4fc7..7dbaf58fab9 100644 --- a/drivers/scsi/csiostor/csio_hw.c +++ b/drivers/scsi/csiostor/csio_hw.c @@ -3892,7 +3892,6 @@ csio_process_fwevtq_entry(struct csio_hw *hw, void *wr, uint32_t len, struct csio_fl_dma_buf *flb, void *priv) { __u8 op; - __be64 *data; void *msg = NULL; uint32_t msg_len = 0; bool msg_sg = 0; @@ -3908,8 +3907,6 @@ csio_process_fwevtq_entry(struct csio_hw *hw, void *wr, uint32_t len, msg = (void *) flb; msg_len = flb->totlen; msg_sg = 1; - - data = (__be64 *) msg; } else if (op == CPL_FW6_MSG || op == CPL_FW4_MSG) { CSIO_INC_STATS(hw, n_cpl_fw6_msg); @@ -3917,8 +3914,6 @@ csio_process_fwevtq_entry(struct csio_hw *hw, void *wr, uint32_t len, msg = (void *)((uintptr_t)wr + sizeof(__be64)); msg_len = (op == CPL_FW6_MSG) ? sizeof(struct cpl_fw6_msg) : sizeof(struct cpl_fw4_msg); - - data = (__be64 *) msg; } else { csio_warn(hw, "unexpected CPL %#x on FW event queue\n", op); CSIO_INC_STATS(hw, n_cpl_unexp); diff --git a/drivers/scsi/device_handler/scsi_dh_alua.c b/drivers/scsi/device_handler/scsi_dh_alua.c index 6f4d8e6f32f..68adb8955d2 100644 --- a/drivers/scsi/device_handler/scsi_dh_alua.c +++ b/drivers/scsi/device_handler/scsi_dh_alua.c @@ -232,13 +232,13 @@ static void stpg_endio(struct request *req, int error) struct scsi_sense_hdr sense_hdr; unsigned err = SCSI_DH_OK; - if (error || host_byte(req->errors) != DID_OK || - msg_byte(req->errors) != COMMAND_COMPLETE) { + if (host_byte(req->errors) != DID_OK || + msg_byte(req->errors) != COMMAND_COMPLETE) { err = SCSI_DH_IO; goto done; } - if (h->senselen > 0) { + if (req->sense_len > 0) { err = scsi_normalize_sense(h->sense, SCSI_SENSE_BUFFERSIZE, &sense_hdr); if (!err) { @@ -255,7 +255,9 @@ static void stpg_endio(struct request *req, int error) ALUA_DH_NAME, sense_hdr.sense_key, sense_hdr.asc, sense_hdr.ascq); err = SCSI_DH_IO; - } + } else if (error) + err = SCSI_DH_IO; + if (err == SCSI_DH_OK) { h->state = TPGS_STATE_OPTIMIZED; sdev_printk(KERN_INFO, h->sdev, @@ -710,6 +712,10 @@ static int alua_set_params(struct scsi_device *sdev, const char *params) return result; } +static uint optimize_stpg; +module_param(optimize_stpg, uint, S_IRUGO|S_IWUSR); +MODULE_PARM_DESC(optimize_stpg, "Allow use of a non-optimized path, rather than sending a STPG, when implicit TPGS is supported (0=No,1=Yes). Default is 0."); + /* * alua_activate - activate a path * @sdev: device on the path to be activated @@ -731,6 +737,9 @@ static int alua_activate(struct scsi_device *sdev, if (err != SCSI_DH_OK) goto out; + if (optimize_stpg) + h->flags |= ALUA_OPTIMIZE_STPG; + if (h->tpgs & TPGS_MODE_EXPLICIT) { switch (h->state) { case TPGS_STATE_NONOPTIMIZED: diff --git a/drivers/scsi/isci/init.c b/drivers/scsi/isci/init.c index 2839baa82a5..d25d0d859f0 100644 --- a/drivers/scsi/isci/init.c +++ b/drivers/scsi/isci/init.c @@ -721,7 +721,7 @@ static void isci_pci_remove(struct pci_dev *pdev) } } -#ifdef CONFIG_PM +#ifdef CONFIG_PM_SLEEP static int isci_suspend(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); @@ -770,18 +770,16 @@ static int isci_resume(struct device *dev) return 0; } +#endif static SIMPLE_DEV_PM_OPS(isci_pm_ops, isci_suspend, isci_resume); -#endif static struct pci_driver isci_pci_driver = { .name = DRV_NAME, .id_table = isci_id_table, .probe = isci_pci_probe, .remove = isci_pci_remove, -#ifdef CONFIG_PM .driver.pm = &isci_pm_ops, -#endif }; static __init int isci_init(void) diff --git a/drivers/scsi/iscsi_tcp.c b/drivers/scsi/iscsi_tcp.c index 1b91ca0dc1e..9e2588a6881 100644 --- a/drivers/scsi/iscsi_tcp.c +++ b/drivers/scsi/iscsi_tcp.c @@ -370,17 +370,24 @@ static inline int iscsi_sw_tcp_xmit_qlen(struct iscsi_conn *conn) static int iscsi_sw_tcp_pdu_xmit(struct iscsi_task *task) { struct iscsi_conn *conn = task->conn; - int rc; + unsigned long pflags = current->flags; + int rc = 0; + + current->flags |= PF_MEMALLOC; while (iscsi_sw_tcp_xmit_qlen(conn)) { rc = iscsi_sw_tcp_xmit(conn); - if (rc == 0) - return -EAGAIN; + if (rc == 0) { + rc = -EAGAIN; + break; + } if (rc < 0) - return rc; + break; + rc = 0; } - return 0; + tsk_restore_flags(current, pflags, PF_MEMALLOC); + return rc; } /* @@ -665,6 +672,7 @@ iscsi_sw_tcp_conn_bind(struct iscsi_cls_session *cls_session, sk->sk_reuse = SK_CAN_REUSE; sk->sk_sndtimeo = 15 * HZ; /* FIXME: make it configurable */ sk->sk_allocation = GFP_ATOMIC; + sk_set_memalloc(sk); iscsi_sw_tcp_conn_set_callbacks(conn); tcp_sw_conn->sendpage = tcp_sw_conn->sock->ops->sendpage; diff --git a/drivers/scsi/libiscsi.c b/drivers/scsi/libiscsi.c index 82c3fd4bc93..5de94698450 100644 --- a/drivers/scsi/libiscsi.c +++ b/drivers/scsi/libiscsi.c @@ -507,7 +507,6 @@ static void iscsi_free_task(struct iscsi_task *task) kfifo_in(&session->cmdpool.queue, (void*)&task, sizeof(void*)); if (sc) { - task->sc = NULL; /* SCSI eh reuses commands to verify us */ sc->SCp.ptr = NULL; /* @@ -3142,7 +3141,7 @@ int iscsi_conn_bind(struct iscsi_cls_session *cls_session, } EXPORT_SYMBOL_GPL(iscsi_conn_bind); -static int iscsi_switch_str_param(char **param, char *new_val_buf) +int iscsi_switch_str_param(char **param, char *new_val_buf) { char *new_val; @@ -3159,6 +3158,7 @@ static int iscsi_switch_str_param(char **param, char *new_val_buf) *param = new_val; return 0; } +EXPORT_SYMBOL_GPL(iscsi_switch_str_param); int iscsi_set_param(struct iscsi_cls_conn *cls_conn, enum iscsi_param param, char *buf, int buflen) diff --git a/drivers/scsi/lpfc/lpfc_attr.c b/drivers/scsi/lpfc/lpfc_attr.c index a364cae9e98..9290713af25 100644 --- a/drivers/scsi/lpfc/lpfc_attr.c +++ b/drivers/scsi/lpfc/lpfc_attr.c @@ -692,7 +692,7 @@ lpfc_do_offline(struct lpfc_hba *phba, uint32_t type) */ for (i = 0; i < psli->num_rings; i++) { pring = &psli->ring[i]; - while (pring->txcmplq_cnt) { + while (!list_empty(&pring->txcmplq)) { msleep(10); if (cnt++ > 500) { /* 5 secs */ lpfc_printf_log(phba, @@ -2302,11 +2302,17 @@ static DEVICE_ATTR(lpfc_enable_npiv, S_IRUGO, lpfc_enable_npiv_show, NULL); LPFC_ATTR_R(fcf_failover_policy, 1, 1, 2, "FCF Fast failover=1 Priority failover=2"); -int lpfc_enable_rrq; +int lpfc_enable_rrq = 2; module_param(lpfc_enable_rrq, int, S_IRUGO); MODULE_PARM_DESC(lpfc_enable_rrq, "Enable RRQ functionality"); lpfc_param_show(enable_rrq); -lpfc_param_init(enable_rrq, 0, 0, 1); +/* +# lpfc_enable_rrq: Track XRI/OXID reuse after IO failures +# 0x0 = disabled, XRI/OXID use not tracked. +# 0x1 = XRI/OXID reuse is timed with ratov, RRQ sent. +# 0x2 = XRI/OXID reuse is timed with ratov, No RRQ sent. +*/ +lpfc_param_init(enable_rrq, 2, 0, 2); static DEVICE_ATTR(lpfc_enable_rrq, S_IRUGO, lpfc_enable_rrq_show, NULL); /* diff --git a/drivers/scsi/lpfc/lpfc_bsg.c b/drivers/scsi/lpfc/lpfc_bsg.c index f5d106456f1..88866689200 100644 --- a/drivers/scsi/lpfc/lpfc_bsg.c +++ b/drivers/scsi/lpfc/lpfc_bsg.c @@ -64,18 +64,14 @@ struct lpfc_bsg_event { struct list_head events_to_get; struct list_head events_to_see; - /* job waiting for this event to finish */ - struct fc_bsg_job *set_job; + /* driver data associated with the job */ + void *dd_data; }; struct lpfc_bsg_iocb { struct lpfc_iocbq *cmdiocbq; - struct lpfc_iocbq *rspiocbq; - struct lpfc_dmabuf *bmp; + struct lpfc_dmabuf *rmp; struct lpfc_nodelist *ndlp; - - /* job waiting for this iocb to finish */ - struct fc_bsg_job *set_job; }; struct lpfc_bsg_mbox { @@ -86,20 +82,13 @@ struct lpfc_bsg_mbox { uint32_t mbOffset; /* from app */ uint32_t inExtWLen; /* from app */ uint32_t outExtWLen; /* from app */ - - /* job waiting for this mbox command to finish */ - struct fc_bsg_job *set_job; }; #define MENLO_DID 0x0000FC0E struct lpfc_bsg_menlo { struct lpfc_iocbq *cmdiocbq; - struct lpfc_iocbq *rspiocbq; - struct lpfc_dmabuf *bmp; - - /* job waiting for this iocb to finish */ - struct fc_bsg_job *set_job; + struct lpfc_dmabuf *rmp; }; #define TYPE_EVT 1 @@ -108,6 +97,7 @@ struct lpfc_bsg_menlo { #define TYPE_MENLO 4 struct bsg_job_data { uint32_t type; + struct fc_bsg_job *set_job; /* job waiting for this iocb to finish */ union { struct lpfc_bsg_event *evt; struct lpfc_bsg_iocb iocb; @@ -141,6 +131,138 @@ struct lpfc_dmabufext { uint32_t flag; }; +static void +lpfc_free_bsg_buffers(struct lpfc_hba *phba, struct lpfc_dmabuf *mlist) +{ + struct lpfc_dmabuf *mlast, *next_mlast; + + if (mlist) { + list_for_each_entry_safe(mlast, next_mlast, &mlist->list, + list) { + lpfc_mbuf_free(phba, mlast->virt, mlast->phys); + list_del(&mlast->list); + kfree(mlast); + } + lpfc_mbuf_free(phba, mlist->virt, mlist->phys); + kfree(mlist); + } + return; +} + +static struct lpfc_dmabuf * +lpfc_alloc_bsg_buffers(struct lpfc_hba *phba, unsigned int size, + int outbound_buffers, struct ulp_bde64 *bpl, + int *bpl_entries) +{ + struct lpfc_dmabuf *mlist = NULL; + struct lpfc_dmabuf *mp; + unsigned int bytes_left = size; + + /* Verify we can support the size specified */ + if (!size || (size > (*bpl_entries * LPFC_BPL_SIZE))) + return NULL; + + /* Determine the number of dma buffers to allocate */ + *bpl_entries = (size % LPFC_BPL_SIZE ? size/LPFC_BPL_SIZE + 1 : + size/LPFC_BPL_SIZE); + + /* Allocate dma buffer and place in BPL passed */ + while (bytes_left) { + /* Allocate dma buffer */ + mp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!mp) { + if (mlist) + lpfc_free_bsg_buffers(phba, mlist); + return NULL; + } + + INIT_LIST_HEAD(&mp->list); + mp->virt = lpfc_mbuf_alloc(phba, MEM_PRI, &(mp->phys)); + + if (!mp->virt) { + kfree(mp); + if (mlist) + lpfc_free_bsg_buffers(phba, mlist); + return NULL; + } + + /* Queue it to a linked list */ + if (!mlist) + mlist = mp; + else + list_add_tail(&mp->list, &mlist->list); + + /* Add buffer to buffer pointer list */ + if (outbound_buffers) + bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; + else + bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64I; + bpl->addrLow = le32_to_cpu(putPaddrLow(mp->phys)); + bpl->addrHigh = le32_to_cpu(putPaddrHigh(mp->phys)); + bpl->tus.f.bdeSize = (uint16_t) + (bytes_left >= LPFC_BPL_SIZE ? LPFC_BPL_SIZE : + bytes_left); + bytes_left -= bpl->tus.f.bdeSize; + bpl->tus.w = le32_to_cpu(bpl->tus.w); + bpl++; + } + return mlist; +} + +static unsigned int +lpfc_bsg_copy_data(struct lpfc_dmabuf *dma_buffers, + struct fc_bsg_buffer *bsg_buffers, + unsigned int bytes_to_transfer, int to_buffers) +{ + + struct lpfc_dmabuf *mp; + unsigned int transfer_bytes, bytes_copied = 0; + unsigned int sg_offset, dma_offset; + unsigned char *dma_address, *sg_address; + struct scatterlist *sgel; + LIST_HEAD(temp_list); + + + list_splice_init(&dma_buffers->list, &temp_list); + list_add(&dma_buffers->list, &temp_list); + sg_offset = 0; + sgel = bsg_buffers->sg_list; + list_for_each_entry(mp, &temp_list, list) { + dma_offset = 0; + while (bytes_to_transfer && sgel && + (dma_offset < LPFC_BPL_SIZE)) { + dma_address = mp->virt + dma_offset; + if (sg_offset) { + /* Continue previous partial transfer of sg */ + sg_address = sg_virt(sgel) + sg_offset; + transfer_bytes = sgel->length - sg_offset; + } else { + sg_address = sg_virt(sgel); + transfer_bytes = sgel->length; + } + if (bytes_to_transfer < transfer_bytes) + transfer_bytes = bytes_to_transfer; + if (transfer_bytes > (LPFC_BPL_SIZE - dma_offset)) + transfer_bytes = LPFC_BPL_SIZE - dma_offset; + if (to_buffers) + memcpy(dma_address, sg_address, transfer_bytes); + else + memcpy(sg_address, dma_address, transfer_bytes); + dma_offset += transfer_bytes; + sg_offset += transfer_bytes; + bytes_to_transfer -= transfer_bytes; + bytes_copied += transfer_bytes; + if (sg_offset >= sgel->length) { + sg_offset = 0; + sgel = sg_next(sgel); + } + } + } + list_del_init(&dma_buffers->list); + list_splice(&temp_list, &dma_buffers->list); + return bytes_copied; +} + /** * lpfc_bsg_send_mgmt_cmd_cmp - lpfc_bsg_send_mgmt_cmd's completion handler * @phba: Pointer to HBA context object. @@ -166,62 +288,72 @@ lpfc_bsg_send_mgmt_cmd_cmp(struct lpfc_hba *phba, struct bsg_job_data *dd_data; struct fc_bsg_job *job; IOCB_t *rsp; - struct lpfc_dmabuf *bmp; + struct lpfc_dmabuf *bmp, *cmp, *rmp; struct lpfc_nodelist *ndlp; struct lpfc_bsg_iocb *iocb; unsigned long flags; + unsigned int rsp_size; int rc = 0; + dd_data = cmdiocbq->context1; + + /* Determine if job has been aborted */ spin_lock_irqsave(&phba->ct_ev_lock, flags); - dd_data = cmdiocbq->context2; - if (!dd_data) { - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - lpfc_sli_release_iocbq(phba, cmdiocbq); - return; + job = dd_data->set_job; + if (job) { + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); iocb = &dd_data->context_un.iocb; - job = iocb->set_job; - job->dd_data = NULL; /* so timeout handler does not reply */ - - bmp = iocb->bmp; + ndlp = iocb->ndlp; + rmp = iocb->rmp; + cmp = cmdiocbq->context2; + bmp = cmdiocbq->context3; rsp = &rspiocbq->iocb; - ndlp = cmdiocbq->context1; - pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - pci_unmap_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); + /* Copy the completed data or set the error status */ - if (rsp->ulpStatus) { - if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { - switch (rsp->un.ulpWord[4] & IOERR_PARAM_MASK) { - case IOERR_SEQUENCE_TIMEOUT: - rc = -ETIMEDOUT; - break; - case IOERR_INVALID_RPI: - rc = -EFAULT; - break; - default: + if (job) { + if (rsp->ulpStatus) { + if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { + switch (rsp->un.ulpWord[4] & IOERR_PARAM_MASK) { + case IOERR_SEQUENCE_TIMEOUT: + rc = -ETIMEDOUT; + break; + case IOERR_INVALID_RPI: + rc = -EFAULT; + break; + default: + rc = -EACCES; + break; + } + } else { rc = -EACCES; - break; } - } else - rc = -EACCES; - } else - job->reply->reply_payload_rcv_len = - rsp->un.genreq64.bdl.bdeSize; + } else { + rsp_size = rsp->un.genreq64.bdl.bdeSize; + job->reply->reply_payload_rcv_len = + lpfc_bsg_copy_data(rmp, &job->reply_payload, + rsp_size, 0); + } + } + lpfc_free_bsg_buffers(phba, cmp); + lpfc_free_bsg_buffers(phba, rmp); lpfc_mbuf_free(phba, bmp->virt, bmp->phys); + kfree(bmp); lpfc_sli_release_iocbq(phba, cmdiocbq); lpfc_nlp_put(ndlp); - kfree(bmp); kfree(dd_data); - /* make error code available to userspace */ - job->reply->result = rc; - /* complete the job back to userspace */ - job->job_done(job); - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Complete the job if the job is still active */ + + if (job) { + job->reply->result = rc; + job->job_done(job); + } return; } @@ -240,12 +372,9 @@ lpfc_bsg_send_mgmt_cmd(struct fc_bsg_job *job) uint32_t timeout; struct lpfc_iocbq *cmdiocbq = NULL; IOCB_t *cmd; - struct lpfc_dmabuf *bmp = NULL; + struct lpfc_dmabuf *bmp = NULL, *cmp = NULL, *rmp = NULL; int request_nseg; int reply_nseg; - struct scatterlist *sgel = NULL; - int numbde; - dma_addr_t busaddr; struct bsg_job_data *dd_data; uint32_t creg_val; int rc = 0; @@ -268,54 +397,50 @@ lpfc_bsg_send_mgmt_cmd(struct fc_bsg_job *job) goto no_ndlp; } - bmp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); - if (!bmp) { - rc = -ENOMEM; - goto free_ndlp; - } - if (ndlp->nlp_flag & NLP_ELS_SND_MASK) { rc = -ENODEV; - goto free_bmp; + goto free_ndlp; } cmdiocbq = lpfc_sli_get_iocbq(phba); if (!cmdiocbq) { rc = -ENOMEM; - goto free_bmp; + goto free_ndlp; } cmd = &cmdiocbq->iocb; + + bmp = kmalloc(sizeof(struct lpfc_dmabuf), GFP_KERNEL); + if (!bmp) { + rc = -ENOMEM; + goto free_cmdiocbq; + } bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); if (!bmp->virt) { rc = -ENOMEM; - goto free_cmdiocbq; + goto free_bmp; } INIT_LIST_HEAD(&bmp->list); + bpl = (struct ulp_bde64 *) bmp->virt; - request_nseg = pci_map_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - for_each_sg(job->request_payload.sg_list, sgel, request_nseg, numbde) { - busaddr = sg_dma_address(sgel); - bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; - bpl->tus.f.bdeSize = sg_dma_len(sgel); - bpl->tus.w = cpu_to_le32(bpl->tus.w); - bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); - bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); - bpl++; + request_nseg = LPFC_BPL_SIZE/sizeof(struct ulp_bde64); + cmp = lpfc_alloc_bsg_buffers(phba, job->request_payload.payload_len, + 1, bpl, &request_nseg); + if (!cmp) { + rc = -ENOMEM; + goto free_bmp; } + lpfc_bsg_copy_data(cmp, &job->request_payload, + job->request_payload.payload_len, 1); - reply_nseg = pci_map_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); - for_each_sg(job->reply_payload.sg_list, sgel, reply_nseg, numbde) { - busaddr = sg_dma_address(sgel); - bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64I; - bpl->tus.f.bdeSize = sg_dma_len(sgel); - bpl->tus.w = cpu_to_le32(bpl->tus.w); - bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); - bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); - bpl++; + bpl += request_nseg; + reply_nseg = LPFC_BPL_SIZE/sizeof(struct ulp_bde64) - request_nseg; + rmp = lpfc_alloc_bsg_buffers(phba, job->reply_payload.payload_len, 0, + bpl, &reply_nseg); + if (!rmp) { + rc = -ENOMEM; + goto free_cmp; } cmd->un.genreq64.bdl.ulpIoTag32 = 0; @@ -343,17 +468,20 @@ lpfc_bsg_send_mgmt_cmd(struct fc_bsg_job *job) cmd->ulpTimeout = timeout; cmdiocbq->iocb_cmpl = lpfc_bsg_send_mgmt_cmd_cmp; - cmdiocbq->context1 = ndlp; - cmdiocbq->context2 = dd_data; + cmdiocbq->context1 = dd_data; + cmdiocbq->context2 = cmp; + cmdiocbq->context3 = bmp; dd_data->type = TYPE_IOCB; + dd_data->set_job = job; dd_data->context_un.iocb.cmdiocbq = cmdiocbq; - dd_data->context_un.iocb.set_job = job; - dd_data->context_un.iocb.bmp = bmp; + dd_data->context_un.iocb.ndlp = ndlp; + dd_data->context_un.iocb.rmp = rmp; + job->dd_data = dd_data; if (phba->cfg_poll & DISABLE_FCP_RING_INT) { if (lpfc_readl(phba->HCregaddr, &creg_val)) { rc = -EIO ; - goto free_cmdiocbq; + goto free_rmp; } creg_val |= (HC_R0INT_ENA << LPFC_FCP_RING); writel(creg_val, phba->HCregaddr); @@ -368,19 +496,18 @@ lpfc_bsg_send_mgmt_cmd(struct fc_bsg_job *job) else rc = -EIO; - /* iocb failed so cleanup */ - pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - pci_unmap_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); - lpfc_mbuf_free(phba, bmp->virt, bmp->phys); - -free_cmdiocbq: - lpfc_sli_release_iocbq(phba, cmdiocbq); +free_rmp: + lpfc_free_bsg_buffers(phba, rmp); +free_cmp: + lpfc_free_bsg_buffers(phba, cmp); free_bmp: + if (bmp->virt) + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); kfree(bmp); +free_cmdiocbq: + lpfc_sli_release_iocbq(phba, cmdiocbq); free_ndlp: lpfc_nlp_put(ndlp); no_ndlp: @@ -418,67 +545,68 @@ lpfc_bsg_rport_els_cmp(struct lpfc_hba *phba, struct fc_bsg_job *job; IOCB_t *rsp; struct lpfc_nodelist *ndlp; - struct lpfc_dmabuf *pbuflist = NULL; + struct lpfc_dmabuf *pcmd = NULL, *prsp = NULL; struct fc_bsg_ctels_reply *els_reply; uint8_t *rjt_data; unsigned long flags; + unsigned int rsp_size; int rc = 0; - spin_lock_irqsave(&phba->ct_ev_lock, flags); dd_data = cmdiocbq->context1; - /* normal completion and timeout crossed paths, already done */ - if (!dd_data) { - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - return; - } + ndlp = dd_data->context_un.iocb.ndlp; + cmdiocbq->context1 = ndlp; - cmdiocbq->iocb_flag |= LPFC_IO_WAKE; - if (cmdiocbq->context2 && rspiocbq) - memcpy(&((struct lpfc_iocbq *)cmdiocbq->context2)->iocb, - &rspiocbq->iocb, sizeof(IOCB_t)); + /* Determine if job has been aborted */ + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - job = dd_data->context_un.iocb.set_job; - cmdiocbq = dd_data->context_un.iocb.cmdiocbq; - rspiocbq = dd_data->context_un.iocb.rspiocbq; rsp = &rspiocbq->iocb; - ndlp = dd_data->context_un.iocb.ndlp; + pcmd = (struct lpfc_dmabuf *)cmdiocbq->context2; + prsp = (struct lpfc_dmabuf *)pcmd->list.next; - pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - pci_unmap_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); + /* Copy the completed job data or determine the job status if job is + * still active + */ - if (job->reply->result == -EAGAIN) - rc = -EAGAIN; - else if (rsp->ulpStatus == IOSTAT_SUCCESS) - job->reply->reply_payload_rcv_len = - rsp->un.elsreq64.bdl.bdeSize; - else if (rsp->ulpStatus == IOSTAT_LS_RJT) { - job->reply->reply_payload_rcv_len = - sizeof(struct fc_bsg_ctels_reply); - /* LS_RJT data returned in word 4 */ - rjt_data = (uint8_t *)&rsp->un.ulpWord[4]; - els_reply = &job->reply->reply_data.ctels_reply; - els_reply->status = FC_CTELS_STATUS_REJECT; - els_reply->rjt_data.action = rjt_data[3]; - els_reply->rjt_data.reason_code = rjt_data[2]; - els_reply->rjt_data.reason_explanation = rjt_data[1]; - els_reply->rjt_data.vendor_unique = rjt_data[0]; - } else - rc = -EIO; + if (job) { + if (rsp->ulpStatus == IOSTAT_SUCCESS) { + rsp_size = rsp->un.elsreq64.bdl.bdeSize; + job->reply->reply_payload_rcv_len = + sg_copy_from_buffer(job->reply_payload.sg_list, + job->reply_payload.sg_cnt, + prsp->virt, + rsp_size); + } else if (rsp->ulpStatus == IOSTAT_LS_RJT) { + job->reply->reply_payload_rcv_len = + sizeof(struct fc_bsg_ctels_reply); + /* LS_RJT data returned in word 4 */ + rjt_data = (uint8_t *)&rsp->un.ulpWord[4]; + els_reply = &job->reply->reply_data.ctels_reply; + els_reply->status = FC_CTELS_STATUS_REJECT; + els_reply->rjt_data.action = rjt_data[3]; + els_reply->rjt_data.reason_code = rjt_data[2]; + els_reply->rjt_data.reason_explanation = rjt_data[1]; + els_reply->rjt_data.vendor_unique = rjt_data[0]; + } else { + rc = -EIO; + } + } - pbuflist = (struct lpfc_dmabuf *) cmdiocbq->context3; - lpfc_mbuf_free(phba, pbuflist->virt, pbuflist->phys); - lpfc_sli_release_iocbq(phba, rspiocbq); - lpfc_sli_release_iocbq(phba, cmdiocbq); lpfc_nlp_put(ndlp); + lpfc_els_free_iocb(phba, cmdiocbq); kfree(dd_data); - /* make error code available to userspace */ - job->reply->result = rc; - job->dd_data = NULL; - /* complete the job back to userspace */ - job->job_done(job); - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Complete the job if the job is still active */ + + if (job) { + job->reply->result = rc; + job->job_done(job); + } return; } @@ -496,19 +624,8 @@ lpfc_bsg_rport_els(struct fc_bsg_job *job) uint32_t elscmd; uint32_t cmdsize; uint32_t rspsize; - struct lpfc_iocbq *rspiocbq; struct lpfc_iocbq *cmdiocbq; - IOCB_t *rsp; uint16_t rpi = 0; - struct lpfc_dmabuf *pcmd; - struct lpfc_dmabuf *prsp; - struct lpfc_dmabuf *pbuflist = NULL; - struct ulp_bde64 *bpl; - int request_nseg; - int reply_nseg; - struct scatterlist *sgel = NULL; - int numbde; - dma_addr_t busaddr; struct bsg_job_data *dd_data; uint32_t creg_val; int rc = 0; @@ -516,6 +633,15 @@ lpfc_bsg_rport_els(struct fc_bsg_job *job) /* in case no data is transferred */ job->reply->reply_payload_rcv_len = 0; + /* verify the els command is not greater than the + * maximum ELS transfer size. + */ + + if (job->request_payload.payload_len > FCELSSIZE) { + rc = -EINVAL; + goto no_dd_data; + } + /* allocate our bsg tracking structure */ dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); if (!dd_data) { @@ -525,88 +651,51 @@ lpfc_bsg_rport_els(struct fc_bsg_job *job) goto no_dd_data; } - if (!lpfc_nlp_get(ndlp)) { - rc = -ENODEV; - goto free_dd_data; - } - elscmd = job->request->rqst_data.r_els.els_code; cmdsize = job->request_payload.payload_len; rspsize = job->reply_payload.payload_len; - rspiocbq = lpfc_sli_get_iocbq(phba); - if (!rspiocbq) { - lpfc_nlp_put(ndlp); - rc = -ENOMEM; + + if (!lpfc_nlp_get(ndlp)) { + rc = -ENODEV; goto free_dd_data; } - rsp = &rspiocbq->iocb; - rpi = ndlp->nlp_rpi; + /* We will use the allocated dma buffers by prep els iocb for command + * and response to ensure if the job times out and the request is freed, + * we won't be dma into memory that is no longer allocated to for the + * request. + */ cmdiocbq = lpfc_prep_els_iocb(vport, 1, cmdsize, 0, ndlp, ndlp->nlp_DID, elscmd); if (!cmdiocbq) { rc = -EIO; - goto free_rspiocbq; + goto release_ndlp; } - /* prep els iocb set context1 to the ndlp, context2 to the command - * dmabuf, context3 holds the data dmabuf - */ - pcmd = (struct lpfc_dmabuf *) cmdiocbq->context2; - prsp = (struct lpfc_dmabuf *) pcmd->list.next; - lpfc_mbuf_free(phba, pcmd->virt, pcmd->phys); - kfree(pcmd); - lpfc_mbuf_free(phba, prsp->virt, prsp->phys); - kfree(prsp); - cmdiocbq->context2 = NULL; - - pbuflist = (struct lpfc_dmabuf *) cmdiocbq->context3; - bpl = (struct ulp_bde64 *) pbuflist->virt; - - request_nseg = pci_map_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - for_each_sg(job->request_payload.sg_list, sgel, request_nseg, numbde) { - busaddr = sg_dma_address(sgel); - bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; - bpl->tus.f.bdeSize = sg_dma_len(sgel); - bpl->tus.w = cpu_to_le32(bpl->tus.w); - bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); - bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); - bpl++; - } + rpi = ndlp->nlp_rpi; + + /* Transfer the request payload to allocated command dma buffer */ + + sg_copy_to_buffer(job->request_payload.sg_list, + job->request_payload.sg_cnt, + ((struct lpfc_dmabuf *)cmdiocbq->context2)->virt, + cmdsize); - reply_nseg = pci_map_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); - for_each_sg(job->reply_payload.sg_list, sgel, reply_nseg, numbde) { - busaddr = sg_dma_address(sgel); - bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64I; - bpl->tus.f.bdeSize = sg_dma_len(sgel); - bpl->tus.w = cpu_to_le32(bpl->tus.w); - bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); - bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); - bpl++; - } - cmdiocbq->iocb.un.elsreq64.bdl.bdeSize = - (request_nseg + reply_nseg) * sizeof(struct ulp_bde64); if (phba->sli_rev == LPFC_SLI_REV4) cmdiocbq->iocb.ulpContext = phba->sli4_hba.rpi_ids[rpi]; else cmdiocbq->iocb.ulpContext = rpi; cmdiocbq->iocb_flag |= LPFC_IO_LIBDFC; - cmdiocbq->context1 = NULL; - cmdiocbq->context2 = NULL; - - cmdiocbq->iocb_cmpl = lpfc_bsg_rport_els_cmp; cmdiocbq->context1 = dd_data; cmdiocbq->context_un.ndlp = ndlp; - cmdiocbq->context2 = rspiocbq; + cmdiocbq->iocb_cmpl = lpfc_bsg_rport_els_cmp; dd_data->type = TYPE_IOCB; + dd_data->set_job = job; dd_data->context_un.iocb.cmdiocbq = cmdiocbq; - dd_data->context_un.iocb.rspiocbq = rspiocbq; - dd_data->context_un.iocb.set_job = job; - dd_data->context_un.iocb.bmp = NULL; dd_data->context_un.iocb.ndlp = ndlp; + dd_data->context_un.iocb.rmp = NULL; + job->dd_data = dd_data; if (phba->cfg_poll & DISABLE_FCP_RING_INT) { if (lpfc_readl(phba->HCregaddr, &creg_val)) { @@ -617,8 +706,9 @@ lpfc_bsg_rport_els(struct fc_bsg_job *job) writel(creg_val, phba->HCregaddr); readl(phba->HCregaddr); /* flush */ } + rc = lpfc_sli_issue_iocb(phba, LPFC_ELS_RING, cmdiocbq, 0); - lpfc_nlp_put(ndlp); + if (rc == IOCB_SUCCESS) return 0; /* done for now */ else if (rc == IOCB_BUSY) @@ -627,17 +717,12 @@ lpfc_bsg_rport_els(struct fc_bsg_job *job) rc = -EIO; linkdown_err: - pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - pci_unmap_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); - lpfc_mbuf_free(phba, pbuflist->virt, pbuflist->phys); - - lpfc_sli_release_iocbq(phba, cmdiocbq); + cmdiocbq->context1 = ndlp; + lpfc_els_free_iocb(phba, cmdiocbq); -free_rspiocbq: - lpfc_sli_release_iocbq(phba, rspiocbq); +release_ndlp: + lpfc_nlp_put(ndlp); free_dd_data: kfree(dd_data); @@ -680,6 +765,7 @@ lpfc_bsg_event_free(struct kref *kref) kfree(ed); } + kfree(evt->dd_data); kfree(evt); } @@ -723,6 +809,7 @@ lpfc_bsg_event_new(uint32_t ev_mask, int ev_reg_id, uint32_t ev_req_id) evt->req_id = ev_req_id; evt->reg_id = ev_reg_id; evt->wait_time_stamp = jiffies; + evt->dd_data = NULL; init_waitqueue_head(&evt->wq); kref_init(&evt->kref); return evt; @@ -790,6 +877,7 @@ lpfc_bsg_ct_unsol_event(struct lpfc_hba *phba, struct lpfc_sli_ring *pring, struct lpfc_hbq_entry *hbqe; struct lpfc_sli_ct_request *ct_req; struct fc_bsg_job *job = NULL; + struct bsg_job_data *dd_data = NULL; unsigned long flags; int size = 0; @@ -986,10 +1074,11 @@ lpfc_bsg_ct_unsol_event(struct lpfc_hba *phba, struct lpfc_sli_ring *pring, } list_move(evt->events_to_see.prev, &evt->events_to_get); - lpfc_bsg_event_unref(evt); - job = evt->set_job; - evt->set_job = NULL; + dd_data = (struct bsg_job_data *)evt->dd_data; + job = dd_data->set_job; + dd_data->set_job = NULL; + lpfc_bsg_event_unref(evt); if (job) { job->reply->reply_payload_rcv_len = size; /* make error code available to userspace */ @@ -1078,14 +1167,6 @@ lpfc_bsg_hba_set_event(struct fc_bsg_job *job) goto job_error; } - dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); - if (dd_data == NULL) { - lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, - "2734 Failed allocation of dd_data\n"); - rc = -ENOMEM; - goto job_error; - } - event_req = (struct set_ct_event *) job->request->rqst_data.h_vendor.vendor_cmd; ev_mask = ((uint32_t)(unsigned long)event_req->type_mask & @@ -1095,6 +1176,7 @@ lpfc_bsg_hba_set_event(struct fc_bsg_job *job) if (evt->reg_id == event_req->ev_reg_id) { lpfc_bsg_event_ref(evt); evt->wait_time_stamp = jiffies; + dd_data = (struct bsg_job_data *)evt->dd_data; break; } } @@ -1102,6 +1184,13 @@ lpfc_bsg_hba_set_event(struct fc_bsg_job *job) if (&evt->node == &phba->ct_ev_waiters) { /* no event waiting struct yet - first call */ + dd_data = kmalloc(sizeof(struct bsg_job_data), GFP_KERNEL); + if (dd_data == NULL) { + lpfc_printf_log(phba, KERN_WARNING, LOG_LIBDFC, + "2734 Failed allocation of dd_data\n"); + rc = -ENOMEM; + goto job_error; + } evt = lpfc_bsg_event_new(ev_mask, event_req->ev_reg_id, event_req->ev_req_id); if (!evt) { @@ -1111,7 +1200,10 @@ lpfc_bsg_hba_set_event(struct fc_bsg_job *job) rc = -ENOMEM; goto job_error; } - + dd_data->type = TYPE_EVT; + dd_data->set_job = NULL; + dd_data->context_un.evt = evt; + evt->dd_data = (void *)dd_data; spin_lock_irqsave(&phba->ct_ev_lock, flags); list_add(&evt->node, &phba->ct_ev_waiters); lpfc_bsg_event_ref(evt); @@ -1121,9 +1213,7 @@ lpfc_bsg_hba_set_event(struct fc_bsg_job *job) spin_lock_irqsave(&phba->ct_ev_lock, flags); evt->waiting = 1; - dd_data->type = TYPE_EVT; - dd_data->context_un.evt = evt; - evt->set_job = job; /* for unsolicited command */ + dd_data->set_job = job; /* for unsolicited command */ job->dd_data = dd_data; /* for fc transport timeout callback*/ spin_unlock_irqrestore(&phba->ct_ev_lock, flags); return 0; /* call job done later */ @@ -1252,57 +1342,64 @@ lpfc_issue_ct_rsp_cmp(struct lpfc_hba *phba, struct bsg_job_data *dd_data; struct fc_bsg_job *job; IOCB_t *rsp; - struct lpfc_dmabuf *bmp; + struct lpfc_dmabuf *bmp, *cmp; struct lpfc_nodelist *ndlp; unsigned long flags; int rc = 0; + dd_data = cmdiocbq->context1; + + /* Determine if job has been aborted */ spin_lock_irqsave(&phba->ct_ev_lock, flags); - dd_data = cmdiocbq->context2; - /* normal completion and timeout crossed paths, already done */ - if (!dd_data) { - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - return; + job = dd_data->set_job; + if (job) { + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - job = dd_data->context_un.iocb.set_job; - bmp = dd_data->context_un.iocb.bmp; - rsp = &rspiocbq->iocb; ndlp = dd_data->context_un.iocb.ndlp; + cmp = cmdiocbq->context2; + bmp = cmdiocbq->context3; + rsp = &rspiocbq->iocb; - pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); + /* Copy the completed job data or set the error status */ - if (rsp->ulpStatus) { - if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { - switch (rsp->un.ulpWord[4] & IOERR_PARAM_MASK) { - case IOERR_SEQUENCE_TIMEOUT: - rc = -ETIMEDOUT; - break; - case IOERR_INVALID_RPI: - rc = -EFAULT; - break; - default: + if (job) { + if (rsp->ulpStatus) { + if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { + switch (rsp->un.ulpWord[4] & IOERR_PARAM_MASK) { + case IOERR_SEQUENCE_TIMEOUT: + rc = -ETIMEDOUT; + break; + case IOERR_INVALID_RPI: + rc = -EFAULT; + break; + default: + rc = -EACCES; + break; + } + } else { rc = -EACCES; - break; } - } else - rc = -EACCES; - } else - job->reply->reply_payload_rcv_len = - rsp->un.genreq64.bdl.bdeSize; + } else { + job->reply->reply_payload_rcv_len = 0; + } + } + lpfc_free_bsg_buffers(phba, cmp); lpfc_mbuf_free(phba, bmp->virt, bmp->phys); + kfree(bmp); lpfc_sli_release_iocbq(phba, cmdiocbq); lpfc_nlp_put(ndlp); - kfree(bmp); kfree(dd_data); - /* make error code available to userspace */ - job->reply->result = rc; - job->dd_data = NULL; - /* complete the job back to userspace */ - job->job_done(job); - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Complete the job if the job is still active */ + + if (job) { + job->reply->result = rc; + job->job_done(job); + } return; } @@ -1316,7 +1413,8 @@ lpfc_issue_ct_rsp_cmp(struct lpfc_hba *phba, **/ static int lpfc_issue_ct_rsp(struct lpfc_hba *phba, struct fc_bsg_job *job, uint32_t tag, - struct lpfc_dmabuf *bmp, int num_entry) + struct lpfc_dmabuf *cmp, struct lpfc_dmabuf *bmp, + int num_entry) { IOCB_t *icmd; struct lpfc_iocbq *ctiocb = NULL; @@ -1377,7 +1475,7 @@ lpfc_issue_ct_rsp(struct lpfc_hba *phba, struct fc_bsg_job *job, uint32_t tag, /* Check if the ndlp is active */ if (!ndlp || !NLP_CHK_NODE_ACT(ndlp)) { - rc = -IOCB_ERROR; + rc = IOCB_ERROR; goto issue_ct_rsp_exit; } @@ -1385,7 +1483,7 @@ lpfc_issue_ct_rsp(struct lpfc_hba *phba, struct fc_bsg_job *job, uint32_t tag, * we respond */ if (!lpfc_nlp_get(ndlp)) { - rc = -IOCB_ERROR; + rc = IOCB_ERROR; goto issue_ct_rsp_exit; } @@ -1407,17 +1505,17 @@ lpfc_issue_ct_rsp(struct lpfc_hba *phba, struct fc_bsg_job *job, uint32_t tag, ctiocb->iocb_cmpl = NULL; ctiocb->iocb_flag |= LPFC_IO_LIBDFC; ctiocb->vport = phba->pport; + ctiocb->context1 = dd_data; + ctiocb->context2 = cmp; ctiocb->context3 = bmp; - ctiocb->iocb_cmpl = lpfc_issue_ct_rsp_cmp; - ctiocb->context2 = dd_data; - ctiocb->context1 = ndlp; + dd_data->type = TYPE_IOCB; + dd_data->set_job = job; dd_data->context_un.iocb.cmdiocbq = ctiocb; - dd_data->context_un.iocb.rspiocbq = NULL; - dd_data->context_un.iocb.set_job = job; - dd_data->context_un.iocb.bmp = bmp; dd_data->context_un.iocb.ndlp = ndlp; + dd_data->context_un.iocb.rmp = NULL; + job->dd_data = dd_data; if (phba->cfg_poll & DISABLE_FCP_RING_INT) { if (lpfc_readl(phba->HCregaddr, &creg_val)) { @@ -1454,11 +1552,8 @@ lpfc_bsg_send_mgmt_rsp(struct fc_bsg_job *job) struct send_mgmt_resp *mgmt_resp = (struct send_mgmt_resp *) job->request->rqst_data.h_vendor.vendor_cmd; struct ulp_bde64 *bpl; - struct lpfc_dmabuf *bmp = NULL; - struct scatterlist *sgel = NULL; - int request_nseg; - int numbde; - dma_addr_t busaddr; + struct lpfc_dmabuf *bmp = NULL, *cmp = NULL; + int bpl_entries; uint32_t tag = mgmt_resp->tag; unsigned long reqbfrcnt = (unsigned long)job->request_payload.payload_len; @@ -1486,30 +1581,28 @@ lpfc_bsg_send_mgmt_rsp(struct fc_bsg_job *job) INIT_LIST_HEAD(&bmp->list); bpl = (struct ulp_bde64 *) bmp->virt; - request_nseg = pci_map_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - for_each_sg(job->request_payload.sg_list, sgel, request_nseg, numbde) { - busaddr = sg_dma_address(sgel); - bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; - bpl->tus.f.bdeSize = sg_dma_len(sgel); - bpl->tus.w = cpu_to_le32(bpl->tus.w); - bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); - bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); - bpl++; + bpl_entries = (LPFC_BPL_SIZE/sizeof(struct ulp_bde64)); + cmp = lpfc_alloc_bsg_buffers(phba, job->request_payload.payload_len, + 1, bpl, &bpl_entries); + if (!cmp) { + rc = -ENOMEM; + goto send_mgmt_rsp_free_bmp; } + lpfc_bsg_copy_data(cmp, &job->request_payload, + job->request_payload.payload_len, 1); - rc = lpfc_issue_ct_rsp(phba, job, tag, bmp, request_nseg); + rc = lpfc_issue_ct_rsp(phba, job, tag, cmp, bmp, bpl_entries); if (rc == IOCB_SUCCESS) return 0; /* done for now */ - /* TBD need to handle a timeout */ - pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); rc = -EACCES; - lpfc_mbuf_free(phba, bmp->virt, bmp->phys); + + lpfc_free_bsg_buffers(phba, cmp); send_mgmt_rsp_free_bmp: + if (bmp->virt) + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); kfree(bmp); send_mgmt_rsp_exit: /* make error code available to userspace */ @@ -1559,7 +1652,7 @@ lpfc_bsg_diag_mode_enter(struct lpfc_hba *phba) scsi_block_requests(shost); } - while (pring->txcmplq_cnt) { + while (!list_empty(&pring->txcmplq)) { if (i++ > 500) /* wait up to 5 seconds */ break; msleep(10); @@ -3193,13 +3286,7 @@ lpfc_bsg_issue_mbox_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) unsigned long flags; uint8_t *pmb, *pmb_buf; - spin_lock_irqsave(&phba->ct_ev_lock, flags); dd_data = pmboxq->context1; - /* job already timed out? */ - if (!dd_data) { - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - return; - } /* * The outgoing buffer is readily referred from the dma buffer, @@ -3209,29 +3296,33 @@ lpfc_bsg_issue_mbox_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) pmb_buf = (uint8_t *)dd_data->context_un.mbox.mb; memcpy(pmb_buf, pmb, sizeof(MAILBOX_t)); - job = dd_data->context_un.mbox.set_job; + /* Determine if job has been aborted */ + + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Copy the mailbox data to the job if it is still active */ + if (job) { size = job->reply_payload.payload_len; job->reply->reply_payload_rcv_len = sg_copy_from_buffer(job->reply_payload.sg_list, job->reply_payload.sg_cnt, pmb_buf, size); - /* need to hold the lock until we set job->dd_data to NULL - * to hold off the timeout handler returning to the mid-layer - * while we are still processing the job. - */ - job->dd_data = NULL; - dd_data->context_un.mbox.set_job = NULL; - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - } else { - dd_data->context_un.mbox.set_job = NULL; - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); } + dd_data->set_job = NULL; mempool_free(dd_data->context_un.mbox.pmboxq, phba->mbox_mem_pool); lpfc_bsg_dma_page_free(phba, dd_data->context_un.mbox.dmabuffers); kfree(dd_data); + /* Complete the job if the job is still active */ + if (job) { job->reply->result = 0; job->job_done(job); @@ -3377,19 +3468,22 @@ lpfc_bsg_issue_mbox_ext_handle_job(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) struct lpfc_sli_config_mbox *sli_cfg_mbx; uint8_t *pmbx; - spin_lock_irqsave(&phba->ct_ev_lock, flags); dd_data = pmboxq->context1; - /* has the job already timed out? */ - if (!dd_data) { - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - job = NULL; - goto job_done_out; + + /* Determine if job has been aborted */ + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); /* * The outgoing buffer is readily referred from the dma buffer, * just need to get header part from mailboxq structure. */ + pmb = (uint8_t *)&pmboxq->u.mb; pmb_buf = (uint8_t *)dd_data->context_un.mbox.mb; /* Copy the byte swapped response mailbox back to the user */ @@ -3406,21 +3500,18 @@ lpfc_bsg_issue_mbox_ext_handle_job(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) sli_cfg_mbx->un.sli_config_emb0_subsys.mse[0].buf_len); } - job = dd_data->context_un.mbox.set_job; + /* Complete the job if the job is still active */ + if (job) { size = job->reply_payload.payload_len; job->reply->reply_payload_rcv_len = sg_copy_from_buffer(job->reply_payload.sg_list, job->reply_payload.sg_cnt, pmb_buf, size); + /* result for successful */ job->reply->result = 0; - job->dd_data = NULL; - /* need to hold the lock util we set job->dd_data to NULL - * to hold off the timeout handler from midlayer to take - * any action. - */ - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, "2937 SLI_CONFIG ext-buffer maibox command " "(x%x/x%x) complete bsg job done, bsize:%d\n", @@ -3431,20 +3522,18 @@ lpfc_bsg_issue_mbox_ext_handle_job(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) phba->mbox_ext_buf_ctx.mboxType, dma_ebuf, sta_pos_addr, phba->mbox_ext_buf_ctx.mbx_dmabuf, 0); - } else - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - -job_done_out: - if (!job) + } else { lpfc_printf_log(phba, KERN_ERR, LOG_LIBDFC, "2938 SLI_CONFIG ext-buffer maibox " "command (x%x/x%x) failure, rc:x%x\n", phba->mbox_ext_buf_ctx.nembType, phba->mbox_ext_buf_ctx.mboxType, rc); + } + + /* state change */ phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_DONE; kfree(dd_data); - return job; } @@ -3461,8 +3550,10 @@ lpfc_bsg_issue_read_mbox_ext_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) { struct fc_bsg_job *job; + job = lpfc_bsg_issue_mbox_ext_handle_job(phba, pmboxq); + /* handle the BSG job with mailbox command */ - if (phba->mbox_ext_buf_ctx.state == LPFC_BSG_MBOX_ABTS) + if (!job) pmboxq->u.mb.mbxStatus = MBXERR_ERROR; lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, @@ -3470,15 +3561,13 @@ lpfc_bsg_issue_read_mbox_ext_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) "complete, ctxState:x%x, mbxStatus:x%x\n", phba->mbox_ext_buf_ctx.state, pmboxq->u.mb.mbxStatus); - job = lpfc_bsg_issue_mbox_ext_handle_job(phba, pmboxq); - if (pmboxq->u.mb.mbxStatus || phba->mbox_ext_buf_ctx.numBuf == 1) lpfc_bsg_mbox_ext_session_reset(phba); /* free base driver mailbox structure memory */ mempool_free(pmboxq, phba->mbox_mem_pool); - /* complete the bsg job if we have it */ + /* if the job is still active, call job done */ if (job) job->job_done(job); @@ -3498,8 +3587,10 @@ lpfc_bsg_issue_write_mbox_ext_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) { struct fc_bsg_job *job; + job = lpfc_bsg_issue_mbox_ext_handle_job(phba, pmboxq); + /* handle the BSG job with the mailbox command */ - if (phba->mbox_ext_buf_ctx.state == LPFC_BSG_MBOX_ABTS) + if (!job) pmboxq->u.mb.mbxStatus = MBXERR_ERROR; lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, @@ -3507,13 +3598,11 @@ lpfc_bsg_issue_write_mbox_ext_cmpl(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmboxq) "complete, ctxState:x%x, mbxStatus:x%x\n", phba->mbox_ext_buf_ctx.state, pmboxq->u.mb.mbxStatus); - job = lpfc_bsg_issue_mbox_ext_handle_job(phba, pmboxq); - /* free all memory, including dma buffers */ mempool_free(pmboxq, phba->mbox_mem_pool); lpfc_bsg_mbox_ext_session_reset(phba); - /* complete the bsg job if we have it */ + /* if the job is still active, call job done */ if (job) job->job_done(job); @@ -3759,9 +3848,9 @@ lpfc_bsg_sli_cfg_read_cmd_ext(struct lpfc_hba *phba, struct fc_bsg_job *job, /* context fields to callback function */ pmboxq->context1 = dd_data; dd_data->type = TYPE_MBOX; + dd_data->set_job = job; dd_data->context_un.mbox.pmboxq = pmboxq; dd_data->context_un.mbox.mb = (MAILBOX_t *)pmbx; - dd_data->context_un.mbox.set_job = job; job->dd_data = dd_data; /* state change */ @@ -3928,14 +4017,14 @@ lpfc_bsg_sli_cfg_write_cmd_ext(struct lpfc_hba *phba, struct fc_bsg_job *job, /* context fields to callback function */ pmboxq->context1 = dd_data; dd_data->type = TYPE_MBOX; + dd_data->set_job = job; dd_data->context_un.mbox.pmboxq = pmboxq; dd_data->context_un.mbox.mb = (MAILBOX_t *)mbx; - dd_data->context_un.mbox.set_job = job; job->dd_data = dd_data; /* state change */ - phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_PORT; + phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_PORT; rc = lpfc_sli_issue_mbox(phba, pmboxq, MBX_NOWAIT); if ((rc == MBX_SUCCESS) || (rc == MBX_BUSY)) { lpfc_printf_log(phba, KERN_INFO, LOG_LIBDFC, @@ -3951,6 +4040,7 @@ lpfc_bsg_sli_cfg_write_cmd_ext(struct lpfc_hba *phba, struct fc_bsg_job *job, } /* wait for additoinal external buffers */ + job->reply->result = 0; job->job_done(job); return SLI_CONFIG_HANDLED; @@ -4268,9 +4358,9 @@ lpfc_bsg_write_ebuf_set(struct lpfc_hba *phba, struct fc_bsg_job *job, /* context fields to callback function */ pmboxq->context1 = dd_data; dd_data->type = TYPE_MBOX; + dd_data->set_job = job; dd_data->context_un.mbox.pmboxq = pmboxq; dd_data->context_un.mbox.mb = (MAILBOX_t *)pbuf; - dd_data->context_un.mbox.set_job = job; job->dd_data = dd_data; /* state change */ @@ -4455,7 +4545,6 @@ lpfc_bsg_issue_mbox(struct lpfc_hba *phba, struct fc_bsg_job *job, uint8_t *from; uint32_t size; - /* in case no data is transferred */ job->reply->reply_payload_rcv_len = 0; @@ -4681,9 +4770,9 @@ lpfc_bsg_issue_mbox(struct lpfc_hba *phba, struct fc_bsg_job *job, /* setup context field to pass wait_queue pointer to wake function */ pmboxq->context1 = dd_data; dd_data->type = TYPE_MBOX; + dd_data->set_job = job; dd_data->context_un.mbox.pmboxq = pmboxq; dd_data->context_un.mbox.mb = (MAILBOX_t *)pmbx; - dd_data->context_un.mbox.set_job = job; dd_data->context_un.mbox.ext = ext; dd_data->context_un.mbox.mbOffset = mbox_req->mbOffset; dd_data->context_un.mbox.inExtWLen = mbox_req->inExtWLen; @@ -4797,75 +4886,79 @@ lpfc_bsg_menlo_cmd_cmp(struct lpfc_hba *phba, struct bsg_job_data *dd_data; struct fc_bsg_job *job; IOCB_t *rsp; - struct lpfc_dmabuf *bmp; + struct lpfc_dmabuf *bmp, *cmp, *rmp; struct lpfc_bsg_menlo *menlo; unsigned long flags; struct menlo_response *menlo_resp; + unsigned int rsp_size; int rc = 0; - spin_lock_irqsave(&phba->ct_ev_lock, flags); dd_data = cmdiocbq->context1; - if (!dd_data) { - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - return; - } - + cmp = cmdiocbq->context2; + bmp = cmdiocbq->context3; menlo = &dd_data->context_un.menlo; - job = menlo->set_job; - job->dd_data = NULL; /* so timeout handler does not reply */ - - spin_lock(&phba->hbalock); - cmdiocbq->iocb_flag |= LPFC_IO_WAKE; - if (cmdiocbq->context2 && rspiocbq) - memcpy(&((struct lpfc_iocbq *)cmdiocbq->context2)->iocb, - &rspiocbq->iocb, sizeof(IOCB_t)); - spin_unlock(&phba->hbalock); - - bmp = menlo->bmp; - rspiocbq = menlo->rspiocbq; + rmp = menlo->rmp; rsp = &rspiocbq->iocb; - pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - pci_unmap_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); + /* Determine if job has been aborted */ + spin_lock_irqsave(&phba->ct_ev_lock, flags); + job = dd_data->set_job; + if (job) { + /* Prevent timeout handling from trying to abort job */ + job->dd_data = NULL; + } + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Copy the job data or set the failing status for the job */ - /* always return the xri, this would be used in the case - * of a menlo download to allow the data to be sent as a continuation - * of the exchange. - */ - menlo_resp = (struct menlo_response *) - job->reply->reply_data.vendor_reply.vendor_rsp; - menlo_resp->xri = rsp->ulpContext; - if (rsp->ulpStatus) { - if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { - switch (rsp->un.ulpWord[4] & IOERR_PARAM_MASK) { - case IOERR_SEQUENCE_TIMEOUT: - rc = -ETIMEDOUT; - break; - case IOERR_INVALID_RPI: - rc = -EFAULT; - break; - default: + if (job) { + /* always return the xri, this would be used in the case + * of a menlo download to allow the data to be sent as a + * continuation of the exchange. + */ + + menlo_resp = (struct menlo_response *) + job->reply->reply_data.vendor_reply.vendor_rsp; + menlo_resp->xri = rsp->ulpContext; + if (rsp->ulpStatus) { + if (rsp->ulpStatus == IOSTAT_LOCAL_REJECT) { + switch (rsp->un.ulpWord[4] & IOERR_PARAM_MASK) { + case IOERR_SEQUENCE_TIMEOUT: + rc = -ETIMEDOUT; + break; + case IOERR_INVALID_RPI: + rc = -EFAULT; + break; + default: + rc = -EACCES; + break; + } + } else { rc = -EACCES; - break; } - } else - rc = -EACCES; - } else - job->reply->reply_payload_rcv_len = - rsp->un.genreq64.bdl.bdeSize; + } else { + rsp_size = rsp->un.genreq64.bdl.bdeSize; + job->reply->reply_payload_rcv_len = + lpfc_bsg_copy_data(rmp, &job->reply_payload, + rsp_size, 0); + } + + } - lpfc_mbuf_free(phba, bmp->virt, bmp->phys); - lpfc_sli_release_iocbq(phba, rspiocbq); lpfc_sli_release_iocbq(phba, cmdiocbq); + lpfc_free_bsg_buffers(phba, cmp); + lpfc_free_bsg_buffers(phba, rmp); + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); kfree(bmp); kfree(dd_data); - /* make error code available to userspace */ - job->reply->result = rc; - /* complete the job back to userspace */ - job->job_done(job); - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + + /* Complete the job if active */ + + if (job) { + job->reply->result = rc; + job->job_done(job); + } + return; } @@ -4883,17 +4976,14 @@ lpfc_menlo_cmd(struct fc_bsg_job *job) { struct lpfc_vport *vport = (struct lpfc_vport *)job->shost->hostdata; struct lpfc_hba *phba = vport->phba; - struct lpfc_iocbq *cmdiocbq, *rspiocbq; - IOCB_t *cmd, *rsp; + struct lpfc_iocbq *cmdiocbq; + IOCB_t *cmd; int rc = 0; struct menlo_command *menlo_cmd; struct menlo_response *menlo_resp; - struct lpfc_dmabuf *bmp = NULL; + struct lpfc_dmabuf *bmp = NULL, *cmp = NULL, *rmp = NULL; int request_nseg; int reply_nseg; - struct scatterlist *sgel = NULL; - int numbde; - dma_addr_t busaddr; struct bsg_job_data *dd_data; struct ulp_bde64 *bpl = NULL; @@ -4948,50 +5038,38 @@ lpfc_menlo_cmd(struct fc_bsg_job *job) goto free_dd; } - cmdiocbq = lpfc_sli_get_iocbq(phba); - if (!cmdiocbq) { + bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); + if (!bmp->virt) { rc = -ENOMEM; goto free_bmp; } - rspiocbq = lpfc_sli_get_iocbq(phba); - if (!rspiocbq) { - rc = -ENOMEM; - goto free_cmdiocbq; - } - - rsp = &rspiocbq->iocb; + INIT_LIST_HEAD(&bmp->list); - bmp->virt = lpfc_mbuf_alloc(phba, 0, &bmp->phys); - if (!bmp->virt) { + bpl = (struct ulp_bde64 *)bmp->virt; + request_nseg = LPFC_BPL_SIZE/sizeof(struct ulp_bde64); + cmp = lpfc_alloc_bsg_buffers(phba, job->request_payload.payload_len, + 1, bpl, &request_nseg); + if (!cmp) { rc = -ENOMEM; - goto free_rspiocbq; + goto free_bmp; } + lpfc_bsg_copy_data(cmp, &job->request_payload, + job->request_payload.payload_len, 1); - INIT_LIST_HEAD(&bmp->list); - bpl = (struct ulp_bde64 *) bmp->virt; - request_nseg = pci_map_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - for_each_sg(job->request_payload.sg_list, sgel, request_nseg, numbde) { - busaddr = sg_dma_address(sgel); - bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64; - bpl->tus.f.bdeSize = sg_dma_len(sgel); - bpl->tus.w = cpu_to_le32(bpl->tus.w); - bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); - bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); - bpl++; + bpl += request_nseg; + reply_nseg = LPFC_BPL_SIZE/sizeof(struct ulp_bde64) - request_nseg; + rmp = lpfc_alloc_bsg_buffers(phba, job->reply_payload.payload_len, 0, + bpl, &reply_nseg); + if (!rmp) { + rc = -ENOMEM; + goto free_cmp; } - reply_nseg = pci_map_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); - for_each_sg(job->reply_payload.sg_list, sgel, reply_nseg, numbde) { - busaddr = sg_dma_address(sgel); - bpl->tus.f.bdeFlags = BUFF_TYPE_BDE_64I; - bpl->tus.f.bdeSize = sg_dma_len(sgel); - bpl->tus.w = cpu_to_le32(bpl->tus.w); - bpl->addrLow = cpu_to_le32(putPaddrLow(busaddr)); - bpl->addrHigh = cpu_to_le32(putPaddrHigh(busaddr)); - bpl++; + cmdiocbq = lpfc_sli_get_iocbq(phba); + if (!cmdiocbq) { + rc = -ENOMEM; + goto free_rmp; } cmd = &cmdiocbq->iocb; @@ -5013,11 +5091,10 @@ lpfc_menlo_cmd(struct fc_bsg_job *job) cmdiocbq->vport = phba->pport; /* We want the firmware to timeout before we do */ cmd->ulpTimeout = MENLO_TIMEOUT - 5; - cmdiocbq->context3 = bmp; - cmdiocbq->context2 = rspiocbq; cmdiocbq->iocb_cmpl = lpfc_bsg_menlo_cmd_cmp; cmdiocbq->context1 = dd_data; - cmdiocbq->context2 = rspiocbq; + cmdiocbq->context2 = cmp; + cmdiocbq->context3 = bmp; if (menlo_cmd->cmd == LPFC_BSG_VENDOR_MENLO_CMD) { cmd->ulpCommand = CMD_GEN_REQUEST64_CR; cmd->ulpPU = MENLO_PU; /* 3 */ @@ -5031,29 +5108,25 @@ lpfc_menlo_cmd(struct fc_bsg_job *job) } dd_data->type = TYPE_MENLO; + dd_data->set_job = job; dd_data->context_un.menlo.cmdiocbq = cmdiocbq; - dd_data->context_un.menlo.rspiocbq = rspiocbq; - dd_data->context_un.menlo.set_job = job; - dd_data->context_un.menlo.bmp = bmp; + dd_data->context_un.menlo.rmp = rmp; + job->dd_data = dd_data; rc = lpfc_sli_issue_iocb(phba, LPFC_ELS_RING, cmdiocbq, MENLO_TIMEOUT - 5); if (rc == IOCB_SUCCESS) return 0; /* done for now */ - /* iocb failed so cleanup */ - pci_unmap_sg(phba->pcidev, job->request_payload.sg_list, - job->request_payload.sg_cnt, DMA_TO_DEVICE); - pci_unmap_sg(phba->pcidev, job->reply_payload.sg_list, - job->reply_payload.sg_cnt, DMA_FROM_DEVICE); - - lpfc_mbuf_free(phba, bmp->virt, bmp->phys); - -free_rspiocbq: - lpfc_sli_release_iocbq(phba, rspiocbq); -free_cmdiocbq: lpfc_sli_release_iocbq(phba, cmdiocbq); + +free_rmp: + lpfc_free_bsg_buffers(phba, rmp); +free_cmp: + lpfc_free_bsg_buffers(phba, cmp); free_bmp: + if (bmp->virt) + lpfc_mbuf_free(phba, bmp->virt, bmp->phys); kfree(bmp); free_dd: kfree(dd_data); @@ -5162,70 +5235,94 @@ lpfc_bsg_timeout(struct fc_bsg_job *job) struct lpfc_vport *vport = (struct lpfc_vport *)job->shost->hostdata; struct lpfc_hba *phba = vport->phba; struct lpfc_iocbq *cmdiocb; - struct lpfc_bsg_event *evt; - struct lpfc_bsg_iocb *iocb; - struct lpfc_bsg_mbox *mbox; - struct lpfc_bsg_menlo *menlo; struct lpfc_sli_ring *pring = &phba->sli.ring[LPFC_ELS_RING]; struct bsg_job_data *dd_data; unsigned long flags; + int rc = 0; + LIST_HEAD(completions); + struct lpfc_iocbq *check_iocb, *next_iocb; + + /* if job's driver data is NULL, the command completed or is in the + * the process of completing. In this case, return status to request + * so the timeout is retried. This avoids double completion issues + * and the request will be pulled off the timer queue when the + * command's completion handler executes. Otherwise, prevent the + * command's completion handler from executing the job done callback + * and continue processing to abort the outstanding the command. + */ spin_lock_irqsave(&phba->ct_ev_lock, flags); dd_data = (struct bsg_job_data *)job->dd_data; - /* timeout and completion crossed paths if no dd_data */ - if (!dd_data) { + if (dd_data) { + dd_data->set_job = NULL; + job->dd_data = NULL; + } else { spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - return 0; + return -EAGAIN; } switch (dd_data->type) { case TYPE_IOCB: - iocb = &dd_data->context_un.iocb; - cmdiocb = iocb->cmdiocbq; - /* hint to completion handler that the job timed out */ - job->reply->result = -EAGAIN; - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - /* this will call our completion handler */ + /* Check to see if IOCB was issued to the port or not. If not, + * remove it from the txq queue and call cancel iocbs. + * Otherwise, call abort iotag + */ + + cmdiocb = dd_data->context_un.iocb.cmdiocbq; spin_lock_irq(&phba->hbalock); - lpfc_sli_issue_abort_iotag(phba, pring, cmdiocb); + list_for_each_entry_safe(check_iocb, next_iocb, &pring->txq, + list) { + if (check_iocb == cmdiocb) { + list_move_tail(&check_iocb->list, &completions); + break; + } + } + if (list_empty(&completions)) + lpfc_sli_issue_abort_iotag(phba, pring, cmdiocb); spin_unlock_irq(&phba->hbalock); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + if (!list_empty(&completions)) { + lpfc_sli_cancel_iocbs(phba, &completions, + IOSTAT_LOCAL_REJECT, + IOERR_SLI_ABORTED); + } break; + case TYPE_EVT: - evt = dd_data->context_un.evt; - /* this event has no job anymore */ - evt->set_job = NULL; - job->dd_data = NULL; - job->reply->reply_payload_rcv_len = 0; - /* Return -EAGAIN which is our way of signallying the - * app to retry. - */ - job->reply->result = -EAGAIN; spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - job->job_done(job); break; + case TYPE_MBOX: - mbox = &dd_data->context_un.mbox; - /* this mbox has no job anymore */ - mbox->set_job = NULL; - job->dd_data = NULL; - job->reply->reply_payload_rcv_len = 0; - job->reply->result = -EAGAIN; - /* the mbox completion handler can now be run */ - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - job->job_done(job); + /* Update the ext buf ctx state if needed */ + if (phba->mbox_ext_buf_ctx.state == LPFC_BSG_MBOX_PORT) phba->mbox_ext_buf_ctx.state = LPFC_BSG_MBOX_ABTS; + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); break; case TYPE_MENLO: - menlo = &dd_data->context_un.menlo; - cmdiocb = menlo->cmdiocbq; - /* hint to completion handler that the job timed out */ - job->reply->result = -EAGAIN; - spin_unlock_irqrestore(&phba->ct_ev_lock, flags); - /* this will call our completion handler */ + /* Check to see if IOCB was issued to the port or not. If not, + * remove it from the txq queue and call cancel iocbs. + * Otherwise, call abort iotag. + */ + + cmdiocb = dd_data->context_un.menlo.cmdiocbq; spin_lock_irq(&phba->hbalock); - lpfc_sli_issue_abort_iotag(phba, pring, cmdiocb); + list_for_each_entry_safe(check_iocb, next_iocb, &pring->txq, + list) { + if (check_iocb == cmdiocb) { + list_move_tail(&check_iocb->list, &completions); + break; + } + } + if (list_empty(&completions)) + lpfc_sli_issue_abort_iotag(phba, pring, cmdiocb); spin_unlock_irq(&phba->hbalock); + spin_unlock_irqrestore(&phba->ct_ev_lock, flags); + if (!list_empty(&completions)) { + lpfc_sli_cancel_iocbs(phba, &completions, + IOSTAT_LOCAL_REJECT, + IOERR_SLI_ABORTED); + } break; default: spin_unlock_irqrestore(&phba->ct_ev_lock, flags); @@ -5236,5 +5333,5 @@ lpfc_bsg_timeout(struct fc_bsg_job *job) * otherwise an error message will be displayed on the console * so always return success (zero) */ - return 0; + return rc; } diff --git a/drivers/scsi/lpfc/lpfc_crtn.h b/drivers/scsi/lpfc/lpfc_crtn.h index 76ca65dae78..7631893ae00 100644 --- a/drivers/scsi/lpfc/lpfc_crtn.h +++ b/drivers/scsi/lpfc/lpfc_crtn.h @@ -106,6 +106,7 @@ void lpfc_cleanup_discovery_resources(struct lpfc_vport *); void lpfc_cleanup(struct lpfc_vport *); void lpfc_disc_timeout(unsigned long); +int lpfc_unregister_fcf_prep(struct lpfc_hba *); struct lpfc_nodelist *__lpfc_findnode_rpi(struct lpfc_vport *, uint16_t); struct lpfc_nodelist *lpfc_findnode_rpi(struct lpfc_vport *, uint16_t); void lpfc_worker_wake_up(struct lpfc_hba *); diff --git a/drivers/scsi/lpfc/lpfc_els.c b/drivers/scsi/lpfc/lpfc_els.c index 08d156a9094..bbed8471bf0 100644 --- a/drivers/scsi/lpfc/lpfc_els.c +++ b/drivers/scsi/lpfc/lpfc_els.c @@ -484,6 +484,7 @@ lpfc_issue_reg_vfi(struct lpfc_vport *vport) vport->port_state = LPFC_FABRIC_CFG_LINK; memcpy(dmabuf->virt, &phba->fc_fabparam, sizeof(vport->fc_sparam)); lpfc_reg_vfi(mboxq, vport, dmabuf->phys); + mboxq->mbox_cmpl = lpfc_mbx_cmpl_reg_vfi; mboxq->vport = vport; mboxq->context1 = dmabuf; @@ -700,6 +701,20 @@ lpfc_cmpl_els_flogi_fabric(struct lpfc_vport *vport, struct lpfc_nodelist *ndlp, } } + /* + * For FC we need to do some special processing because of the SLI + * Port's default settings of the Common Service Parameters. + */ + if (phba->sli4_hba.lnk_info.lnk_tp == LPFC_LNK_TYPE_FC) { + /* If physical FC port changed, unreg VFI and ALL VPIs / RPIs */ + if ((phba->sli_rev == LPFC_SLI_REV4) && fabric_param_changed) + lpfc_unregister_fcf_prep(phba); + + /* This should just update the VFI CSPs*/ + if (vport->fc_flag & FC_VFI_REGISTERED) + lpfc_issue_reg_vfi(vport); + } + if (fabric_param_changed && !(vport->fc_flag & FC_VPORT_NEEDS_REG_VPI)) { @@ -6225,7 +6240,7 @@ lpfc_els_timeout_handler(struct lpfc_vport *vport) spin_unlock_irq(&phba->hbalock); } - if (phba->sli.ring[LPFC_ELS_RING].txcmplq_cnt) + if (!list_empty(&phba->sli.ring[LPFC_ELS_RING].txcmplq)) mod_timer(&vport->els_tmofunc, jiffies + HZ * timeout); } @@ -6279,7 +6294,6 @@ lpfc_els_flush_cmd(struct lpfc_vport *vport) continue; list_move_tail(&piocb->list, &completions); - pring->txq_cnt--; } list_for_each_entry_safe(piocb, tmp_iocb, &pring->txcmplq, list) { @@ -6339,7 +6353,6 @@ lpfc_els_flush_all_cmd(struct lpfc_hba *phba) cmd->ulpCommand == CMD_ABORT_XRI_CN) continue; list_move_tail(&piocb->list, &completions); - pring->txq_cnt--; } list_for_each_entry_safe(piocb, tmp_iocb, &pring->txcmplq, list) { if (piocb->iocb_flag & LPFC_IO_LIBDFC) @@ -8065,7 +8078,7 @@ lpfc_sli4_els_xri_aborted(struct lpfc_hba *phba, rxid, 1); /* Check if TXQ queue needs to be serviced */ - if (pring->txq_cnt) + if (!(list_empty(&pring->txq))) lpfc_worker_wake_up(phba); return; } diff --git a/drivers/scsi/lpfc/lpfc_hbadisc.c b/drivers/scsi/lpfc/lpfc_hbadisc.c index bfda18467ee..326e05a65a7 100644 --- a/drivers/scsi/lpfc/lpfc_hbadisc.c +++ b/drivers/scsi/lpfc/lpfc_hbadisc.c @@ -691,12 +691,15 @@ lpfc_work_done(struct lpfc_hba *phba) /* Set the lpfc data pending flag */ set_bit(LPFC_DATA_READY, &phba->data_flags); } else { - pring->flag &= ~LPFC_DEFERRED_RING_EVENT; - lpfc_sli_handle_slow_ring_event(phba, pring, - (status & - HA_RXMASK)); + if (phba->link_state >= LPFC_LINK_UP) { + pring->flag &= ~LPFC_DEFERRED_RING_EVENT; + lpfc_sli_handle_slow_ring_event(phba, pring, + (status & + HA_RXMASK)); + } } - if ((phba->sli_rev == LPFC_SLI_REV4) && pring->txq_cnt) + if ((phba->sli_rev == LPFC_SLI_REV4) & + (!list_empty(&pring->txq))) lpfc_drain_txq(phba); /* * Turn on Ring interrupts @@ -1792,6 +1795,8 @@ lpfc_sli4_fcf_rec_mbox_parse(struct lpfc_hba *phba, LPFC_MBOXQ_t *mboxq, virt_addr = mboxq->sge_array->addr[0]; shdr = (union lpfc_sli4_cfg_shdr *)virt_addr; + lpfc_sli_pcimem_bcopy(shdr, shdr, + sizeof(union lpfc_sli4_cfg_shdr)); shdr_status = bf_get(lpfc_mbox_hdr_status, &shdr->response); shdr_add_status = bf_get(lpfc_mbox_hdr_add_status, &shdr->response); if (shdr_status || shdr_add_status) { @@ -2888,6 +2893,11 @@ lpfc_mbx_cmpl_reg_vfi(struct lpfc_hba *phba, LPFC_MBOXQ_t *mboxq) lpfc_vport_set_state(vport, FC_VPORT_FAILED); goto out_free_mem; } + + /* If the VFI is already registered, there is nothing else to do */ + if (vport->fc_flag & FC_VFI_REGISTERED) + goto out_free_mem; + /* The VPI is implicitly registered when the VFI is registered */ spin_lock_irq(shost->host_lock); vport->vpi_state |= LPFC_VPI_REGISTERED; @@ -2980,6 +2990,7 @@ lpfc_mbx_process_link_up(struct lpfc_hba *phba, struct lpfc_mbx_read_top *la) struct lpfc_dmabuf *mp; int rc; struct fcf_record *fcf_record; + uint32_t fc_flags = 0; spin_lock_irq(&phba->hbalock); switch (bf_get(lpfc_mbx_read_top_link_spd, la)) { @@ -3011,11 +3022,8 @@ lpfc_mbx_process_link_up(struct lpfc_hba *phba, struct lpfc_mbx_read_top *la) "1309 Link Up Event npiv not supported in loop " "topology\n"); /* Get Loop Map information */ - if (bf_get(lpfc_mbx_read_top_il, la)) { - spin_lock(shost->host_lock); - vport->fc_flag |= FC_LBIT; - spin_unlock(shost->host_lock); - } + if (bf_get(lpfc_mbx_read_top_il, la)) + fc_flags |= FC_LBIT; vport->fc_myDID = bf_get(lpfc_mbx_read_top_alpa_granted, la); i = la->lilpBde64.tus.f.bdeSize; @@ -3064,12 +3072,16 @@ lpfc_mbx_process_link_up(struct lpfc_hba *phba, struct lpfc_mbx_read_top *la) phba->sli3_options |= LPFC_SLI3_NPIV_ENABLED; } vport->fc_myDID = phba->fc_pref_DID; - spin_lock(shost->host_lock); - vport->fc_flag |= FC_LBIT; - spin_unlock(shost->host_lock); + fc_flags |= FC_LBIT; } spin_unlock_irq(&phba->hbalock); + if (fc_flags) { + spin_lock_irq(shost->host_lock); + vport->fc_flag |= fc_flags; + spin_unlock_irq(shost->host_lock); + } + lpfc_linkup(phba); sparam_mbox = mempool_alloc(phba->mbox_mem_pool, GFP_KERNEL); if (!sparam_mbox) @@ -3237,8 +3249,7 @@ lpfc_mbx_cmpl_read_topology(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmb) vport->fc_flag &= ~FC_BYPASSED_MODE; spin_unlock_irq(shost->host_lock); - if ((phba->fc_eventTag < la->eventTag) || - (phba->fc_eventTag == la->eventTag)) { + if (phba->fc_eventTag <= la->eventTag) { phba->fc_stat.LinkMultiEvent++; if (bf_get(lpfc_mbx_read_top_att_type, la) == LPFC_ATT_LINK_UP) if (phba->fc_eventTag != 0) @@ -3246,16 +3257,18 @@ lpfc_mbx_cmpl_read_topology(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmb) } phba->fc_eventTag = la->eventTag; - spin_lock_irq(&phba->hbalock); - if (bf_get(lpfc_mbx_read_top_mm, la)) - phba->sli.sli_flag |= LPFC_MENLO_MAINT; - else - phba->sli.sli_flag &= ~LPFC_MENLO_MAINT; - spin_unlock_irq(&phba->hbalock); + if (phba->sli_rev < LPFC_SLI_REV4) { + spin_lock_irq(&phba->hbalock); + if (bf_get(lpfc_mbx_read_top_mm, la)) + phba->sli.sli_flag |= LPFC_MENLO_MAINT; + else + phba->sli.sli_flag &= ~LPFC_MENLO_MAINT; + spin_unlock_irq(&phba->hbalock); + } phba->link_events++; if ((bf_get(lpfc_mbx_read_top_att_type, la) == LPFC_ATT_LINK_UP) && - (!bf_get(lpfc_mbx_read_top_mm, la))) { + !(phba->sli.sli_flag & LPFC_MENLO_MAINT)) { phba->fc_stat.LinkUp++; if (phba->link_flag & LS_LOOPBACK_MODE) { lpfc_printf_log(phba, KERN_ERR, LOG_LINK_EVENT, @@ -3300,8 +3313,8 @@ lpfc_mbx_cmpl_read_topology(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmb) bf_get(lpfc_mbx_read_top_fa, la)); lpfc_mbx_issue_link_down(phba); } - if ((bf_get(lpfc_mbx_read_top_mm, la)) && - (bf_get(lpfc_mbx_read_top_att_type, la) == LPFC_ATT_LINK_UP)) { + if ((phba->sli.sli_flag & LPFC_MENLO_MAINT) && + ((bf_get(lpfc_mbx_read_top_att_type, la) == LPFC_ATT_LINK_UP))) { if (phba->link_state != LPFC_LINK_DOWN) { phba->fc_stat.LinkDown++; lpfc_printf_log(phba, KERN_ERR, LOG_LINK_EVENT, @@ -3329,8 +3342,9 @@ lpfc_mbx_cmpl_read_topology(struct lpfc_hba *phba, LPFC_MBOXQ_t *pmb) } } - if (bf_get(lpfc_mbx_read_top_fa, la)) { - if (bf_get(lpfc_mbx_read_top_mm, la)) + if ((phba->sli_rev < LPFC_SLI_REV4) && + bf_get(lpfc_mbx_read_top_fa, la)) { + if (phba->sli.sli_flag & LPFC_MENLO_MAINT) lpfc_issue_clear_la(phba, vport); lpfc_printf_log(phba, KERN_INFO, LOG_LINK_EVENT, "1311 fa %d\n", @@ -4354,7 +4368,6 @@ lpfc_no_rpi(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp) with an error */ list_move_tail(&iocb->list, &completions); - pring->txq_cnt--; } } spin_unlock_irq(&phba->hbalock); @@ -5055,7 +5068,6 @@ lpfc_free_tx(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp) (icmd->ulpCommand == CMD_XMIT_ELS_RSP64_CX)) { list_move_tail(&iocb->list, &completions); - pring->txq_cnt--; } } diff --git a/drivers/scsi/lpfc/lpfc_hw4.h b/drivers/scsi/lpfc/lpfc_hw4.h index 6e93b886cd4..1dd2f6f0a12 100644 --- a/drivers/scsi/lpfc/lpfc_hw4.h +++ b/drivers/scsi/lpfc/lpfc_hw4.h @@ -1958,6 +1958,9 @@ struct lpfc_mbx_init_vfi { struct lpfc_mbx_reg_vfi { uint32_t word1; +#define lpfc_reg_vfi_upd_SHIFT 29 +#define lpfc_reg_vfi_upd_MASK 0x00000001 +#define lpfc_reg_vfi_upd_WORD word1 #define lpfc_reg_vfi_vp_SHIFT 28 #define lpfc_reg_vfi_vp_MASK 0x00000001 #define lpfc_reg_vfi_vp_WORD word1 diff --git a/drivers/scsi/lpfc/lpfc_init.c b/drivers/scsi/lpfc/lpfc_init.c index 314b4f61b9e..5da29729026 100644 --- a/drivers/scsi/lpfc/lpfc_init.c +++ b/drivers/scsi/lpfc/lpfc_init.c @@ -839,7 +839,6 @@ lpfc_hba_down_post_s3(struct lpfc_hba *phba) * way, nothing should be on txcmplq as it will NEVER complete. */ list_splice_init(&pring->txcmplq, &completions); - pring->txcmplq_cnt = 0; spin_unlock_irq(&phba->hbalock); /* Cancel all the IOCBs from the completions list */ @@ -2915,9 +2914,9 @@ lpfc_sli4_xri_sgl_update(struct lpfc_hba *phba) sglq_entry->state = SGL_FREED; list_add_tail(&sglq_entry->list, &els_sgl_list); } - spin_lock(&phba->hbalock); + spin_lock_irq(&phba->hbalock); list_splice_init(&els_sgl_list, &phba->sli4_hba.lpfc_sgl_list); - spin_unlock(&phba->hbalock); + spin_unlock_irq(&phba->hbalock); } else if (els_xri_cnt < phba->sli4_hba.els_xri_cnt) { /* els xri-sgl shrinked */ xri_cnt = phba->sli4_hba.els_xri_cnt - els_xri_cnt; @@ -3015,9 +3014,9 @@ lpfc_sli4_xri_sgl_update(struct lpfc_hba *phba) psb->cur_iocbq.sli4_lxritag = lxri; psb->cur_iocbq.sli4_xritag = phba->sli4_hba.xri_ids[lxri]; } - spin_lock(&phba->scsi_buf_list_lock); + spin_lock_irq(&phba->scsi_buf_list_lock); list_splice_init(&scsi_sgl_list, &phba->lpfc_scsi_buf_list); - spin_unlock(&phba->scsi_buf_list_lock); + spin_unlock_irq(&phba->scsi_buf_list_lock); return 0; @@ -4004,6 +4003,52 @@ lpfc_sli4_perform_all_vport_cvl(struct lpfc_hba *phba) } /** + * lpfc_sli4_perform_inuse_fcf_recovery - Perform inuse fcf recovery + * @vport: pointer to lpfc hba data structure. + * + * This routine is to perform FCF recovery when the in-use FCF either dead or + * got modified. + **/ +static void +lpfc_sli4_perform_inuse_fcf_recovery(struct lpfc_hba *phba, + struct lpfc_acqe_fip *acqe_fip) +{ + int rc; + + spin_lock_irq(&phba->hbalock); + /* Mark the fast failover process in progress */ + phba->fcf.fcf_flag |= FCF_DEAD_DISC; + spin_unlock_irq(&phba->hbalock); + + lpfc_printf_log(phba, KERN_INFO, LOG_FIP | LOG_DISCOVERY, + "2771 Start FCF fast failover process due to in-use " + "FCF DEAD/MODIFIED event: evt_tag:x%x, index:x%x\n", + acqe_fip->event_tag, acqe_fip->index); + rc = lpfc_sli4_redisc_fcf_table(phba); + if (rc) { + lpfc_printf_log(phba, KERN_ERR, LOG_FIP | LOG_DISCOVERY, + "2772 Issue FCF rediscover mabilbox command " + "failed, fail through to FCF dead event\n"); + spin_lock_irq(&phba->hbalock); + phba->fcf.fcf_flag &= ~FCF_DEAD_DISC; + spin_unlock_irq(&phba->hbalock); + /* + * Last resort will fail over by treating this as a link + * down to FCF registration. + */ + lpfc_sli4_fcf_dead_failthrough(phba); + } else { + /* Reset FCF roundrobin bmask for new discovery */ + lpfc_sli4_clear_fcf_rr_bmask(phba); + /* + * Handling fast FCF failover to a DEAD FCF event is + * considered equalivant to receiving CVL to all vports. + */ + lpfc_sli4_perform_all_vport_cvl(phba); + } +} + +/** * lpfc_sli4_async_fip_evt - Process the asynchronous FCoE FIP event * @phba: pointer to lpfc hba data structure. * @acqe_link: pointer to the async fcoe completion queue entry. @@ -4068,9 +4113,22 @@ lpfc_sli4_async_fip_evt(struct lpfc_hba *phba, break; } - /* If the FCF has been in discovered state, do nothing. */ - if (phba->fcf.fcf_flag & FCF_SCAN_DONE) { + /* If FCF has been in discovered state, perform rediscovery + * only if the FCF with the same index of the in-use FCF got + * modified during normal operation. Otherwise, do nothing. + */ + if (phba->pport->port_state > LPFC_FLOGI) { spin_unlock_irq(&phba->hbalock); + if (phba->fcf.current_rec.fcf_indx == + acqe_fip->index) { + lpfc_printf_log(phba, KERN_ERR, LOG_FIP, + "3300 In-use FCF (%d) " + "modified, perform FCF " + "rediscovery\n", + acqe_fip->index); + lpfc_sli4_perform_inuse_fcf_recovery(phba, + acqe_fip); + } break; } spin_unlock_irq(&phba->hbalock); @@ -4123,39 +4181,7 @@ lpfc_sli4_async_fip_evt(struct lpfc_hba *phba, * is no longer valid as we are not in the middle of FCF * failover process already. */ - spin_lock_irq(&phba->hbalock); - /* Mark the fast failover process in progress */ - phba->fcf.fcf_flag |= FCF_DEAD_DISC; - spin_unlock_irq(&phba->hbalock); - - lpfc_printf_log(phba, KERN_INFO, LOG_FIP | LOG_DISCOVERY, - "2771 Start FCF fast failover process due to " - "FCF DEAD event: evt_tag:x%x, fcf_index:x%x " - "\n", acqe_fip->event_tag, acqe_fip->index); - rc = lpfc_sli4_redisc_fcf_table(phba); - if (rc) { - lpfc_printf_log(phba, KERN_ERR, LOG_FIP | - LOG_DISCOVERY, - "2772 Issue FCF rediscover mabilbox " - "command failed, fail through to FCF " - "dead event\n"); - spin_lock_irq(&phba->hbalock); - phba->fcf.fcf_flag &= ~FCF_DEAD_DISC; - spin_unlock_irq(&phba->hbalock); - /* - * Last resort will fail over by treating this - * as a link down to FCF registration. - */ - lpfc_sli4_fcf_dead_failthrough(phba); - } else { - /* Reset FCF roundrobin bmask for new discovery */ - lpfc_sli4_clear_fcf_rr_bmask(phba); - /* - * Handling fast FCF failover to a DEAD FCF event is - * considered equalivant to receiving CVL to all vports. - */ - lpfc_sli4_perform_all_vport_cvl(phba); - } + lpfc_sli4_perform_inuse_fcf_recovery(phba, acqe_fip); break; case LPFC_FIP_EVENT_TYPE_CVL: phba->fcoe_cvl_eventtag = acqe_fip->event_tag; diff --git a/drivers/scsi/lpfc/lpfc_mbox.c b/drivers/scsi/lpfc/lpfc_mbox.c index efc9cd9def8..a7a9fa46830 100644 --- a/drivers/scsi/lpfc/lpfc_mbox.c +++ b/drivers/scsi/lpfc/lpfc_mbox.c @@ -2126,32 +2126,40 @@ void lpfc_reg_vfi(struct lpfcMboxq *mbox, struct lpfc_vport *vport, dma_addr_t phys) { struct lpfc_mbx_reg_vfi *reg_vfi; + struct lpfc_hba *phba = vport->phba; memset(mbox, 0, sizeof(*mbox)); reg_vfi = &mbox->u.mqe.un.reg_vfi; bf_set(lpfc_mqe_command, &mbox->u.mqe, MBX_REG_VFI); bf_set(lpfc_reg_vfi_vp, reg_vfi, 1); bf_set(lpfc_reg_vfi_vfi, reg_vfi, - vport->phba->sli4_hba.vfi_ids[vport->vfi]); - bf_set(lpfc_reg_vfi_fcfi, reg_vfi, vport->phba->fcf.fcfi); - bf_set(lpfc_reg_vfi_vpi, reg_vfi, vport->phba->vpi_ids[vport->vpi]); + phba->sli4_hba.vfi_ids[vport->vfi]); + bf_set(lpfc_reg_vfi_fcfi, reg_vfi, phba->fcf.fcfi); + bf_set(lpfc_reg_vfi_vpi, reg_vfi, phba->vpi_ids[vport->vpi]); memcpy(reg_vfi->wwn, &vport->fc_portname, sizeof(struct lpfc_name)); reg_vfi->wwn[0] = cpu_to_le32(reg_vfi->wwn[0]); reg_vfi->wwn[1] = cpu_to_le32(reg_vfi->wwn[1]); - reg_vfi->e_d_tov = vport->phba->fc_edtov; - reg_vfi->r_a_tov = vport->phba->fc_ratov; + reg_vfi->e_d_tov = phba->fc_edtov; + reg_vfi->r_a_tov = phba->fc_ratov; reg_vfi->bde.addrHigh = putPaddrHigh(phys); reg_vfi->bde.addrLow = putPaddrLow(phys); reg_vfi->bde.tus.f.bdeSize = sizeof(vport->fc_sparam); reg_vfi->bde.tus.f.bdeFlags = BUFF_TYPE_BDE_64; bf_set(lpfc_reg_vfi_nport_id, reg_vfi, vport->fc_myDID); + + /* Only FC supports upd bit */ + if ((phba->sli4_hba.lnk_info.lnk_tp == LPFC_LNK_TYPE_FC) && + (vport->fc_flag & FC_VFI_REGISTERED)) { + bf_set(lpfc_reg_vfi_vp, reg_vfi, 0); + bf_set(lpfc_reg_vfi_upd, reg_vfi, 1); + } lpfc_printf_vlog(vport, KERN_INFO, LOG_MBOX, "3134 Register VFI, mydid:x%x, fcfi:%d, " " vfi:%d, vpi:%d, fc_pname:%x%x\n", vport->fc_myDID, - vport->phba->fcf.fcfi, - vport->phba->sli4_hba.vfi_ids[vport->vfi], - vport->phba->vpi_ids[vport->vpi], + phba->fcf.fcfi, + phba->sli4_hba.vfi_ids[vport->vfi], + phba->vpi_ids[vport->vpi], reg_vfi->wwn[0], reg_vfi->wwn[1]); } diff --git a/drivers/scsi/lpfc/lpfc_nportdisc.c b/drivers/scsi/lpfc/lpfc_nportdisc.c index 46128c67920..82f4d354228 100644 --- a/drivers/scsi/lpfc/lpfc_nportdisc.c +++ b/drivers/scsi/lpfc/lpfc_nportdisc.c @@ -226,7 +226,6 @@ lpfc_els_abort(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp) if (lpfc_check_sli_ndlp(phba, pring, iocb, ndlp)) { /* It matches, so deque and call compl with anp error */ list_move_tail(&iocb->list, &completions); - pring->txq_cnt--; } } diff --git a/drivers/scsi/lpfc/lpfc_scsi.c b/drivers/scsi/lpfc/lpfc_scsi.c index 98af07c6e30..74b8710e1e9 100644 --- a/drivers/scsi/lpfc/lpfc_scsi.c +++ b/drivers/scsi/lpfc/lpfc_scsi.c @@ -732,7 +732,7 @@ lpfc_sli4_fcp_xri_aborted(struct lpfc_hba *phba, psb = container_of(iocbq, struct lpfc_scsi_buf, cur_iocbq); psb->exch_busy = 0; spin_unlock_irqrestore(&phba->hbalock, iflag); - if (pring->txq_cnt) + if (!list_empty(&pring->txq)) lpfc_worker_wake_up(phba); return; @@ -885,9 +885,9 @@ lpfc_sli4_repost_scsi_sgl_list(struct lpfc_hba *phba) int num_posted, rc = 0; /* get all SCSI buffers need to repost to a local list */ - spin_lock(&phba->scsi_buf_list_lock); + spin_lock_irq(&phba->scsi_buf_list_lock); list_splice_init(&phba->lpfc_scsi_buf_list, &post_sblist); - spin_unlock(&phba->scsi_buf_list_lock); + spin_unlock_irq(&phba->scsi_buf_list_lock); /* post the list of scsi buffer sgls to port if available */ if (!list_empty(&post_sblist)) { @@ -4246,7 +4246,7 @@ static __inline__ void lpfc_poll_rearm_timer(struct lpfc_hba * phba) unsigned long poll_tmo_expires = (jiffies + msecs_to_jiffies(phba->cfg_poll_tmo)); - if (phba->sli.ring[LPFC_FCP_RING].txcmplq_cnt) + if (!list_empty(&phba->sli.ring[LPFC_FCP_RING].txcmplq)) mod_timer(&phba->fcp_poll_timer, poll_tmo_expires); } diff --git a/drivers/scsi/lpfc/lpfc_sli.c b/drivers/scsi/lpfc/lpfc_sli.c index d43faf34c1e..35dd17eb0f2 100644 --- a/drivers/scsi/lpfc/lpfc_sli.c +++ b/drivers/scsi/lpfc/lpfc_sli.c @@ -873,14 +873,16 @@ lpfc_set_rrq_active(struct lpfc_hba *phba, struct lpfc_nodelist *ndlp, xritag, rxid, ndlp->nlp_DID, send_rrq); return -EINVAL; } - rrq->send_rrq = send_rrq; + if (phba->cfg_enable_rrq == 1) + rrq->send_rrq = send_rrq; + else + rrq->send_rrq = 0; rrq->xritag = xritag; rrq->rrq_stop_time = jiffies + HZ * (phba->fc_ratov + 1); rrq->ndlp = ndlp; rrq->nlp_DID = ndlp->nlp_DID; rrq->vport = ndlp->vport; rrq->rxid = rxid; - rrq->send_rrq = send_rrq; spin_lock_irqsave(&phba->hbalock, iflags); empty = list_empty(&phba->active_rrq_list); list_add_tail(&rrq->list, &phba->active_rrq_list); @@ -1009,6 +1011,18 @@ __lpfc_sli_release_iocbq_s4(struct lpfc_hba *phba, struct lpfc_iocbq *iocbq) else sglq = __lpfc_clear_active_sglq(phba, iocbq->sli4_lxritag); + /* + ** This should have been removed from the txcmplq before calling + ** iocbq_release. The normal completion + ** path should have already done the list_del_init. + */ + if (unlikely(!list_empty(&iocbq->list))) { + if (iocbq->iocb_flag & LPFC_IO_ON_TXCMPLQ) + iocbq->iocb_flag &= ~LPFC_IO_ON_TXCMPLQ; + list_del_init(&iocbq->list); + } + + if (sglq) { if ((iocbq->iocb_flag & LPFC_EXCHANGE_BUSY) && (sglq->state != SGL_XRI_ABORTED)) { @@ -1025,7 +1039,7 @@ __lpfc_sli_release_iocbq_s4(struct lpfc_hba *phba, struct lpfc_iocbq *iocbq) &phba->sli4_hba.lpfc_sgl_list); /* Check if TXQ queue needs to be serviced */ - if (pring->txq_cnt) + if (!list_empty(&pring->txq)) lpfc_worker_wake_up(phba); } } @@ -1057,6 +1071,14 @@ __lpfc_sli_release_iocbq_s3(struct lpfc_hba *phba, struct lpfc_iocbq *iocbq) size_t start_clean = offsetof(struct lpfc_iocbq, iocb); /* + ** This should have been removed from the txcmplq before calling + ** iocbq_release. The normal completion + ** path should have already done the list_del_init. + */ + if (unlikely(!list_empty(&iocbq->list))) + list_del_init(&iocbq->list); + + /* * Clean all volatile data fields, preserve iotag and node struct. */ memset((char*)iocbq + start_clean, 0, sizeof(*iocbq) - start_clean); @@ -1122,7 +1144,6 @@ lpfc_sli_cancel_iocbs(struct lpfc_hba *phba, struct list_head *iocblist, while (!list_empty(iocblist)) { list_remove_head(iocblist, piocb, struct lpfc_iocbq, list); - if (!piocb->iocb_cmpl) lpfc_sli_release_iocbq(phba, piocb); else { @@ -1310,9 +1331,6 @@ lpfc_sli_ringtxcmpl_put(struct lpfc_hba *phba, struct lpfc_sli_ring *pring, { list_add_tail(&piocb->list, &pring->txcmplq); piocb->iocb_flag |= LPFC_IO_ON_TXCMPLQ; - pring->txcmplq_cnt++; - if (pring->txcmplq_cnt > pring->txcmplq_max) - pring->txcmplq_max = pring->txcmplq_cnt; if ((unlikely(pring->ringno == LPFC_ELS_RING)) && (piocb->iocb.ulpCommand != CMD_ABORT_XRI_CN) && @@ -1344,8 +1362,6 @@ lpfc_sli_ringtx_get(struct lpfc_hba *phba, struct lpfc_sli_ring *pring) struct lpfc_iocbq *cmd_iocb; list_remove_head((&pring->txq), cmd_iocb, struct lpfc_iocbq, list); - if (cmd_iocb != NULL) - pring->txq_cnt--; return cmd_iocb; } @@ -1614,8 +1630,9 @@ lpfc_sli_resume_iocb(struct lpfc_hba *phba, struct lpfc_sli_ring *pring) * (c) link attention events can be processed (fcp ring only) * (d) IOCB processing is not blocked by the outstanding mbox command. */ - if (pring->txq_cnt && - lpfc_is_link_up(phba) && + + if (lpfc_is_link_up(phba) && + (!list_empty(&pring->txq)) && (pring->ringno != phba->sli.fcp_ring || phba->sli.sli_flag & LPFC_PROCESS_LA)) { @@ -2612,7 +2629,6 @@ lpfc_sli_iocbq_lookup(struct lpfc_hba *phba, cmd_iocb = phba->sli.iocbq_lookup[iotag]; list_del_init(&cmd_iocb->list); if (cmd_iocb->iocb_flag & LPFC_IO_ON_TXCMPLQ) { - pring->txcmplq_cnt--; cmd_iocb->iocb_flag &= ~LPFC_IO_ON_TXCMPLQ; } return cmd_iocb; @@ -2650,7 +2666,6 @@ lpfc_sli_iocbq_lookup_by_tag(struct lpfc_hba *phba, /* remove from txcmpl queue list */ list_del_init(&cmd_iocb->list); cmd_iocb->iocb_flag &= ~LPFC_IO_ON_TXCMPLQ; - pring->txcmplq_cnt--; return cmd_iocb; } } @@ -3499,7 +3514,6 @@ lpfc_sli_abort_iocb_ring(struct lpfc_hba *phba, struct lpfc_sli_ring *pring) */ spin_lock_irq(&phba->hbalock); list_splice_init(&pring->txq, &completions); - pring->txq_cnt = 0; /* Next issue ABTS for everything on the txcmplq */ list_for_each_entry_safe(iocb, next_iocb, &pring->txcmplq, list) @@ -3536,11 +3550,9 @@ lpfc_sli_flush_fcp_rings(struct lpfc_hba *phba) spin_lock_irq(&phba->hbalock); /* Retrieve everything on txq */ list_splice_init(&pring->txq, &txq); - pring->txq_cnt = 0; /* Retrieve everything on the txcmplq */ list_splice_init(&pring->txcmplq, &txcmplq); - pring->txcmplq_cnt = 0; /* Indicate the I/O queues are flushed */ phba->hba_flag |= HBA_FCP_IOQ_FLUSH; @@ -5988,9 +6000,9 @@ lpfc_sli4_repost_els_sgl_list(struct lpfc_hba *phba) LIST_HEAD(post_sgl_list); LIST_HEAD(free_sgl_list); - spin_lock(&phba->hbalock); + spin_lock_irq(&phba->hbalock); list_splice_init(&phba->sli4_hba.lpfc_sgl_list, &allc_sgl_list); - spin_unlock(&phba->hbalock); + spin_unlock_irq(&phba->hbalock); list_for_each_entry_safe(sglq_entry, sglq_entry_next, &allc_sgl_list, list) { @@ -6091,10 +6103,10 @@ lpfc_sli4_repost_els_sgl_list(struct lpfc_hba *phba) /* push els sgls posted to the availble list */ if (!list_empty(&post_sgl_list)) { - spin_lock(&phba->hbalock); + spin_lock_irq(&phba->hbalock); list_splice_init(&post_sgl_list, &phba->sli4_hba.lpfc_sgl_list); - spin_unlock(&phba->hbalock); + spin_unlock_irq(&phba->hbalock); } else { lpfc_printf_log(phba, KERN_ERR, LOG_SLI, "3161 Failure to post els sgl to port.\n"); @@ -7615,7 +7627,6 @@ __lpfc_sli_ringtx_put(struct lpfc_hba *phba, struct lpfc_sli_ring *pring, { /* Insert the caller's iocb in the txq tail for later processing. */ list_add_tail(&piocb->list, &pring->txq); - pring->txq_cnt++; } /** @@ -8387,7 +8398,7 @@ __lpfc_sli_issue_iocb_s4(struct lpfc_hba *phba, uint32_t ring_number, piocb->iocb.ulpCommand == CMD_CLOSE_XRI_CN) sglq = NULL; else { - if (pring->txq_cnt) { + if (!list_empty(&pring->txq)) { if (!(flag & SLI_IOCB_RET_IOCB)) { __lpfc_sli_ringtx_put(phba, pring, piocb); @@ -9055,7 +9066,6 @@ lpfc_sli_host_down(struct lpfc_vport *vport) if (iocb->vport != vport) continue; list_move_tail(&iocb->list, &completions); - pring->txq_cnt--; } /* Next issue ABTS for everything on the txcmplq */ @@ -9124,8 +9134,6 @@ lpfc_sli_hba_down(struct lpfc_hba *phba) * given to the FW yet. */ list_splice_init(&pring->txq, &completions); - pring->txq_cnt = 0; - } spin_unlock_irqrestore(&phba->hbalock, flags); @@ -9966,6 +9974,9 @@ lpfc_sli_issue_iocb_wait(struct lpfc_hba *phba, long timeleft, timeout_req = 0; int retval = IOCB_SUCCESS; uint32_t creg_val; + struct lpfc_iocbq *iocb; + int txq_cnt = 0; + int txcmplq_cnt = 0; struct lpfc_sli_ring *pring = &phba->sli.ring[LPFC_ELS_RING]; /* * If the caller has provided a response iocbq buffer, then context2 @@ -10013,9 +10024,17 @@ lpfc_sli_issue_iocb_wait(struct lpfc_hba *phba, retval = IOCB_TIMEDOUT; } } else if (retval == IOCB_BUSY) { - lpfc_printf_log(phba, KERN_INFO, LOG_SLI, - "2818 Max IOCBs %d txq cnt %d txcmplq cnt %d\n", - phba->iocb_cnt, pring->txq_cnt, pring->txcmplq_cnt); + if (phba->cfg_log_verbose & LOG_SLI) { + list_for_each_entry(iocb, &pring->txq, list) { + txq_cnt++; + } + list_for_each_entry(iocb, &pring->txcmplq, list) { + txcmplq_cnt++; + } + lpfc_printf_log(phba, KERN_INFO, LOG_SLI, + "2818 Max IOCBs %d txq cnt %d txcmplq cnt %d\n", + phba->iocb_cnt, txq_cnt, txcmplq_cnt); + } return retval; } else { lpfc_printf_log(phba, KERN_INFO, LOG_SLI, @@ -11298,16 +11317,25 @@ lpfc_sli4_sp_handle_els_wcqe(struct lpfc_hba *phba, struct lpfc_queue *cq, struct lpfc_iocbq *irspiocbq; unsigned long iflags; struct lpfc_sli_ring *pring = cq->pring; + int txq_cnt = 0; + int txcmplq_cnt = 0; + int fcp_txcmplq_cnt = 0; /* Get an irspiocbq for later ELS response processing use */ irspiocbq = lpfc_sli_get_iocbq(phba); if (!irspiocbq) { + if (!list_empty(&pring->txq)) + txq_cnt++; + if (!list_empty(&pring->txcmplq)) + txcmplq_cnt++; + if (!list_empty(&phba->sli.ring[LPFC_FCP_RING].txcmplq)) + fcp_txcmplq_cnt++; lpfc_printf_log(phba, KERN_ERR, LOG_SLI, "0387 NO IOCBQ data: txq_cnt=%d iocb_cnt=%d " "fcp_txcmplq_cnt=%d, els_txcmplq_cnt=%d\n", - pring->txq_cnt, phba->iocb_cnt, - phba->sli.ring[LPFC_FCP_RING].txcmplq_cnt, - phba->sli.ring[LPFC_ELS_RING].txcmplq_cnt); + txq_cnt, phba->iocb_cnt, + fcp_txcmplq_cnt, + txcmplq_cnt); return false; } @@ -15482,11 +15510,18 @@ lpfc_check_next_fcf_pri_level(struct lpfc_hba *phba) LPFC_SLI4_FCF_TBL_INDX_MAX); lpfc_printf_log(phba, KERN_INFO, LOG_FIP, "3060 Last IDX %d\n", last_index); - if (list_empty(&phba->fcf.fcf_pri_list)) { + + /* Verify the priority list has 2 or more entries */ + spin_lock_irq(&phba->hbalock); + if (list_empty(&phba->fcf.fcf_pri_list) || + list_is_singular(&phba->fcf.fcf_pri_list)) { + spin_unlock_irq(&phba->hbalock); lpfc_printf_log(phba, KERN_ERR, LOG_FIP, "3061 Last IDX %d\n", last_index); return 0; /* Empty rr list */ } + spin_unlock_irq(&phba->hbalock); + next_fcf_pri = 0; /* * Clear the rr_bmask and set all of the bits that are at this @@ -16245,14 +16280,19 @@ lpfc_drain_txq(struct lpfc_hba *phba) char *fail_msg = NULL; struct lpfc_sglq *sglq; union lpfc_wqe wqe; + int txq_cnt = 0; spin_lock_irqsave(&phba->hbalock, iflags); - if (pring->txq_cnt > pring->txq_max) - pring->txq_max = pring->txq_cnt; + list_for_each_entry(piocbq, &pring->txq, list) { + txq_cnt++; + } + + if (txq_cnt > pring->txq_max) + pring->txq_max = txq_cnt; spin_unlock_irqrestore(&phba->hbalock, iflags); - while (pring->txq_cnt) { + while (!list_empty(&pring->txq)) { spin_lock_irqsave(&phba->hbalock, iflags); piocbq = lpfc_sli_ringtx_get(phba, pring); @@ -16260,7 +16300,7 @@ lpfc_drain_txq(struct lpfc_hba *phba) spin_unlock_irqrestore(&phba->hbalock, iflags); lpfc_printf_log(phba, KERN_ERR, LOG_SLI, "2823 txq empty and txq_cnt is %d\n ", - pring->txq_cnt); + txq_cnt); break; } sglq = __lpfc_sli_get_sglq(phba, piocbq); @@ -16269,6 +16309,7 @@ lpfc_drain_txq(struct lpfc_hba *phba) spin_unlock_irqrestore(&phba->hbalock, iflags); break; } + txq_cnt--; /* The xri and iocb resources secured, * attempt to issue request @@ -16300,5 +16341,5 @@ lpfc_drain_txq(struct lpfc_hba *phba) lpfc_sli_cancel_iocbs(phba, &completions, IOSTAT_LOCAL_REJECT, IOERR_SLI_ABORTED); - return pring->txq_cnt; + return txq_cnt; } diff --git a/drivers/scsi/lpfc/lpfc_version.h b/drivers/scsi/lpfc/lpfc_version.h index f3b7795a296..664cd04f7cd 100644 --- a/drivers/scsi/lpfc/lpfc_version.h +++ b/drivers/scsi/lpfc/lpfc_version.h @@ -18,7 +18,7 @@ * included with this package. * *******************************************************************/ -#define LPFC_DRIVER_VERSION "8.3.37" +#define LPFC_DRIVER_VERSION "8.3.38" #define LPFC_DRIVER_NAME "lpfc" /* Used for SLI 2/3 */ diff --git a/drivers/scsi/qla2xxx/Makefile b/drivers/scsi/qla2xxx/Makefile index dce7d788cdc..c37b244cf8a 100644 --- a/drivers/scsi/qla2xxx/Makefile +++ b/drivers/scsi/qla2xxx/Makefile @@ -1,6 +1,6 @@ qla2xxx-y := qla_os.o qla_init.o qla_mbx.o qla_iocb.o qla_isr.o qla_gs.o \ qla_dbg.o qla_sup.o qla_attr.o qla_mid.o qla_dfs.o qla_bsg.o \ - qla_nx.o qla_target.o + qla_nx.o qla_mr.o qla_target.o obj-$(CONFIG_SCSI_QLA_FC) += qla2xxx.o obj-$(CONFIG_TCM_QLA2XXX) += tcm_qla2xxx.o diff --git a/drivers/scsi/qla2xxx/qla_attr.c b/drivers/scsi/qla2xxx/qla_attr.c index b3db9dcc261..bf60c631abb 100644 --- a/drivers/scsi/qla2xxx/qla_attr.c +++ b/drivers/scsi/qla2xxx/qla_attr.c @@ -888,7 +888,10 @@ qla2x00_serial_num_show(struct device *dev, struct device_attribute *attr, struct qla_hw_data *ha = vha->hw; uint32_t sn; - if (IS_FWI2_CAPABLE(ha)) { + if (IS_QLAFX00(vha->hw)) { + return snprintf(buf, PAGE_SIZE, "%s\n", + vha->hw->mr.serial_num); + } else if (IS_FWI2_CAPABLE(ha)) { qla2xxx_get_vpd_field(vha, "SN", buf, PAGE_SIZE); return snprintf(buf, PAGE_SIZE, "%s\n", buf); } @@ -912,6 +915,11 @@ qla2x00_isp_id_show(struct device *dev, struct device_attribute *attr, { scsi_qla_host_t *vha = shost_priv(class_to_shost(dev)); struct qla_hw_data *ha = vha->hw; + + if (IS_QLAFX00(vha->hw)) + return snprintf(buf, PAGE_SIZE, "%s\n", + vha->hw->mr.hw_version); + return snprintf(buf, PAGE_SIZE, "%04x %04x %04x %04x\n", ha->product_id[0], ha->product_id[1], ha->product_id[2], ha->product_id[3]); @@ -922,6 +930,11 @@ qla2x00_model_name_show(struct device *dev, struct device_attribute *attr, char *buf) { scsi_qla_host_t *vha = shost_priv(class_to_shost(dev)); + + if (IS_QLAFX00(vha->hw)) + return snprintf(buf, PAGE_SIZE, "%s\n", + vha->hw->mr.product_name); + return snprintf(buf, PAGE_SIZE, "%s\n", vha->hw->model_number); } @@ -1304,6 +1317,12 @@ qla2x00_fw_state_show(struct device *dev, struct device_attribute *attr, scsi_qla_host_t *vha = shost_priv(class_to_shost(dev)); int rval = QLA_FUNCTION_FAILED; uint16_t state[5]; + uint32_t pstate; + + if (IS_QLAFX00(vha->hw)) { + pstate = qlafx00_fw_state_show(dev, attr, buf); + return snprintf(buf, PAGE_SIZE, "0x%x\n", pstate); + } if (qla2x00_reset_active(vha)) ql_log(ql_log_warn, vha, 0x707c, @@ -1454,6 +1473,11 @@ qla2x00_get_host_speed(struct Scsi_Host *shost) (shost_priv(shost)))->hw; u32 speed = FC_PORTSPEED_UNKNOWN; + if (IS_QLAFX00(ha)) { + qlafx00_get_host_speed(shost); + return; + } + switch (ha->link_data_rate) { case PORT_SPEED_1GB: speed = FC_PORTSPEED_1GBIT; @@ -1637,6 +1661,9 @@ qla2x00_issue_lip(struct Scsi_Host *shost) { scsi_qla_host_t *vha = shost_priv(shost); + if (IS_QLAFX00(vha->hw)) + return 0; + qla2x00_loop_reset(vha); return 0; } @@ -1655,6 +1682,9 @@ qla2x00_get_fc_host_stats(struct Scsi_Host *shost) pfc_host_stat = &vha->fc_host_stat; memset(pfc_host_stat, -1, sizeof(struct fc_host_statistics)); + if (IS_QLAFX00(vha->hw)) + goto done; + if (test_bit(UNLOADING, &vha->dpc_flags)) goto done; @@ -2087,6 +2117,9 @@ qla2x00_init_host_attr(scsi_qla_host_t *vha) FC_PORTSPEED_1GBIT; else if (IS_QLA23XX(ha)) speed = FC_PORTSPEED_2GBIT | FC_PORTSPEED_1GBIT; + else if (IS_QLAFX00(ha)) + speed = FC_PORTSPEED_8GBIT | FC_PORTSPEED_4GBIT | + FC_PORTSPEED_2GBIT | FC_PORTSPEED_1GBIT; else speed = FC_PORTSPEED_1GBIT; fc_host_supported_speeds(vha->host) = speed; diff --git a/drivers/scsi/qla2xxx/qla_bsg.c b/drivers/scsi/qla2xxx/qla_bsg.c index ad54099cb80..39719f89248 100644 --- a/drivers/scsi/qla2xxx/qla_bsg.c +++ b/drivers/scsi/qla2xxx/qla_bsg.c @@ -30,14 +30,31 @@ qla2x00_bsg_sp_free(void *data, void *ptr) struct scsi_qla_host *vha = sp->fcport->vha; struct fc_bsg_job *bsg_job = sp->u.bsg_job; struct qla_hw_data *ha = vha->hw; + struct qla_mt_iocb_rqst_fx00 *piocb_rqst; - dma_unmap_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, - bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); + if (sp->type == SRB_FXIOCB_BCMD) { + piocb_rqst = (struct qla_mt_iocb_rqst_fx00 *) + &bsg_job->request->rqst_data.h_vendor.vendor_cmd[1]; - dma_unmap_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, - bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); + if (piocb_rqst->flags & SRB_FXDISC_REQ_DMA_VALID) + dma_unmap_sg(&ha->pdev->dev, + bsg_job->request_payload.sg_list, + bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); + + if (piocb_rqst->flags & SRB_FXDISC_RESP_DMA_VALID) + dma_unmap_sg(&ha->pdev->dev, + bsg_job->reply_payload.sg_list, + bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); + } else { + dma_unmap_sg(&ha->pdev->dev, bsg_job->request_payload.sg_list, + bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); + + dma_unmap_sg(&ha->pdev->dev, bsg_job->reply_payload.sg_list, + bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); + } if (sp->type == SRB_CT_CMD || + sp->type == SRB_FXIOCB_BCMD || sp->type == SRB_ELS_CMD_HST) kfree(sp->fcport); qla2x00_rel_sp(vha, sp); @@ -751,6 +768,8 @@ qla2x00_process_loopback(struct fc_bsg_job *bsg_job) elreq.transfer_size = req_data_len; elreq.options = bsg_job->request->rqst_data.h_vendor.vendor_cmd[1]; + elreq.iteration_count = + bsg_job->request->rqst_data.h_vendor.vendor_cmd[2]; if (atomic_read(&vha->loop_state) == LOOP_READY && (ha->current_topology == ISP_CFG_F || @@ -1883,6 +1902,128 @@ done: } static int +qlafx00_mgmt_cmd(struct fc_bsg_job *bsg_job) +{ + struct Scsi_Host *host = bsg_job->shost; + scsi_qla_host_t *vha = shost_priv(host); + struct qla_hw_data *ha = vha->hw; + int rval = (DRIVER_ERROR << 16); + struct qla_mt_iocb_rqst_fx00 *piocb_rqst; + srb_t *sp; + int req_sg_cnt = 0, rsp_sg_cnt = 0; + struct fc_port *fcport; + char *type = "FC_BSG_HST_FX_MGMT"; + + /* Copy the IOCB specific information */ + piocb_rqst = (struct qla_mt_iocb_rqst_fx00 *) + &bsg_job->request->rqst_data.h_vendor.vendor_cmd[1]; + + /* Dump the vendor information */ + ql_dump_buffer(ql_dbg_user + ql_dbg_verbose , vha, 0x70cf, + (uint8_t *)piocb_rqst, sizeof(struct qla_mt_iocb_rqst_fx00)); + + if (!vha->flags.online) { + ql_log(ql_log_warn, vha, 0x70d0, + "Host is not online.\n"); + rval = -EIO; + goto done; + } + + if (piocb_rqst->flags & SRB_FXDISC_REQ_DMA_VALID) { + req_sg_cnt = dma_map_sg(&ha->pdev->dev, + bsg_job->request_payload.sg_list, + bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); + if (!req_sg_cnt) { + ql_log(ql_log_warn, vha, 0x70c7, + "dma_map_sg return %d for request\n", req_sg_cnt); + rval = -ENOMEM; + goto done; + } + } + + if (piocb_rqst->flags & SRB_FXDISC_RESP_DMA_VALID) { + rsp_sg_cnt = dma_map_sg(&ha->pdev->dev, + bsg_job->reply_payload.sg_list, + bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); + if (!rsp_sg_cnt) { + ql_log(ql_log_warn, vha, 0x70c8, + "dma_map_sg return %d for reply\n", rsp_sg_cnt); + rval = -ENOMEM; + goto done_unmap_req_sg; + } + } + + ql_dbg(ql_dbg_user, vha, 0x70c9, + "request_sg_cnt: %x dma_request_sg_cnt: %x reply_sg_cnt:%x " + "dma_reply_sg_cnt: %x\n", bsg_job->request_payload.sg_cnt, + req_sg_cnt, bsg_job->reply_payload.sg_cnt, rsp_sg_cnt); + + /* Allocate a dummy fcport structure, since functions preparing the + * IOCB and mailbox command retrieves port specific information + * from fcport structure. For Host based ELS commands there will be + * no fcport structure allocated + */ + fcport = qla2x00_alloc_fcport(vha, GFP_KERNEL); + if (!fcport) { + ql_log(ql_log_warn, vha, 0x70ca, + "Failed to allocate fcport.\n"); + rval = -ENOMEM; + goto done_unmap_rsp_sg; + } + + /* Alloc SRB structure */ + sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); + if (!sp) { + ql_log(ql_log_warn, vha, 0x70cb, + "qla2x00_get_sp failed.\n"); + rval = -ENOMEM; + goto done_free_fcport; + } + + /* Initialize all required fields of fcport */ + fcport->vha = vha; + fcport->loop_id = piocb_rqst->dataword; + + sp->type = SRB_FXIOCB_BCMD; + sp->name = "bsg_fx_mgmt"; + sp->iocbs = qla24xx_calc_ct_iocbs(req_sg_cnt + rsp_sg_cnt); + sp->u.bsg_job = bsg_job; + sp->free = qla2x00_bsg_sp_free; + sp->done = qla2x00_bsg_job_done; + + ql_dbg(ql_dbg_user, vha, 0x70cc, + "bsg rqst type: %s fx_mgmt_type: %x id=%x\n", + type, piocb_rqst->func_type, fcport->loop_id); + + rval = qla2x00_start_sp(sp); + if (rval != QLA_SUCCESS) { + ql_log(ql_log_warn, vha, 0x70cd, + "qla2x00_start_sp failed=%d.\n", rval); + mempool_free(sp, ha->srb_mempool); + rval = -EIO; + goto done_free_fcport; + } + return rval; + +done_free_fcport: + kfree(fcport); + +done_unmap_rsp_sg: + if (piocb_rqst->flags & SRB_FXDISC_RESP_DMA_VALID) + dma_unmap_sg(&ha->pdev->dev, + bsg_job->reply_payload.sg_list, + bsg_job->reply_payload.sg_cnt, DMA_FROM_DEVICE); +done_unmap_req_sg: + if (piocb_rqst->flags & SRB_FXDISC_REQ_DMA_VALID) + dma_unmap_sg(&ha->pdev->dev, + bsg_job->request_payload.sg_list, + bsg_job->request_payload.sg_cnt, DMA_TO_DEVICE); + +done: + return rval; +} + +static int qla2x00_process_vendor_specific(struct fc_bsg_job *bsg_job) { switch (bsg_job->request->rqst_data.h_vendor.vendor_cmd[0]) { @@ -1928,6 +2069,8 @@ qla2x00_process_vendor_specific(struct fc_bsg_job *bsg_job) case QL_VND_DIAG_IO_CMD: return qla24xx_process_bidir_cmd(bsg_job); + case QL_VND_FX00_MGMT_CMD: + return qlafx00_mgmt_cmd(bsg_job); default: return -ENOSYS; } @@ -2007,7 +2150,8 @@ qla24xx_bsg_timeout(struct fc_bsg_job *bsg_job) sp = req->outstanding_cmds[cnt]; if (sp) { if (((sp->type == SRB_CT_CMD) || - (sp->type == SRB_ELS_CMD_HST)) + (sp->type == SRB_ELS_CMD_HST) || + (sp->type == SRB_FXIOCB_BCMD)) && (sp->u.bsg_job == bsg_job)) { spin_unlock_irqrestore(&ha->hardware_lock, flags); if (ha->isp_ops->abort_command(sp)) { diff --git a/drivers/scsi/qla2xxx/qla_bsg.h b/drivers/scsi/qla2xxx/qla_bsg.h index e9f6b9bbf29..04f770332c2 100644 --- a/drivers/scsi/qla2xxx/qla_bsg.h +++ b/drivers/scsi/qla2xxx/qla_bsg.h @@ -22,6 +22,7 @@ #define QL_VND_DIAG_IO_CMD 0x0A #define QL_VND_WRITE_I2C 0x10 #define QL_VND_READ_I2C 0x11 +#define QL_VND_FX00_MGMT_CMD 0x12 /* BSG Vendor specific subcode returns */ #define EXT_STATUS_OK 0 diff --git a/drivers/scsi/qla2xxx/qla_dbg.c b/drivers/scsi/qla2xxx/qla_dbg.c index fbc305f1c87..cfa2a20dee9 100644 --- a/drivers/scsi/qla2xxx/qla_dbg.c +++ b/drivers/scsi/qla2xxx/qla_dbg.c @@ -11,28 +11,31 @@ * ---------------------------------------------------------------------- * | Level | Last Value Used | Holes | * ---------------------------------------------------------------------- - * | Module Init and Probe | 0x0126 | 0x4b,0xba,0xfa | - * | Mailbox commands | 0x115b | 0x111a-0x111b | - * | | | 0x112c-0x112e | - * | | | 0x113a | + * | Module Init and Probe | 0x014f | 0x4b,0xba,0xfa | + * | Mailbox commands | 0x1179 | 0x111a-0x111b | * | | | 0x1155-0x1158 | - * | Device Discovery | 0x2087 | 0x2020-0x2022, | + * | Device Discovery | 0x2095 | 0x2020-0x2022, | * | | | 0x2016 | - * | Queue Command and IO tracing | 0x3031 | 0x3006-0x300b | + * | Queue Command and IO tracing | 0x3058 | 0x3006-0x300b | * | | | 0x3027-0x3028 | - * | | | 0x302d-0x302e | - * | DPC Thread | 0x401d | 0x4002,0x4013 | - * | Async Events | 0x5071 | 0x502b-0x502f | + * | | | 0x303d-0x3041 | + * | | | 0x302d,0x3033 | + * | | | 0x3036,0x3038 | + * | | | 0x303a | + * | DPC Thread | 0x4022 | 0x4002,0x4013 | + * | Async Events | 0x5081 | 0x502b-0x502f | * | | | 0x5047,0x5052 | + * | | | 0x5040,0x5075 | * | Timer Routines | 0x6011 | | - * | User Space Interactions | 0x70c4 | 0x7018,0x702e, | + * | User Space Interactions | 0x70dd | 0x7018,0x702e, | * | | | 0x7020,0x7024, | * | | | 0x7039,0x7045, | * | | | 0x7073-0x7075, | - * | | | 0x708c, | + * | | | 0x707b,0x708c, | * | | | 0x70a5,0x70a6, | * | | | 0x70a8,0x70ab, | - * | | | 0x70ad-0x70ae | + * | | | 0x70ad-0x70ae, | + * | | | 0x70d1-0x70da | * | Task Management | 0x803c | 0x8025-0x8026 | * | | | 0x800b,0x8039 | * | AER/EEH | 0x9011 | | diff --git a/drivers/scsi/qla2xxx/qla_def.h b/drivers/scsi/qla2xxx/qla_def.h index 65c5ff75936..c32efc75322 100644 --- a/drivers/scsi/qla2xxx/qla_def.h +++ b/drivers/scsi/qla2xxx/qla_def.h @@ -245,7 +245,6 @@ #define MAX_CMDSZ 16 /* SCSI maximum CDB size. */ #include "qla_fw.h" - /* * Timeout timer counts in seconds */ @@ -265,6 +264,7 @@ #define RESPONSE_ENTRY_CNT_2300 512 /* Number of response entries.*/ #define RESPONSE_ENTRY_CNT_MQ 128 /* Number of response entries.*/ #define ATIO_ENTRY_CNT_24XX 4096 /* Number of ATIO entries. */ +#define RESPONSE_ENTRY_CNT_FX00 256 /* Number of response entries.*/ struct req_que; @@ -284,6 +284,7 @@ struct sd_dif_tuple { struct srb_cmd { struct scsi_cmnd *cmd; /* Linux SCSI command pkt */ uint32_t request_sense_length; + uint32_t fw_sense_length; uint8_t *request_sense_ptr; void *ctx; }; @@ -321,7 +322,39 @@ struct srb_iocb { uint32_t flags; uint32_t lun; uint32_t data; + struct completion comp; + uint32_t comp_status; } tmf; + struct { +#define SRB_FXDISC_REQ_DMA_VALID BIT_0 +#define SRB_FXDISC_RESP_DMA_VALID BIT_1 +#define SRB_FXDISC_REQ_DWRD_VALID BIT_2 +#define SRB_FXDISC_RSP_DWRD_VALID BIT_3 +#define FXDISC_TIMEOUT 20 + uint8_t flags; + uint32_t req_len; + uint32_t rsp_len; + void *req_addr; + void *rsp_addr; + dma_addr_t req_dma_handle; + dma_addr_t rsp_dma_handle; + uint32_t adapter_id; + uint32_t adapter_id_hi; + uint32_t req_func_type; + uint32_t req_data; + uint32_t req_data_extra; + uint32_t result; + uint32_t seq_number; + uint32_t fw_flags; + struct completion fxiocb_comp; + uint32_t reserved_0; + uint8_t reserved_1; + } fxiocb; + struct { + uint32_t cmd_hndl; + uint32_t comp_status; + struct completion comp; + } abt; } u; struct timer_list timer; @@ -338,6 +371,10 @@ struct srb_iocb { #define SRB_TM_CMD 7 #define SRB_SCSI_CMD 8 #define SRB_BIDI_CMD 9 +#define SRB_FXIOCB_DCMD 10 +#define SRB_FXIOCB_BCMD 11 +#define SRB_ABT_CMD 12 + typedef struct srb { atomic_t ref_count; @@ -368,6 +405,10 @@ typedef struct srb { (sp->u.scmd.request_sense_ptr) #define SET_CMD_SENSE_PTR(sp, ptr) \ (sp->u.scmd.request_sense_ptr = ptr) +#define GET_FW_SENSE_LEN(sp) \ + (sp->u.scmd.fw_sense_length) +#define SET_FW_SENSE_LEN(sp, len) \ + (sp->u.scmd.fw_sense_length = len) struct msg_echo_lb { dma_addr_t send_dma; @@ -376,6 +417,7 @@ struct msg_echo_lb { uint16_t rsp_sg_cnt; uint16_t options; uint32_t transfer_size; + uint32_t iteration_count; }; /* @@ -542,11 +584,74 @@ struct device_reg_25xxmq { uint32_t atio_q_out; }; + +struct device_reg_fx00 { + uint32_t mailbox0; /* 00 */ + uint32_t mailbox1; /* 04 */ + uint32_t mailbox2; /* 08 */ + uint32_t mailbox3; /* 0C */ + uint32_t mailbox4; /* 10 */ + uint32_t mailbox5; /* 14 */ + uint32_t mailbox6; /* 18 */ + uint32_t mailbox7; /* 1C */ + uint32_t mailbox8; /* 20 */ + uint32_t mailbox9; /* 24 */ + uint32_t mailbox10; /* 28 */ + uint32_t mailbox11; + uint32_t mailbox12; + uint32_t mailbox13; + uint32_t mailbox14; + uint32_t mailbox15; + uint32_t mailbox16; + uint32_t mailbox17; + uint32_t mailbox18; + uint32_t mailbox19; + uint32_t mailbox20; + uint32_t mailbox21; + uint32_t mailbox22; + uint32_t mailbox23; + uint32_t mailbox24; + uint32_t mailbox25; + uint32_t mailbox26; + uint32_t mailbox27; + uint32_t mailbox28; + uint32_t mailbox29; + uint32_t mailbox30; + uint32_t mailbox31; + uint32_t aenmailbox0; + uint32_t aenmailbox1; + uint32_t aenmailbox2; + uint32_t aenmailbox3; + uint32_t aenmailbox4; + uint32_t aenmailbox5; + uint32_t aenmailbox6; + uint32_t aenmailbox7; + /* Request Queue. */ + uint32_t req_q_in; /* A0 - Request Queue In-Pointer */ + uint32_t req_q_out; /* A4 - Request Queue Out-Pointer */ + /* Response Queue. */ + uint32_t rsp_q_in; /* A8 - Response Queue In-Pointer */ + uint32_t rsp_q_out; /* AC - Response Queue Out-Pointer */ + /* Init values shadowed on FW Up Event */ + uint32_t initval0; /* B0 */ + uint32_t initval1; /* B4 */ + uint32_t initval2; /* B8 */ + uint32_t initval3; /* BC */ + uint32_t initval4; /* C0 */ + uint32_t initval5; /* C4 */ + uint32_t initval6; /* C8 */ + uint32_t initval7; /* CC */ + uint32_t fwheartbeat; /* D0 */ +}; + + + typedef union { struct device_reg_2xxx isp; struct device_reg_24xx isp24; struct device_reg_25xxmq isp25mq; struct device_reg_82xx isp82; + struct device_reg_fx00 ispfx00; } device_reg_t; #define ISP_REQ_Q_IN(ha, reg) \ @@ -602,6 +707,20 @@ typedef struct { #define IOCTL_CMD BIT_2 } mbx_cmd_t; +struct mbx_cmd_32 { + uint32_t out_mb; /* outbound from driver */ + uint32_t in_mb; /* Incoming from RISC */ + uint32_t mb[MAILBOX_REGISTER_COUNT]; + long buf_size; + void *bufp; + uint32_t tov; + uint8_t flags; +#define MBX_DMA_IN BIT_0 +#define MBX_DMA_OUT BIT_1 +#define IOCTL_CMD BIT_2 +}; + + #define MBX_TOV_SECONDS 30 /* @@ -677,6 +796,15 @@ typedef struct { #define MBA_BYPASS_NOTIFICATION 0x8043 /* Auto bypass notification. */ #define MBA_DISCARD_RND_FRAME 0x8048 /* discard RND frame due to error. */ #define MBA_REJECTED_FCP_CMD 0x8049 /* rejected FCP_CMD. */ +#define MBA_FW_NOT_STARTED 0x8050 /* Firmware not started */ +#define MBA_FW_STARTING 0x8051 /* Firmware starting */ +#define MBA_FW_RESTART_CMPLT 0x8060 /* Firmware restart complete */ +#define MBA_INIT_REQUIRED 0x8061 /* Initialization required */ +#define MBA_SHUTDOWN_REQUESTED 0x8062 /* Shutdown Requested */ +#define MBA_FW_INIT_FAILURE 0x8401 /* Firmware initialization failure */ +#define MBA_MIRROR_LUN_CHANGE 0x8402 /* Mirror LUN State Change + Notification */ +#define MBA_FW_POLL_STATE 0x8600 /* Firmware in poll diagnostic state */ /* 83XX FCoE specific */ #define MBA_IDC_AEN 0x8200 /* FCoE: NIC Core state change AEN */ @@ -798,6 +926,12 @@ typedef struct { #define MBC_LUN_RESET 0x7E /* Send LUN reset */ /* + * all the Mt. Rainier mailbox command codes that clash with FC/FCoE ones + * should be defined with MBC_MR_* + */ +#define MBC_MR_DRV_SHUTDOWN 0x6A + +/* * ISP24xx mailbox commands */ #define MBC_SERDES_PARAMS 0x10 /* Serdes Tx Parameters. */ @@ -1058,6 +1192,30 @@ typedef struct { uint8_t reserved_3[26]; } init_cb_t; + +struct init_cb_fx { + uint16_t version; + uint16_t reserved_1[13]; + uint16_t request_q_outpointer; + uint16_t response_q_inpointer; + uint16_t reserved_2[2]; + uint16_t response_q_length; + uint16_t request_q_length; + uint16_t reserved_3[2]; + uint32_t request_q_address[2]; + uint32_t response_q_address[2]; + uint16_t reserved_4[4]; + uint8_t response_q_msivec; + uint8_t reserved_5[19]; + uint16_t interrupt_delay_timer; + uint16_t reserved_6; + uint32_t fwoptions1; + uint32_t fwoptions2; + uint32_t fwoptions3; + uint8_t reserved_7[24]; +}; + + /* * Get Link Status mailbox command return buffer. */ @@ -1831,6 +1989,9 @@ typedef struct fc_port { uint16_t loop_id; uint16_t old_loop_id; + uint16_t tgt_id; + uint16_t old_tgt_id; + uint8_t fcp_prio; uint8_t fabric_port_name[WWN_SIZE]; @@ -1848,8 +2009,15 @@ typedef struct fc_port { uint8_t fc4_type; uint8_t scan_state; + + unsigned long last_queue_full; + unsigned long last_ramp_up; + + uint16_t port_id; } fc_port_t; +#include "qla_mr.h" + /* * Fibre channel port/lun states. */ @@ -2391,6 +2559,7 @@ struct isp_operations { int (*start_scsi) (srb_t *); int (*abort_isp) (struct scsi_qla_host *); int (*iospace_config)(struct qla_hw_data*); + int (*initialize_adapter)(struct scsi_qla_host *); }; /* MSI-X Support *************************************************************/ @@ -2429,6 +2598,7 @@ enum qla_work_type { QLA_EVT_ASYNC_ADISC, QLA_EVT_ASYNC_ADISC_DONE, QLA_EVT_UEVENT, + QLA_EVT_AENFX, }; @@ -2456,7 +2626,15 @@ struct qla_work_evt { u32 code; #define QLA_UEVENT_CODE_FW_DUMP 0 } uevent; - } u; + struct { + uint32_t evtcode; + uint32_t mbx[8]; + uint32_t count; + } aenfx; + struct { + srb_t *sp; + } iosb; + } u; }; struct qla_chip_state_84xx { @@ -2520,6 +2698,11 @@ struct rsp_que { struct req_que *req; srb_t *status_srb; /* status continuation entry */ struct work_struct q_work; + + dma_addr_t dma_fx00; + response_t *ring_fx00; + uint16_t length_fx00; + uint8_t rsp_pkt[REQUEST_ENTRY_SIZE]; }; /* Request queue data structure */ @@ -2544,6 +2727,11 @@ struct req_que { uint16_t num_outstanding_cmds; #define MAX_Q_DEPTH 32 int max_q_depth; + + dma_addr_t dma_fx00; + request_t *ring_fx00; + uint16_t length_fx00; + uint8_t req_pkt[REQUEST_ENTRY_SIZE]; }; /* Place holder for FW buffer parameters */ @@ -2633,7 +2821,10 @@ struct qla_hw_data { uint32_t isp82xx_no_md_cap:1; uint32_t host_shutting_down:1; uint32_t idc_compl_status:1; - /* 32 bits */ + + uint32_t mr_reset_hdlr_active:1; + uint32_t mr_intr_valid:1; + /* 34 bits */ } flags; /* This spinlock is used to protect "io transactions", you must @@ -2650,7 +2841,21 @@ struct qla_hw_data { resource_size_t pio_address; #define MIN_IOBASE_LEN 0x100 -/* Multi queue data structs */ + dma_addr_t bar0_hdl; + + void __iomem *cregbase; + dma_addr_t bar2_hdl; +#define BAR0_LEN_FX00 (1024 * 1024) +#define BAR2_LEN_FX00 (128 * 1024) + + uint32_t rqstq_intr_code; + uint32_t mbx_intr_code; + uint32_t req_que_len; + uint32_t rsp_que_len; + uint32_t req_que_off; + uint32_t rsp_que_off; + + /* Multi queue data structs */ device_reg_t __iomem *mqiobase; device_reg_t __iomem *msixbase; uint16_t msix_count; @@ -2729,7 +2934,8 @@ struct qla_hw_data { #define DT_ISP8021 BIT_14 #define DT_ISP2031 BIT_15 #define DT_ISP8031 BIT_16 -#define DT_ISP_LAST (DT_ISP8031 << 1) +#define DT_ISPFX00 BIT_17 +#define DT_ISP_LAST (DT_ISPFX00 << 1) #define DT_T10_PI BIT_25 #define DT_IIDMA BIT_26 @@ -2757,6 +2963,7 @@ struct qla_hw_data { #define IS_QLA82XX(ha) (DT_MASK(ha) & DT_ISP8021) #define IS_QLA2031(ha) (DT_MASK(ha) & DT_ISP2031) #define IS_QLA8031(ha) (DT_MASK(ha) & DT_ISP8031) +#define IS_QLAFX00(ha) (DT_MASK(ha) & DT_ISPFX00) #define IS_QLA23XX(ha) (IS_QLA2300(ha) || IS_QLA2312(ha) || IS_QLA2322(ha) || \ IS_QLA6312(ha) || IS_QLA6322(ha)) @@ -2821,6 +3028,7 @@ struct qla_hw_data { uint16_t r_a_tov; int port_down_retry_count; uint8_t mbx_count; + uint8_t aen_mbx_count; uint32_t login_retry_count; /* SNS command interfaces. */ @@ -2868,9 +3076,13 @@ struct qla_hw_data { void *swl; /* These are used by mailbox operations. */ - volatile uint16_t mailbox_out[MAILBOX_REGISTER_COUNT]; + uint16_t mailbox_out[MAILBOX_REGISTER_COUNT]; + uint32_t mailbox_out32[MAILBOX_REGISTER_COUNT]; + uint32_t aenmb[AEN_MAILBOX_REGISTER_COUNT_FX00]; mbx_cmd_t *mcp; + struct mbx_cmd_32 *mcp32; + unsigned long mbx_cmd_flags; #define MBX_INTERRUPT 1 #define MBX_INTR_WAIT 2 @@ -3014,6 +3226,7 @@ struct qla_hw_data { int cur_vport_count; struct qla_chip_state_84xx *cs84xx; + struct qla_statistics qla_stats; struct isp_operations *isp_ops; struct workqueue_struct *wq; struct qlfc_fw fw_buf; @@ -3080,6 +3293,8 @@ struct qla_hw_data { unsigned long host_last_rampup_time; int cfg_lun_q_depth; + struct mr_data_fx00 mr; + struct qlt_hw_data tgt; uint16_t thermal_support; #define THERMAL_SUPPORT_I2C BIT_0 @@ -3109,6 +3324,8 @@ typedef struct scsi_qla_host { uint32_t process_response_queue :1; uint32_t difdix_supported:1; uint32_t delete_progress:1; + + uint32_t fw_tgt_reported:1; } flags; atomic_t loop_state; @@ -3144,6 +3361,9 @@ typedef struct scsi_qla_host { #define SCR_PENDING 21 /* SCR in target mode */ #define HOST_RAMP_DOWN_QUEUE_DEPTH 22 #define HOST_RAMP_UP_QUEUE_DEPTH 23 +#define PORT_UPDATE_NEEDED 24 +#define FX00_RESET_RECOVERY 25 +#define FX00_TARGET_SCAN 26 uint32_t device_flags; #define SWITCH_FOUND BIT_0 @@ -3234,6 +3454,10 @@ struct qla_tgt_vp_map { test_bit(LOOP_RESYNC_NEEDED, &ha->dpc_flags) || \ atomic_read(&ha->loop_state) == LOOP_DOWN) +#define STATE_TRANSITION(ha) \ + (test_bit(ISP_ABORT_NEEDED, &ha->dpc_flags) || \ + test_bit(LOOP_RESYNC_NEEDED, &ha->dpc_flags)) + #define QLA_VHA_MARK_BUSY(__vha, __bail) do { \ atomic_inc(&__vha->vref_count); \ mb(); \ diff --git a/drivers/scsi/qla2xxx/qla_gbl.h b/drivers/scsi/qla2xxx/qla_gbl.h index b310fa97b54..026bfde33e6 100644 --- a/drivers/scsi/qla2xxx/qla_gbl.h +++ b/drivers/scsi/qla2xxx/qla_gbl.h @@ -86,6 +86,7 @@ extern int qla2xxx_mctp_dump(scsi_qla_host_t *); extern int qla2x00_alloc_outstanding_cmds(struct qla_hw_data *, struct req_que *); +extern int qla2x00_init_rings(scsi_qla_host_t *); /* * Global Data in qla_os.c source file. @@ -134,7 +135,6 @@ extern int qla2x00_post_async_adisc_work(struct scsi_qla_host *, fc_port_t *, uint16_t *); extern int qla2x00_post_async_adisc_done_work(struct scsi_qla_host *, fc_port_t *, uint16_t *); -extern int qla2x00_post_uevent_work(struct scsi_qla_host *, u32); extern int qla81xx_restart_mpi_firmware(scsi_qla_host_t *); @@ -158,6 +158,7 @@ extern int qla83xx_set_drv_presence(scsi_qla_host_t *vha); extern int __qla83xx_set_drv_presence(scsi_qla_host_t *vha); extern int qla83xx_clear_drv_presence(scsi_qla_host_t *vha); extern int __qla83xx_clear_drv_presence(scsi_qla_host_t *vha); +extern int qla2x00_post_uevent_work(struct scsi_qla_host *, u32); /* * Global Functions in qla_mid.c source file. @@ -211,8 +212,6 @@ extern int qla24xx_start_scsi(srb_t *sp); int qla2x00_marker(struct scsi_qla_host *, struct req_que *, struct rsp_que *, uint16_t, uint16_t, uint8_t); extern int qla2x00_start_sp(srb_t *); -extern uint16_t qla24xx_calc_iocbs(scsi_qla_host_t *, uint16_t); -extern void qla24xx_build_scsi_iocbs(srb_t *, struct cmd_type_7 *, uint16_t); extern int qla24xx_dif_start_scsi(srb_t *); extern int qla2x00_start_bidir(srb_t *, struct scsi_qla_host *, uint32_t); extern unsigned long qla2x00_get_async_timeout(struct scsi_qla_host *); @@ -424,6 +423,12 @@ extern void qla2x00_free_irqs(scsi_qla_host_t *); extern int qla2x00_get_data_rate(scsi_qla_host_t *); extern const char *qla2x00_get_link_speed_str(struct qla_hw_data *, uint16_t); +extern srb_t * +qla2x00_get_sp_from_handle(scsi_qla_host_t *, const char *, struct req_que *, + void *); +extern void +qla2x00_process_completed_request(struct scsi_qla_host *, struct req_que *, + uint32_t); /* * Global Function Prototypes in qla_sup.c source file. @@ -561,6 +566,42 @@ extern void qla25xx_wrt_req_reg(struct qla_hw_data *, uint16_t, uint16_t); extern void qla25xx_wrt_rsp_reg(struct qla_hw_data *, uint16_t, uint16_t); extern void qla24xx_wrt_rsp_reg(struct qla_hw_data *, uint16_t, uint16_t); +/* qlafx00 related functions */ +extern int qlafx00_pci_config(struct scsi_qla_host *); +extern int qlafx00_initialize_adapter(struct scsi_qla_host *); +extern void qlafx00_soft_reset(scsi_qla_host_t *); +extern int qlafx00_chip_diag(scsi_qla_host_t *); +extern void qlafx00_config_rings(struct scsi_qla_host *); +extern char *qlafx00_pci_info_str(struct scsi_qla_host *, char *); +extern char *qlafx00_fw_version_str(struct scsi_qla_host *, char *); +extern irqreturn_t qlafx00_intr_handler(int, void *); +extern void qlafx00_enable_intrs(struct qla_hw_data *); +extern void qlafx00_disable_intrs(struct qla_hw_data *); +extern int qlafx00_abort_command(srb_t *); +extern int qlafx00_abort_target(fc_port_t *, unsigned int, int); +extern int qlafx00_lun_reset(fc_port_t *, unsigned int, int); +extern int qlafx00_start_scsi(srb_t *); +extern int qlafx00_abort_isp(scsi_qla_host_t *); +extern int qlafx00_iospace_config(struct qla_hw_data *); +extern int qlafx00_init_firmware(scsi_qla_host_t *, uint16_t); +extern int qlafx00_fw_ready(scsi_qla_host_t *); +extern int qlafx00_configure_devices(scsi_qla_host_t *); +extern int qlafx00_reset_initialize(scsi_qla_host_t *); +extern int qlafx00_fx_disc(scsi_qla_host_t *, fc_port_t *, uint8_t); +extern int qlafx00_process_aen(struct scsi_qla_host *, struct qla_work_evt *); +extern int qlafx00_post_aenfx_work(struct scsi_qla_host *, uint32_t, + uint32_t *, int); +extern uint32_t qlafx00_fw_state_show(struct device *, + struct device_attribute *, char *); +extern void qlafx00_get_host_speed(struct Scsi_Host *); +extern void qlafx00_init_response_q_entries(struct rsp_que *); + +extern void qlafx00_tm_iocb(srb_t *, struct tsk_mgmt_entry_fx00 *); +extern void qlafx00_abort_iocb(srb_t *, struct abort_iocb_entry_fx00 *); +extern void qlafx00_fxdisc_iocb(srb_t *, struct fxdisc_entry_fx00 *); +extern void qlafx00_timer_routine(scsi_qla_host_t *); +extern int qlafx00_rescan_isp(scsi_qla_host_t *); + /* qla82xx related functions */ /* PCI related functions */ diff --git a/drivers/scsi/qla2xxx/qla_gs.c b/drivers/scsi/qla2xxx/qla_gs.c index 9b455250c10..d0ea8b92117 100644 --- a/drivers/scsi/qla2xxx/qla_gs.c +++ b/drivers/scsi/qla2xxx/qla_gs.c @@ -639,9 +639,14 @@ void qla2x00_get_sym_node_name(scsi_qla_host_t *vha, uint8_t *snn) { struct qla_hw_data *ha = vha->hw; - sprintf(snn, "%s FW:v%d.%02d.%02d DVR:v%s",ha->model_number, - ha->fw_major_version, ha->fw_minor_version, - ha->fw_subminor_version, qla2x00_version_str); + + if (IS_QLAFX00(ha)) + sprintf(snn, "%s FW:v%s DVR:v%s", ha->model_number, + ha->mr.fw_version, qla2x00_version_str); + else + sprintf(snn, "%s FW:v%d.%02d.%02d DVR:v%s", ha->model_number, + ha->fw_major_version, ha->fw_minor_version, + ha->fw_subminor_version, qla2x00_version_str); } /** @@ -923,7 +928,7 @@ qla2x00_sns_gpn_id(scsi_qla_host_t *vha, sw_info_t *list) sns_cmd->p.gpn_data[9] != 0x02) { ql_dbg(ql_dbg_disc + ql_dbg_buffer, vha, 0x207e, "GPN_ID failed, rejected request, gpn_rsp:\n"); - ql_dump_buffer(ql_dbg_disc, vha, 0x207f, + ql_dump_buffer(ql_dbg_disc + ql_dbg_buffer, vha, 0x207f, sns_cmd->p.gpn_data, 16); rval = QLA_FUNCTION_FAILED; } else { @@ -1718,7 +1723,8 @@ qla2x00_fdmi_register(scsi_qla_host_t *vha) int rval; struct qla_hw_data *ha = vha->hw; - if (IS_QLA2100(ha) || IS_QLA2200(ha)) + if (IS_QLA2100(ha) || IS_QLA2200(ha) || + IS_QLAFX00(ha)) return QLA_FUNCTION_FAILED; rval = qla2x00_mgmt_svr_login(vha); diff --git a/drivers/scsi/qla2xxx/qla_init.c b/drivers/scsi/qla2xxx/qla_init.c index b59203393cb..3565dfd8f37 100644 --- a/drivers/scsi/qla2xxx/qla_init.c +++ b/drivers/scsi/qla2xxx/qla_init.c @@ -25,7 +25,6 @@ */ static int qla2x00_isp_firmware(scsi_qla_host_t *); static int qla2x00_setup_chip(scsi_qla_host_t *); -static int qla2x00_init_rings(scsi_qla_host_t *); static int qla2x00_fw_ready(scsi_qla_host_t *); static int qla2x00_configure_hba(scsi_qla_host_t *); static int qla2x00_configure_loop(scsi_qla_host_t *); @@ -83,7 +82,9 @@ qla2x00_get_async_timeout(struct scsi_qla_host *vha) /* Firmware should use switch negotiated r_a_tov for timeout. */ tmo = ha->r_a_tov / 10 * 2; - if (!IS_FWI2_CAPABLE(ha)) { + if (IS_QLAFX00(ha)) { + tmo = FX00_DEF_RATOV * 2; + } else if (!IS_FWI2_CAPABLE(ha)) { /* * Except for earlier ISPs where the timeout is seeded from the * initialization control block. @@ -1977,7 +1978,7 @@ qla24xx_config_rings(struct scsi_qla_host *vha) * * Returns 0 on success. */ -static int +int qla2x00_init_rings(scsi_qla_host_t *vha) { int rval; @@ -2012,7 +2013,10 @@ qla2x00_init_rings(scsi_qla_host_t *vha) if (!rsp) continue; /* Initialize response queue entries */ - qla2x00_init_response_q_entries(rsp); + if (IS_QLAFX00(ha)) + qlafx00_init_response_q_entries(rsp); + else + qla2x00_init_response_q_entries(rsp); } ha->tgt.atio_ring_ptr = ha->tgt.atio_ring; @@ -2024,11 +2028,16 @@ qla2x00_init_rings(scsi_qla_host_t *vha) spin_unlock_irqrestore(&ha->hardware_lock, flags); + ql_dbg(ql_dbg_init, vha, 0x00d1, "Issue init firmware.\n"); + + if (IS_QLAFX00(ha)) { + rval = qlafx00_init_firmware(vha, ha->init_cb_size); + goto next_check; + } + /* Update any ISP specific firmware options before initialization. */ ha->isp_ops->update_fw_options(vha); - ql_dbg(ql_dbg_init, vha, 0x00d1, "Issue init firmware.\n"); - if (ha->flags.npiv_supported) { if (ha->operating_mode == LOOP && !IS_CNA_CAPABLE(ha)) ha->max_npiv_vports = MIN_MULTI_ID_FABRIC - 1; @@ -2042,6 +2051,7 @@ qla2x00_init_rings(scsi_qla_host_t *vha) } rval = qla2x00_init_firmware(vha, ha->init_cb_size); +next_check: if (rval) { ql_log(ql_log_fatal, vha, 0x00d2, "Init Firmware **** FAILED ****.\n"); @@ -2069,6 +2079,9 @@ qla2x00_fw_ready(scsi_qla_host_t *vha) uint16_t state[5]; struct qla_hw_data *ha = vha->hw; + if (IS_QLAFX00(vha->hw)) + return qlafx00_fw_ready(vha); + rval = QLA_SUCCESS; /* 20 seconds for loop down. */ @@ -3134,6 +3147,12 @@ void qla2x00_update_fcport(scsi_qla_host_t *vha, fc_port_t *fcport) { fcport->vha = vha; + + if (IS_QLAFX00(vha->hw)) { + qla2x00_set_fcport_state(fcport, FCS_ONLINE); + qla2x00_reg_remote_port(vha, fcport); + return; + } fcport->login_retry = 0; fcport->flags &= ~(FCF_LOGIN_NEEDED | FCF_ASYNC_SENT); @@ -3894,15 +3913,24 @@ qla2x00_loop_resync(scsi_qla_host_t *vha) /* Wait at most MAX_TARGET RSCNs for a stable link. */ wait_time = 256; do { - /* Issue a marker after FW becomes ready. */ - qla2x00_marker(vha, req, rsp, 0, 0, - MK_SYNC_ALL); - vha->marker_needed = 0; + if (!IS_QLAFX00(vha->hw)) { + /* + * Issue a marker after FW becomes + * ready. + */ + qla2x00_marker(vha, req, rsp, 0, 0, + MK_SYNC_ALL); + vha->marker_needed = 0; + } /* Remap devices on Loop. */ clear_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); - qla2x00_configure_loop(vha); + if (IS_QLAFX00(vha->hw)) + qlafx00_configure_devices(vha); + else + qla2x00_configure_loop(vha); + wait_time--; } while (!atomic_read(&vha->loop_down_timer) && !(test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags)) @@ -3968,9 +3996,7 @@ qla2x00_update_fcports(scsi_qla_host_t *base_vha) if (fcport->drport && atomic_read(&fcport->state) != FCS_UNCONFIGURED) { spin_unlock_irqrestore(&ha->vport_slock, flags); - qla2x00_rport_del(fcport); - spin_lock_irqsave(&ha->vport_slock, flags); } } diff --git a/drivers/scsi/qla2xxx/qla_inline.h b/drivers/scsi/qla2xxx/qla_inline.h index 68e2c4afc13..98ab921070d 100644 --- a/drivers/scsi/qla2xxx/qla_inline.h +++ b/drivers/scsi/qla2xxx/qla_inline.h @@ -5,6 +5,28 @@ * See LICENSE.qla2xxx for copyright and licensing details. */ +/** + * qla24xx_calc_iocbs() - Determine number of Command Type 3 and + * Continuation Type 1 IOCBs to allocate. + * + * @dsds: number of data segment decriptors needed + * + * Returns the number of IOCB entries needed to store @dsds. + */ +static inline uint16_t +qla24xx_calc_iocbs(scsi_qla_host_t *vha, uint16_t dsds) +{ + uint16_t iocbs; + + iocbs = 1; + if (dsds > 1) { + iocbs += (dsds - 1) / 5; + if ((dsds - 1) % 5) + iocbs++; + } + return iocbs; +} + /* * qla2x00_debounce_register * Debounce register. @@ -58,6 +80,17 @@ host_to_fcp_swap(uint8_t *fcp, uint32_t bsize) } static inline void +host_to_adap(uint8_t *src, uint8_t *dst, uint32_t bsize) +{ + uint32_t *isrc = (uint32_t *) src; + uint32_t *odest = (uint32_t *) dst; + uint32_t iter = bsize >> 2; + + for (; iter ; iter--) + *odest++ = cpu_to_le32(*isrc++); +} + +static inline void qla2x00_set_reserved_loop_ids(struct qla_hw_data *ha) { int i; @@ -213,12 +246,18 @@ qla2x00_init_timer(srb_t *sp, unsigned long tmo) sp->u.iocb_cmd.timer.function = qla2x00_sp_timeout; add_timer(&sp->u.iocb_cmd.timer); sp->free = qla2x00_sp_free; + if ((IS_QLAFX00(sp->fcport->vha->hw)) && + (sp->type == SRB_FXIOCB_DCMD)) + init_completion(&sp->u.iocb_cmd.u.fxiocb.fxiocb_comp); } static inline int qla2x00_gid_list_size(struct qla_hw_data *ha) { - return sizeof(struct gid_list_info) * ha->max_fibre_devices; + if (IS_QLAFX00(ha)) + return sizeof(uint32_t) * 32; + else + return sizeof(struct gid_list_info) * ha->max_fibre_devices; } static inline void diff --git a/drivers/scsi/qla2xxx/qla_iocb.c b/drivers/scsi/qla2xxx/qla_iocb.c index d2630317cce..15e4080b347 100644 --- a/drivers/scsi/qla2xxx/qla_iocb.c +++ b/drivers/scsi/qla2xxx/qla_iocb.c @@ -135,7 +135,8 @@ qla2x00_prep_cont_type1_iocb(scsi_qla_host_t *vha, struct req_que *req) cont_pkt = (cont_a64_entry_t *)req->ring_ptr; /* Load packet defaults. */ - *((uint32_t *)(&cont_pkt->entry_type)) = + *((uint32_t *)(&cont_pkt->entry_type)) = IS_QLAFX00(vha->hw) ? + __constant_cpu_to_le32(CONTINUE_A64_TYPE_FX00) : __constant_cpu_to_le32(CONTINUE_A64_TYPE); return (cont_pkt); @@ -486,6 +487,10 @@ qla2x00_start_iocbs(struct scsi_qla_host *vha, struct req_que *req) if (ha->mqenable || IS_QLA83XX(ha)) { WRT_REG_DWORD(req->req_q_in, req->ring_index); RD_REG_DWORD_RELAXED(&ha->iobase->isp24.hccr); + } else if (IS_QLAFX00(ha)) { + WRT_REG_DWORD(®->ispfx00.req_q_in, req->ring_index); + RD_REG_DWORD_RELAXED(®->ispfx00.req_q_in); + QLAFX00_SET_HST_INTR(ha, ha->rqstq_intr_code); } else if (IS_FWI2_CAPABLE(ha)) { WRT_REG_DWORD(®->isp24.req_q_in, req->ring_index); RD_REG_DWORD_RELAXED(®->isp24.req_q_in); @@ -514,11 +519,12 @@ __qla2x00_marker(struct scsi_qla_host *vha, struct req_que *req, uint16_t lun, uint8_t type) { mrk_entry_t *mrk; - struct mrk_entry_24xx *mrk24; + struct mrk_entry_24xx *mrk24 = NULL; + struct mrk_entry_fx00 *mrkfx = NULL; + struct qla_hw_data *ha = vha->hw; scsi_qla_host_t *base_vha = pci_get_drvdata(ha->pdev); - mrk24 = NULL; req = ha->req_q_map[0]; mrk = (mrk_entry_t *)qla2x00_alloc_iocbs(vha, NULL); if (mrk == NULL) { @@ -531,7 +537,15 @@ __qla2x00_marker(struct scsi_qla_host *vha, struct req_que *req, mrk->entry_type = MARKER_TYPE; mrk->modifier = type; if (type != MK_SYNC_ALL) { - if (IS_FWI2_CAPABLE(ha)) { + if (IS_QLAFX00(ha)) { + mrkfx = (struct mrk_entry_fx00 *) mrk; + mrkfx->handle = MAKE_HANDLE(req->id, mrkfx->handle); + mrkfx->handle_hi = 0; + mrkfx->tgt_id = cpu_to_le16(loop_id); + mrkfx->lun[1] = LSB(lun); + mrkfx->lun[2] = MSB(lun); + host_to_fcp_swap(mrkfx->lun, sizeof(mrkfx->lun)); + } else if (IS_FWI2_CAPABLE(ha)) { mrk24 = (struct mrk_entry_24xx *) mrk; mrk24->nport_handle = cpu_to_le16(loop_id); mrk24->lun[1] = LSB(lun); @@ -589,28 +603,6 @@ int qla2x00_issue_marker(scsi_qla_host_t *vha, int ha_locked) return QLA_SUCCESS; } -/** - * qla24xx_calc_iocbs() - Determine number of Command Type 3 and - * Continuation Type 1 IOCBs to allocate. - * - * @dsds: number of data segment decriptors needed - * - * Returns the number of IOCB entries needed to store @dsds. - */ -inline uint16_t -qla24xx_calc_iocbs(scsi_qla_host_t *vha, uint16_t dsds) -{ - uint16_t iocbs; - - iocbs = 1; - if (dsds > 1) { - iocbs += (dsds - 1) / 5; - if ((dsds - 1) % 5) - iocbs++; - } - return iocbs; -} - static inline int qla24xx_build_scsi_type_6_iocbs(srb_t *sp, struct cmd_type_6 *cmd_pkt, uint16_t tot_dsds) @@ -1583,7 +1575,6 @@ queuing_error: return QLA_FUNCTION_FAILED; } - /** * qla24xx_dif_start_scsi() - Send a SCSI command to the ISP * @sp: command to send to the ISP @@ -1852,6 +1843,8 @@ skip_cmd_array: cnt = RD_REG_DWORD(®->isp82.req_q_out); else if (IS_FWI2_CAPABLE(ha)) cnt = RD_REG_DWORD(®->isp24.req_q_out); + else if (IS_QLAFX00(ha)) + cnt = RD_REG_DWORD(®->ispfx00.req_q_out); else cnt = qla2x00_debounce_register( ISP_REQ_Q_OUT(ha, ®->isp)); @@ -1869,8 +1862,13 @@ skip_cmd_array: req->cnt -= req_cnt; pkt = req->ring_ptr; memset(pkt, 0, REQUEST_ENTRY_SIZE); - pkt->entry_count = req_cnt; - pkt->handle = handle; + if (IS_QLAFX00(ha)) { + WRT_REG_BYTE(&pkt->entry_count, req_cnt); + WRT_REG_WORD(&pkt->handle, handle); + } else { + pkt->entry_count = req_cnt; + pkt->handle = handle; + } queuing_error: return pkt; @@ -2625,7 +2623,16 @@ qla2x00_start_sp(srb_t *sp) qla2x00_adisc_iocb(sp, pkt); break; case SRB_TM_CMD: - qla24xx_tm_iocb(sp, pkt); + IS_QLAFX00(ha) ? + qlafx00_tm_iocb(sp, pkt) : + qla24xx_tm_iocb(sp, pkt); + break; + case SRB_FXIOCB_DCMD: + case SRB_FXIOCB_BCMD: + qlafx00_fxdisc_iocb(sp, pkt); + break; + case SRB_ABT_CMD: + qlafx00_abort_iocb(sp, pkt); break; default: break; diff --git a/drivers/scsi/qla2xxx/qla_isr.c b/drivers/scsi/qla2xxx/qla_isr.c index e9dbd74c20d..259d9205d87 100644 --- a/drivers/scsi/qla2xxx/qla_isr.c +++ b/drivers/scsi/qla2xxx/qla_isr.c @@ -13,11 +13,7 @@ #include <scsi/scsi_bsg_fc.h> #include <scsi/scsi_eh.h> -#include "qla_target.h" - static void qla2x00_mbx_completion(scsi_qla_host_t *, uint16_t); -static void qla2x00_process_completed_request(struct scsi_qla_host *, - struct req_que *, uint32_t); static void qla2x00_status_entry(scsi_qla_host_t *, struct rsp_que *, void *); static void qla2x00_status_cont_entry(struct rsp_que *, sts_cont_entry_t *); static void qla2x00_error_entry(scsi_qla_host_t *, struct rsp_que *, @@ -1065,9 +1061,9 @@ skip_rio: * @ha: SCSI driver HA context * @index: SRB index */ -static void +void qla2x00_process_completed_request(struct scsi_qla_host *vha, - struct req_que *req, uint32_t index) + struct req_que *req, uint32_t index) { srb_t *sp; struct qla_hw_data *ha = vha->hw; @@ -1101,7 +1097,7 @@ qla2x00_process_completed_request(struct scsi_qla_host *vha, } } -static srb_t * +srb_t * qla2x00_get_sp_from_handle(scsi_qla_host_t *vha, const char *func, struct req_que *req, void *iocb) { @@ -1994,7 +1990,7 @@ qla2x00_status_entry(scsi_qla_host_t *vha, struct rsp_que *rsp, void *pkt) return; } - lscsi_status = scsi_status & STATUS_MASK; + lscsi_status = scsi_status & STATUS_MASK; fcport = sp->fcport; @@ -2939,7 +2935,7 @@ qla2x00_request_irqs(struct qla_hw_data *ha, struct rsp_que *rsp) /* If possible, enable MSI-X. */ if (!IS_QLA2432(ha) && !IS_QLA2532(ha) && !IS_QLA8432(ha) && - !IS_CNA_CAPABLE(ha) && !IS_QLA2031(ha)) + !IS_CNA_CAPABLE(ha) && !IS_QLA2031(ha) && !IS_QLAFX00(ha)) goto skip_msi; if (ha->pdev->subsystem_vendor == PCI_VENDOR_ID_HP && @@ -2972,7 +2968,7 @@ qla2x00_request_irqs(struct qla_hw_data *ha, struct rsp_que *rsp) skip_msix: if (!IS_QLA24XX(ha) && !IS_QLA2532(ha) && !IS_QLA8432(ha) && - !IS_QLA8001(ha) && !IS_QLA82XX(ha)) + !IS_QLA8001(ha) && !IS_QLA82XX(ha) && !IS_QLAFX00(ha)) goto skip_msi; ret = pci_enable_msi(ha->pdev); @@ -2998,9 +2994,11 @@ skip_msi: "Failed to reserve interrupt %d already in use.\n", ha->pdev->irq); goto fail; - } else if (!ha->flags.msi_enabled) + } else if (!ha->flags.msi_enabled) { ql_dbg(ql_dbg_init, vha, 0x0125, "INTa mode: Enabled.\n"); + ha->flags.mr_intr_valid = 1; + } clear_risc_ints: diff --git a/drivers/scsi/qla2xxx/qla_mbx.c b/drivers/scsi/qla2xxx/qla_mbx.c index 43345af5643..9e5d89db727 100644 --- a/drivers/scsi/qla2xxx/qla_mbx.c +++ b/drivers/scsi/qla2xxx/qla_mbx.c @@ -4113,7 +4113,6 @@ qla2x00_loopback_test(scsi_qla_host_t *vha, struct msg_echo_lb *mreq, int rval; mbx_cmd_t mc; mbx_cmd_t *mcp = &mc; - uint32_t iter_cnt = 0x1; ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x10f7, "Entered %s.\n", __func__); @@ -4139,8 +4138,8 @@ qla2x00_loopback_test(scsi_qla_host_t *vha, struct msg_echo_lb *mreq, mcp->mb[7] = MSW(MSD(mreq->rcv_dma)); /* Iteration count */ - mcp->mb[18] = LSW(iter_cnt); - mcp->mb[19] = MSW(iter_cnt); + mcp->mb[18] = LSW(mreq->iteration_count); + mcp->mb[19] = MSW(mreq->iteration_count); mcp->out_mb = MBX_21|MBX_20|MBX_19|MBX_18|MBX_17|MBX_16|MBX_15| MBX_14|MBX_13|MBX_12|MBX_11|MBX_10|MBX_7|MBX_6|MBX_1|MBX_0; @@ -4518,7 +4517,8 @@ qla2x00_get_thermal_temp(scsi_qla_host_t *vha, uint16_t *temp) goto done; ql_log(ql_log_warn, vha, 0x10c9, - "Thermal not supported by I2C.\n"); + "Thermal not supported through I2C bus, trying alternate " + "method (ISP access).\n"); ha->thermal_support &= ~THERMAL_SUPPORT_I2C; } @@ -4528,7 +4528,7 @@ qla2x00_get_thermal_temp(scsi_qla_host_t *vha, uint16_t *temp) goto done; ql_log(ql_log_warn, vha, 0x1019, - "Thermal not supported by ISP.\n"); + "Thermal not supported through ISP.\n"); ha->thermal_support &= ~THERMAL_SUPPORT_ISP; } diff --git a/drivers/scsi/qla2xxx/qla_mr.c b/drivers/scsi/qla2xxx/qla_mr.c new file mode 100644 index 00000000000..729b74389f8 --- /dev/null +++ b/drivers/scsi/qla2xxx/qla_mr.c @@ -0,0 +1,3476 @@ +/* + * QLogic Fibre Channel HBA Driver + * Copyright (c) 2003-2013 QLogic Corporation + * + * See LICENSE.qla2xxx for copyright and licensing details. + */ +#include "qla_def.h" +#include <linux/delay.h> +#include <linux/pci.h> +#include <linux/ratelimit.h> +#include <linux/vmalloc.h> +#include <scsi/scsi_tcq.h> +#include <linux/utsname.h> + + +/* QLAFX00 specific Mailbox implementation functions */ + +/* + * qlafx00_mailbox_command + * Issue mailbox command and waits for completion. + * + * Input: + * ha = adapter block pointer. + * mcp = driver internal mbx struct pointer. + * + * Output: + * mb[MAX_MAILBOX_REGISTER_COUNT] = returned mailbox data. + * + * Returns: + * 0 : QLA_SUCCESS = cmd performed success + * 1 : QLA_FUNCTION_FAILED (error encountered) + * 6 : QLA_FUNCTION_TIMEOUT (timeout condition encountered) + * + * Context: + * Kernel context. + */ +static int +qlafx00_mailbox_command(scsi_qla_host_t *vha, struct mbx_cmd_32 *mcp) + +{ + int rval; + unsigned long flags = 0; + device_reg_t __iomem *reg; + uint8_t abort_active; + uint8_t io_lock_on; + uint16_t command = 0; + uint32_t *iptr; + uint32_t __iomem *optr; + uint32_t cnt; + uint32_t mboxes; + unsigned long wait_time; + struct qla_hw_data *ha = vha->hw; + scsi_qla_host_t *base_vha = pci_get_drvdata(ha->pdev); + + if (ha->pdev->error_state > pci_channel_io_frozen) { + ql_log(ql_log_warn, vha, 0x115c, + "error_state is greater than pci_channel_io_frozen, " + "exiting.\n"); + return QLA_FUNCTION_TIMEOUT; + } + + if (vha->device_flags & DFLG_DEV_FAILED) { + ql_log(ql_log_warn, vha, 0x115f, + "Device in failed state, exiting.\n"); + return QLA_FUNCTION_TIMEOUT; + } + + reg = ha->iobase; + io_lock_on = base_vha->flags.init_done; + + rval = QLA_SUCCESS; + abort_active = test_bit(ABORT_ISP_ACTIVE, &base_vha->dpc_flags); + + if (ha->flags.pci_channel_io_perm_failure) { + ql_log(ql_log_warn, vha, 0x1175, + "Perm failure on EEH timeout MBX, exiting.\n"); + return QLA_FUNCTION_TIMEOUT; + } + + if (ha->flags.isp82xx_fw_hung) { + /* Setting Link-Down error */ + mcp->mb[0] = MBS_LINK_DOWN_ERROR; + ql_log(ql_log_warn, vha, 0x1176, + "FW hung = %d.\n", ha->flags.isp82xx_fw_hung); + rval = QLA_FUNCTION_FAILED; + goto premature_exit; + } + + /* + * Wait for active mailbox commands to finish by waiting at most tov + * seconds. This is to serialize actual issuing of mailbox cmds during + * non ISP abort time. + */ + if (!wait_for_completion_timeout(&ha->mbx_cmd_comp, mcp->tov * HZ)) { + /* Timeout occurred. Return error. */ + ql_log(ql_log_warn, vha, 0x1177, + "Cmd access timeout, cmd=0x%x, Exiting.\n", + mcp->mb[0]); + return QLA_FUNCTION_TIMEOUT; + } + + ha->flags.mbox_busy = 1; + /* Save mailbox command for debug */ + ha->mcp32 = mcp; + + ql_dbg(ql_dbg_mbx, vha, 0x1178, + "Prepare to issue mbox cmd=0x%x.\n", mcp->mb[0]); + + spin_lock_irqsave(&ha->hardware_lock, flags); + + /* Load mailbox registers. */ + optr = (uint32_t __iomem *)®->ispfx00.mailbox0; + + iptr = mcp->mb; + command = mcp->mb[0]; + mboxes = mcp->out_mb; + + for (cnt = 0; cnt < ha->mbx_count; cnt++) { + if (mboxes & BIT_0) + WRT_REG_DWORD(optr, *iptr); + + mboxes >>= 1; + optr++; + iptr++; + } + + /* Issue set host interrupt command to send cmd out. */ + ha->flags.mbox_int = 0; + clear_bit(MBX_INTERRUPT, &ha->mbx_cmd_flags); + + ql_dump_buffer(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1172, + (uint8_t *)mcp->mb, 16); + ql_dump_buffer(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1173, + ((uint8_t *)mcp->mb + 0x10), 16); + ql_dump_buffer(ql_dbg_mbx + ql_dbg_buffer, vha, 0x1174, + ((uint8_t *)mcp->mb + 0x20), 8); + + /* Unlock mbx registers and wait for interrupt */ + ql_dbg(ql_dbg_mbx, vha, 0x1179, + "Going to unlock irq & waiting for interrupts. " + "jiffies=%lx.\n", jiffies); + + /* Wait for mbx cmd completion until timeout */ + if ((!abort_active && io_lock_on) || IS_NOPOLLING_TYPE(ha)) { + set_bit(MBX_INTR_WAIT, &ha->mbx_cmd_flags); + + QLAFX00_SET_HST_INTR(ha, ha->mbx_intr_code); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + wait_for_completion_timeout(&ha->mbx_intr_comp, mcp->tov * HZ); + + clear_bit(MBX_INTR_WAIT, &ha->mbx_cmd_flags); + + } else { + ql_dbg(ql_dbg_mbx, vha, 0x112c, + "Cmd=%x Polling Mode.\n", command); + + QLAFX00_SET_HST_INTR(ha, ha->mbx_intr_code); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + wait_time = jiffies + mcp->tov * HZ; /* wait at most tov secs */ + while (!ha->flags.mbox_int) { + if (time_after(jiffies, wait_time)) + break; + + /* Check for pending interrupts. */ + qla2x00_poll(ha->rsp_q_map[0]); + + if (!ha->flags.mbox_int && + !(IS_QLA2200(ha) && + command == MBC_LOAD_RISC_RAM_EXTENDED)) + usleep_range(10000, 11000); + } /* while */ + ql_dbg(ql_dbg_mbx, vha, 0x112d, + "Waited %d sec.\n", + (uint)((jiffies - (wait_time - (mcp->tov * HZ)))/HZ)); + } + + /* Check whether we timed out */ + if (ha->flags.mbox_int) { + uint32_t *iptr2; + + ql_dbg(ql_dbg_mbx, vha, 0x112e, + "Cmd=%x completed.\n", command); + + /* Got interrupt. Clear the flag. */ + ha->flags.mbox_int = 0; + clear_bit(MBX_INTERRUPT, &ha->mbx_cmd_flags); + + if (ha->mailbox_out32[0] != MBS_COMMAND_COMPLETE) + rval = QLA_FUNCTION_FAILED; + + /* Load return mailbox registers. */ + iptr2 = mcp->mb; + iptr = (uint32_t *)&ha->mailbox_out32[0]; + mboxes = mcp->in_mb; + for (cnt = 0; cnt < ha->mbx_count; cnt++) { + if (mboxes & BIT_0) + *iptr2 = *iptr; + + mboxes >>= 1; + iptr2++; + iptr++; + } + } else { + + rval = QLA_FUNCTION_TIMEOUT; + } + + ha->flags.mbox_busy = 0; + + /* Clean up */ + ha->mcp32 = NULL; + + if ((abort_active || !io_lock_on) && !IS_NOPOLLING_TYPE(ha)) { + ql_dbg(ql_dbg_mbx, vha, 0x113a, + "checking for additional resp interrupt.\n"); + + /* polling mode for non isp_abort commands. */ + qla2x00_poll(ha->rsp_q_map[0]); + } + + if (rval == QLA_FUNCTION_TIMEOUT && + mcp->mb[0] != MBC_GEN_SYSTEM_ERROR) { + if (!io_lock_on || (mcp->flags & IOCTL_CMD) || + ha->flags.eeh_busy) { + /* not in dpc. schedule it for dpc to take over. */ + ql_dbg(ql_dbg_mbx, vha, 0x115d, + "Timeout, schedule isp_abort_needed.\n"); + + if (!test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags) && + !test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags) && + !test_bit(ISP_ABORT_RETRY, &vha->dpc_flags)) { + + ql_log(ql_log_info, base_vha, 0x115e, + "Mailbox cmd timeout occurred, cmd=0x%x, " + "mb[0]=0x%x, eeh_busy=0x%x. Scheduling ISP " + "abort.\n", command, mcp->mb[0], + ha->flags.eeh_busy); + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + } + } else if (!abort_active) { + /* call abort directly since we are in the DPC thread */ + ql_dbg(ql_dbg_mbx, vha, 0x1160, + "Timeout, calling abort_isp.\n"); + + if (!test_bit(ISP_ABORT_NEEDED, &vha->dpc_flags) && + !test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags) && + !test_bit(ISP_ABORT_RETRY, &vha->dpc_flags)) { + + ql_log(ql_log_info, base_vha, 0x1161, + "Mailbox cmd timeout occurred, cmd=0x%x, " + "mb[0]=0x%x. Scheduling ISP abort ", + command, mcp->mb[0]); + + set_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags); + clear_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + if (ha->isp_ops->abort_isp(vha)) { + /* Failed. retry later. */ + set_bit(ISP_ABORT_NEEDED, + &vha->dpc_flags); + } + clear_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags); + ql_dbg(ql_dbg_mbx, vha, 0x1162, + "Finished abort_isp.\n"); + } + } + } + +premature_exit: + /* Allow next mbx cmd to come in. */ + complete(&ha->mbx_cmd_comp); + + if (rval) { + ql_log(ql_log_warn, base_vha, 0x1163, + "**** Failed mbx[0]=%x, mb[1]=%x, mb[2]=%x, " + "mb[3]=%x, cmd=%x ****.\n", + mcp->mb[0], mcp->mb[1], mcp->mb[2], mcp->mb[3], command); + } else { + ql_dbg(ql_dbg_mbx, base_vha, 0x1164, "Done %s.\n", __func__); + } + + return rval; +} + +/* + * qlafx00_driver_shutdown + * Indicate a driver shutdown to firmware. + * + * Input: + * ha = adapter block pointer. + * + * Returns: + * local function return status code. + * + * Context: + * Kernel context. + */ +static int +qlafx00_driver_shutdown(scsi_qla_host_t *vha, int tmo) +{ + int rval; + struct mbx_cmd_32 mc; + struct mbx_cmd_32 *mcp = &mc; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1166, + "Entered %s.\n", __func__); + + mcp->mb[0] = MBC_MR_DRV_SHUTDOWN; + mcp->out_mb = MBX_0; + mcp->in_mb = MBX_0; + if (tmo) + mcp->tov = tmo; + else + mcp->tov = MBX_TOV_SECONDS; + mcp->flags = 0; + rval = qlafx00_mailbox_command(vha, mcp); + + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x1167, + "Failed=%x.\n", rval); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1168, + "Done %s.\n", __func__); + } + + return rval; +} + +/* + * qlafx00_get_firmware_state + * Get adapter firmware state. + * + * Input: + * ha = adapter block pointer. + * TARGET_QUEUE_LOCK must be released. + * ADAPTER_STATE_LOCK must be released. + * + * Returns: + * qla7xxx local function return status code. + * + * Context: + * Kernel context. + */ +static int +qlafx00_get_firmware_state(scsi_qla_host_t *vha, uint32_t *states) +{ + int rval; + struct mbx_cmd_32 mc; + struct mbx_cmd_32 *mcp = &mc; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1169, + "Entered %s.\n", __func__); + + mcp->mb[0] = MBC_GET_FIRMWARE_STATE; + mcp->out_mb = MBX_0; + mcp->in_mb = MBX_1|MBX_0; + mcp->tov = MBX_TOV_SECONDS; + mcp->flags = 0; + rval = qlafx00_mailbox_command(vha, mcp); + + /* Return firmware states. */ + states[0] = mcp->mb[1]; + + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x116a, + "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x116b, + "Done %s.\n", __func__); + } + return rval; +} + +/* + * qlafx00_init_firmware + * Initialize adapter firmware. + * + * Input: + * ha = adapter block pointer. + * dptr = Initialization control block pointer. + * size = size of initialization control block. + * TARGET_QUEUE_LOCK must be released. + * ADAPTER_STATE_LOCK must be released. + * + * Returns: + * qlafx00 local function return status code. + * + * Context: + * Kernel context. + */ +int +qlafx00_init_firmware(scsi_qla_host_t *vha, uint16_t size) +{ + int rval; + struct mbx_cmd_32 mc; + struct mbx_cmd_32 *mcp = &mc; + struct qla_hw_data *ha = vha->hw; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x116c, + "Entered %s.\n", __func__); + + mcp->mb[0] = MBC_INITIALIZE_FIRMWARE; + + mcp->mb[1] = 0; + mcp->mb[2] = MSD(ha->init_cb_dma); + mcp->mb[3] = LSD(ha->init_cb_dma); + + mcp->out_mb = MBX_3|MBX_2|MBX_1|MBX_0; + mcp->in_mb = MBX_0; + mcp->buf_size = size; + mcp->flags = MBX_DMA_OUT; + mcp->tov = MBX_TOV_SECONDS; + rval = qlafx00_mailbox_command(vha, mcp); + + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x116d, + "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x116e, + "Done %s.\n", __func__); + } + return rval; +} + +/* + * qlafx00_mbx_reg_test + */ +static int +qlafx00_mbx_reg_test(scsi_qla_host_t *vha) +{ + int rval; + struct mbx_cmd_32 mc; + struct mbx_cmd_32 *mcp = &mc; + + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x116f, + "Entered %s.\n", __func__); + + + mcp->mb[0] = MBC_MAILBOX_REGISTER_TEST; + mcp->mb[1] = 0xAAAA; + mcp->mb[2] = 0x5555; + mcp->mb[3] = 0xAA55; + mcp->mb[4] = 0x55AA; + mcp->mb[5] = 0xA5A5; + mcp->mb[6] = 0x5A5A; + mcp->mb[7] = 0x2525; + mcp->mb[8] = 0xBBBB; + mcp->mb[9] = 0x6666; + mcp->mb[10] = 0xBB66; + mcp->mb[11] = 0x66BB; + mcp->mb[12] = 0xB6B6; + mcp->mb[13] = 0x6B6B; + mcp->mb[14] = 0x3636; + mcp->mb[15] = 0xCCCC; + + + mcp->out_mb = MBX_15|MBX_14|MBX_13|MBX_12|MBX_11|MBX_10|MBX_9|MBX_8| + MBX_7|MBX_6|MBX_5|MBX_4|MBX_3|MBX_2|MBX_1|MBX_0; + mcp->in_mb = MBX_15|MBX_14|MBX_13|MBX_12|MBX_11|MBX_10|MBX_9|MBX_8| + MBX_7|MBX_6|MBX_5|MBX_4|MBX_3|MBX_2|MBX_1|MBX_0; + mcp->buf_size = 0; + mcp->flags = MBX_DMA_OUT; + mcp->tov = MBX_TOV_SECONDS; + rval = qlafx00_mailbox_command(vha, mcp); + if (rval == QLA_SUCCESS) { + if (mcp->mb[17] != 0xAAAA || mcp->mb[18] != 0x5555 || + mcp->mb[19] != 0xAA55 || mcp->mb[20] != 0x55AA) + rval = QLA_FUNCTION_FAILED; + if (mcp->mb[21] != 0xA5A5 || mcp->mb[22] != 0x5A5A || + mcp->mb[23] != 0x2525 || mcp->mb[24] != 0xBBBB) + rval = QLA_FUNCTION_FAILED; + if (mcp->mb[25] != 0x6666 || mcp->mb[26] != 0xBB66 || + mcp->mb[27] != 0x66BB || mcp->mb[28] != 0xB6B6) + rval = QLA_FUNCTION_FAILED; + if (mcp->mb[29] != 0x6B6B || mcp->mb[30] != 0x3636 || + mcp->mb[31] != 0xCCCC) + rval = QLA_FUNCTION_FAILED; + } + + if (rval != QLA_SUCCESS) { + ql_dbg(ql_dbg_mbx, vha, 0x1170, + "Failed=%x mb[0]=%x.\n", rval, mcp->mb[0]); + } else { + ql_dbg(ql_dbg_mbx + ql_dbg_verbose, vha, 0x1171, + "Done %s.\n", __func__); + } + return rval; +} + +/** + * qlafx00_pci_config() - Setup ISPFx00 PCI configuration registers. + * @ha: HA context + * + * Returns 0 on success. + */ +int +qlafx00_pci_config(scsi_qla_host_t *vha) +{ + uint16_t w; + struct qla_hw_data *ha = vha->hw; + + pci_set_master(ha->pdev); + pci_try_set_mwi(ha->pdev); + + pci_read_config_word(ha->pdev, PCI_COMMAND, &w); + w |= (PCI_COMMAND_PARITY | PCI_COMMAND_SERR); + w &= ~PCI_COMMAND_INTX_DISABLE; + pci_write_config_word(ha->pdev, PCI_COMMAND, w); + + /* PCIe -- adjust Maximum Read Request Size (2048). */ + if (pci_find_capability(ha->pdev, PCI_CAP_ID_EXP)) + pcie_set_readrq(ha->pdev, 2048); + + ha->chip_revision = ha->pdev->revision; + + return QLA_SUCCESS; +} + +/** + * qlafx00_warm_reset() - Perform warm reset of iSA(CPUs being reset on SOC). + * @ha: HA context + * + */ +static inline void +qlafx00_soc_cpu_reset(scsi_qla_host_t *vha) +{ + unsigned long flags = 0; + struct qla_hw_data *ha = vha->hw; + int i, core; + uint32_t cnt; + + /* Set all 4 cores in reset */ + for (i = 0; i < 4; i++) { + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_SW_RST_CONTROL_REG_CORE0 + 8*i), (0xF01)); + } + + /* Set all 4 core Clock gating control */ + for (i = 0; i < 4; i++) { + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_SW_RST_CONTROL_REG_CORE0 + 4 + 8*i), (0x01010101)); + } + + /* Reset all units in Fabric */ + QLAFX00_SET_HBA_SOC_REG(ha, SOC_FABRIC_RST_CONTROL_REG, (0x11F0101)); + + /* Reset all interrupt control registers */ + for (i = 0; i < 115; i++) { + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_INTERRUPT_SOURCE_I_CONTROL_REG + 4*i), (0x0)); + } + + /* Reset Timers control registers. per core */ + for (core = 0; core < 4; core++) + for (i = 0; i < 8; i++) + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_CORE_TIMER_REG + 0x100*core + 4*i), (0x0)); + + /* Reset per core IRQ ack register */ + for (core = 0; core < 4; core++) + QLAFX00_SET_HBA_SOC_REG(ha, + (SOC_IRQ_ACK_REG + 0x100*core), (0x3FF)); + + /* Set Fabric control and config to defaults */ + QLAFX00_SET_HBA_SOC_REG(ha, SOC_FABRIC_CONTROL_REG, (0x2)); + QLAFX00_SET_HBA_SOC_REG(ha, SOC_FABRIC_CONFIG_REG, (0x3)); + + spin_lock_irqsave(&ha->hardware_lock, flags); + + /* Kick in Fabric units */ + QLAFX00_SET_HBA_SOC_REG(ha, SOC_FABRIC_RST_CONTROL_REG, (0x0)); + + /* Kick in Core0 to start boot process */ + QLAFX00_SET_HBA_SOC_REG(ha, SOC_SW_RST_CONTROL_REG_CORE0, (0xF00)); + + /* Wait 10secs for soft-reset to complete. */ + for (cnt = 10; cnt; cnt--) { + msleep(1000); + barrier(); + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +/** + * qlafx00_soft_reset() - Soft Reset ISPFx00. + * @ha: HA context + * + * Returns 0 on success. + */ +void +qlafx00_soft_reset(scsi_qla_host_t *vha) +{ + struct qla_hw_data *ha = vha->hw; + + if (unlikely(pci_channel_offline(ha->pdev) && + ha->flags.pci_channel_io_perm_failure)) + return; + + ha->isp_ops->disable_intrs(ha); + qlafx00_soc_cpu_reset(vha); + ha->isp_ops->enable_intrs(ha); +} + +/** + * qlafx00_chip_diag() - Test ISPFx00 for proper operation. + * @ha: HA context + * + * Returns 0 on success. + */ +int +qlafx00_chip_diag(scsi_qla_host_t *vha) +{ + int rval = 0; + struct qla_hw_data *ha = vha->hw; + struct req_que *req = ha->req_q_map[0]; + + ha->fw_transfer_size = REQUEST_ENTRY_SIZE * req->length; + + rval = qlafx00_mbx_reg_test(vha); + if (rval) { + ql_log(ql_log_warn, vha, 0x1165, + "Failed mailbox send register test\n"); + } else { + /* Flag a successful rval */ + rval = QLA_SUCCESS; + } + return rval; +} + +void +qlafx00_config_rings(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct device_reg_fx00 __iomem *reg = &ha->iobase->ispfx00; + struct init_cb_fx *icb; + struct req_que *req = ha->req_q_map[0]; + struct rsp_que *rsp = ha->rsp_q_map[0]; + + /* Setup ring parameters in initialization control block. */ + icb = (struct init_cb_fx *)ha->init_cb; + icb->request_q_outpointer = __constant_cpu_to_le16(0); + icb->response_q_inpointer = __constant_cpu_to_le16(0); + icb->request_q_length = cpu_to_le16(req->length); + icb->response_q_length = cpu_to_le16(rsp->length); + icb->request_q_address[0] = cpu_to_le32(LSD(req->dma)); + icb->request_q_address[1] = cpu_to_le32(MSD(req->dma)); + icb->response_q_address[0] = cpu_to_le32(LSD(rsp->dma)); + icb->response_q_address[1] = cpu_to_le32(MSD(rsp->dma)); + + WRT_REG_DWORD(®->req_q_in, 0); + WRT_REG_DWORD(®->req_q_out, 0); + + WRT_REG_DWORD(®->rsp_q_in, 0); + WRT_REG_DWORD(®->rsp_q_out, 0); + + /* PCI posting */ + RD_REG_DWORD(®->rsp_q_out); +} + +char * +qlafx00_pci_info_str(struct scsi_qla_host *vha, char *str) +{ + struct qla_hw_data *ha = vha->hw; + int pcie_reg; + + pcie_reg = pci_find_capability(ha->pdev, PCI_CAP_ID_EXP); + if (pcie_reg) { + strcpy(str, "PCIe iSA"); + return str; + } + return str; +} + +char * +qlafx00_fw_version_str(struct scsi_qla_host *vha, char *str) +{ + struct qla_hw_data *ha = vha->hw; + + sprintf(str, "%s", ha->mr.fw_version); + return str; +} + +void +qlafx00_enable_intrs(struct qla_hw_data *ha) +{ + unsigned long flags = 0; + + spin_lock_irqsave(&ha->hardware_lock, flags); + ha->interrupts_on = 1; + QLAFX00_ENABLE_ICNTRL_REG(ha); + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +void +qlafx00_disable_intrs(struct qla_hw_data *ha) +{ + unsigned long flags = 0; + + spin_lock_irqsave(&ha->hardware_lock, flags); + ha->interrupts_on = 0; + QLAFX00_DISABLE_ICNTRL_REG(ha); + spin_unlock_irqrestore(&ha->hardware_lock, flags); +} + +static void +qlafx00_tmf_iocb_timeout(void *data) +{ + srb_t *sp = (srb_t *)data; + struct srb_iocb *tmf = &sp->u.iocb_cmd; + + tmf->u.tmf.comp_status = CS_TIMEOUT; + complete(&tmf->u.tmf.comp); +} + +static void +qlafx00_tmf_sp_done(void *data, void *ptr, int res) +{ + srb_t *sp = (srb_t *)ptr; + struct srb_iocb *tmf = &sp->u.iocb_cmd; + + complete(&tmf->u.tmf.comp); +} + +static int +qlafx00_async_tm_cmd(fc_port_t *fcport, uint32_t flags, + uint32_t lun, uint32_t tag) +{ + scsi_qla_host_t *vha = fcport->vha; + struct srb_iocb *tm_iocb; + srb_t *sp; + int rval = QLA_FUNCTION_FAILED; + + sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); + if (!sp) + goto done; + + tm_iocb = &sp->u.iocb_cmd; + sp->type = SRB_TM_CMD; + sp->name = "tmf"; + qla2x00_init_timer(sp, qla2x00_get_async_timeout(vha)); + tm_iocb->u.tmf.flags = flags; + tm_iocb->u.tmf.lun = lun; + tm_iocb->u.tmf.data = tag; + sp->done = qlafx00_tmf_sp_done; + tm_iocb->timeout = qlafx00_tmf_iocb_timeout; + init_completion(&tm_iocb->u.tmf.comp); + + rval = qla2x00_start_sp(sp); + if (rval != QLA_SUCCESS) + goto done_free_sp; + + ql_dbg(ql_dbg_async, vha, 0x507b, + "Task management command issued target_id=%x\n", + fcport->tgt_id); + + wait_for_completion(&tm_iocb->u.tmf.comp); + + rval = tm_iocb->u.tmf.comp_status == CS_COMPLETE ? + QLA_SUCCESS : QLA_FUNCTION_FAILED; + +done_free_sp: + sp->free(vha, sp); +done: + return rval; +} + +int +qlafx00_abort_target(fc_port_t *fcport, unsigned int l, int tag) +{ + return qlafx00_async_tm_cmd(fcport, TCF_TARGET_RESET, l, tag); +} + +int +qlafx00_lun_reset(fc_port_t *fcport, unsigned int l, int tag) +{ + return qlafx00_async_tm_cmd(fcport, TCF_LUN_RESET, l, tag); +} + +int +qlafx00_iospace_config(struct qla_hw_data *ha) +{ + if (pci_request_selected_regions(ha->pdev, ha->bars, + QLA2XXX_DRIVER_NAME)) { + ql_log_pci(ql_log_fatal, ha->pdev, 0x014e, + "Failed to reserve PIO/MMIO regions (%s), aborting.\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + + /* Use MMIO operations for all accesses. */ + if (!(pci_resource_flags(ha->pdev, 0) & IORESOURCE_MEM)) { + ql_log_pci(ql_log_warn, ha->pdev, 0x014f, + "Invalid pci I/O region size (%s).\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + if (pci_resource_len(ha->pdev, 0) < BAR0_LEN_FX00) { + ql_log_pci(ql_log_warn, ha->pdev, 0x0127, + "Invalid PCI mem BAR0 region size (%s), aborting\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + + ha->cregbase = + ioremap_nocache(pci_resource_start(ha->pdev, 0), BAR0_LEN_FX00); + if (!ha->cregbase) { + ql_log_pci(ql_log_fatal, ha->pdev, 0x0128, + "cannot remap MMIO (%s), aborting\n", pci_name(ha->pdev)); + goto iospace_error_exit; + } + + if (!(pci_resource_flags(ha->pdev, 2) & IORESOURCE_MEM)) { + ql_log_pci(ql_log_warn, ha->pdev, 0x0129, + "region #2 not an MMIO resource (%s), aborting\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + if (pci_resource_len(ha->pdev, 2) < BAR2_LEN_FX00) { + ql_log_pci(ql_log_warn, ha->pdev, 0x012a, + "Invalid PCI mem BAR2 region size (%s), aborting\n", + pci_name(ha->pdev)); + goto iospace_error_exit; + } + + ha->iobase = + ioremap_nocache(pci_resource_start(ha->pdev, 2), BAR2_LEN_FX00); + if (!ha->iobase) { + ql_log_pci(ql_log_fatal, ha->pdev, 0x012b, + "cannot remap MMIO (%s), aborting\n", pci_name(ha->pdev)); + goto iospace_error_exit; + } + + /* Determine queue resources */ + ha->max_req_queues = ha->max_rsp_queues = 1; + + ql_log_pci(ql_log_info, ha->pdev, 0x012c, + "Bars 0x%x, iobase0 0x%p, iobase2 0x%p\n", + ha->bars, ha->cregbase, ha->iobase); + + return 0; + +iospace_error_exit: + return -ENOMEM; +} + +static void +qlafx00_save_queue_ptrs(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct req_que *req = ha->req_q_map[0]; + struct rsp_que *rsp = ha->rsp_q_map[0]; + + req->length_fx00 = req->length; + req->ring_fx00 = req->ring; + req->dma_fx00 = req->dma; + + rsp->length_fx00 = rsp->length; + rsp->ring_fx00 = rsp->ring; + rsp->dma_fx00 = rsp->dma; + + ql_dbg(ql_dbg_init, vha, 0x012d, + "req: %p, ring_fx00: %p, length_fx00: 0x%x," + "req->dma_fx00: 0x%llx\n", req, req->ring_fx00, + req->length_fx00, (u64)req->dma_fx00); + + ql_dbg(ql_dbg_init, vha, 0x012e, + "rsp: %p, ring_fx00: %p, length_fx00: 0x%x," + "rsp->dma_fx00: 0x%llx\n", rsp, rsp->ring_fx00, + rsp->length_fx00, (u64)rsp->dma_fx00); +} + +static int +qlafx00_config_queues(struct scsi_qla_host *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct req_que *req = ha->req_q_map[0]; + struct rsp_que *rsp = ha->rsp_q_map[0]; + dma_addr_t bar2_hdl = pci_resource_start(ha->pdev, 2); + + req->length = ha->req_que_len; + req->ring = (void *)ha->iobase + ha->req_que_off; + req->dma = bar2_hdl + ha->req_que_off; + if ((!req->ring) || (req->length == 0)) { + ql_log_pci(ql_log_info, ha->pdev, 0x012f, + "Unable to allocate memory for req_ring\n"); + return QLA_FUNCTION_FAILED; + } + + ql_dbg(ql_dbg_init, vha, 0x0130, + "req: %p req_ring pointer %p req len 0x%x " + "req off 0x%x\n, req->dma: 0x%llx", + req, req->ring, req->length, + ha->req_que_off, (u64)req->dma); + + rsp->length = ha->rsp_que_len; + rsp->ring = (void *)ha->iobase + ha->rsp_que_off; + rsp->dma = bar2_hdl + ha->rsp_que_off; + if ((!rsp->ring) || (rsp->length == 0)) { + ql_log_pci(ql_log_info, ha->pdev, 0x0131, + "Unable to allocate memory for rsp_ring\n"); + return QLA_FUNCTION_FAILED; + } + + ql_dbg(ql_dbg_init, vha, 0x0132, + "rsp: %p rsp_ring pointer %p rsp len 0x%x " + "rsp off 0x%x, rsp->dma: 0x%llx\n", + rsp, rsp->ring, rsp->length, + ha->rsp_que_off, (u64)rsp->dma); + + return QLA_SUCCESS; +} + +static int +qlafx00_init_fw_ready(scsi_qla_host_t *vha) +{ + int rval = 0; + unsigned long wtime; + uint16_t wait_time; /* Wait time */ + struct qla_hw_data *ha = vha->hw; + struct device_reg_fx00 __iomem *reg = &ha->iobase->ispfx00; + uint32_t aenmbx, aenmbx7 = 0; + uint32_t state[5]; + bool done = false; + + /* 30 seconds wait - Adjust if required */ + wait_time = 30; + + /* wait time before firmware ready */ + wtime = jiffies + (wait_time * HZ); + do { + aenmbx = RD_REG_DWORD(®->aenmailbox0); + barrier(); + ql_dbg(ql_dbg_mbx, vha, 0x0133, + "aenmbx: 0x%x\n", aenmbx); + + switch (aenmbx) { + case MBA_FW_NOT_STARTED: + case MBA_FW_STARTING: + break; + + case MBA_SYSTEM_ERR: + case MBA_REQ_TRANSFER_ERR: + case MBA_RSP_TRANSFER_ERR: + case MBA_FW_INIT_FAILURE: + qlafx00_soft_reset(vha); + break; + + case MBA_FW_RESTART_CMPLT: + /* Set the mbx and rqstq intr code */ + aenmbx7 = RD_REG_DWORD(®->aenmailbox7); + ha->mbx_intr_code = MSW(aenmbx7); + ha->rqstq_intr_code = LSW(aenmbx7); + ha->req_que_off = RD_REG_DWORD(®->aenmailbox1); + ha->rsp_que_off = RD_REG_DWORD(®->aenmailbox3); + ha->req_que_len = RD_REG_DWORD(®->aenmailbox5); + ha->rsp_que_len = RD_REG_DWORD(®->aenmailbox6); + WRT_REG_DWORD(®->aenmailbox0, 0); + RD_REG_DWORD_RELAXED(®->aenmailbox0); + ql_dbg(ql_dbg_init, vha, 0x0134, + "f/w returned mbx_intr_code: 0x%x, " + "rqstq_intr_code: 0x%x\n", + ha->mbx_intr_code, ha->rqstq_intr_code); + QLAFX00_CLR_INTR_REG(ha, QLAFX00_HST_INT_STS_BITS); + rval = QLA_SUCCESS; + done = true; + break; + + default: + /* If fw is apparently not ready. In order to continue, + * we might need to issue Mbox cmd, but the problem is + * that the DoorBell vector values that come with the + * 8060 AEN are most likely gone by now (and thus no + * bell would be rung on the fw side when mbox cmd is + * issued). We have to therefore grab the 8060 AEN + * shadow regs (filled in by FW when the last 8060 + * AEN was being posted). + * Do the following to determine what is needed in + * order to get the FW ready: + * 1. reload the 8060 AEN values from the shadow regs + * 2. clear int status to get rid of possible pending + * interrupts + * 3. issue Get FW State Mbox cmd to determine fw state + * Set the mbx and rqstq intr code from Shadow Regs + */ + aenmbx7 = RD_REG_DWORD(®->initval7); + ha->mbx_intr_code = MSW(aenmbx7); + ha->rqstq_intr_code = LSW(aenmbx7); + ha->req_que_off = RD_REG_DWORD(®->initval1); + ha->rsp_que_off = RD_REG_DWORD(®->initval3); + ha->req_que_len = RD_REG_DWORD(®->initval5); + ha->rsp_que_len = RD_REG_DWORD(®->initval6); + ql_dbg(ql_dbg_init, vha, 0x0135, + "f/w returned mbx_intr_code: 0x%x, " + "rqstq_intr_code: 0x%x\n", + ha->mbx_intr_code, ha->rqstq_intr_code); + QLAFX00_CLR_INTR_REG(ha, QLAFX00_HST_INT_STS_BITS); + + /* Get the FW state */ + rval = qlafx00_get_firmware_state(vha, state); + if (rval != QLA_SUCCESS) { + /* Retry if timer has not expired */ + break; + } + + if (state[0] == FSTATE_FX00_CONFIG_WAIT) { + /* Firmware is waiting to be + * initialized by driver + */ + rval = QLA_SUCCESS; + done = true; + break; + } + + /* Issue driver shutdown and wait until f/w recovers. + * Driver should continue to poll until 8060 AEN is + * received indicating firmware recovery. + */ + ql_dbg(ql_dbg_init, vha, 0x0136, + "Sending Driver shutdown fw_state 0x%x\n", + state[0]); + + rval = qlafx00_driver_shutdown(vha, 10); + if (rval != QLA_SUCCESS) { + rval = QLA_FUNCTION_FAILED; + break; + } + msleep(500); + + wtime = jiffies + (wait_time * HZ); + break; + } + + if (!done) { + if (time_after_eq(jiffies, wtime)) { + ql_dbg(ql_dbg_init, vha, 0x0137, + "Init f/w failed: aen[7]: 0x%x\n", + RD_REG_DWORD(®->aenmailbox7)); + rval = QLA_FUNCTION_FAILED; + done = true; + break; + } + /* Delay for a while */ + msleep(500); + } + } while (!done); + + if (rval) + ql_dbg(ql_dbg_init, vha, 0x0138, + "%s **** FAILED ****.\n", __func__); + else + ql_dbg(ql_dbg_init, vha, 0x0139, + "%s **** SUCCESS ****.\n", __func__); + + return rval; +} + +/* + * qlafx00_fw_ready() - Waits for firmware ready. + * @ha: HA context + * + * Returns 0 on success. + */ +int +qlafx00_fw_ready(scsi_qla_host_t *vha) +{ + int rval; + unsigned long wtime; + uint16_t wait_time; /* Wait time if loop is coming ready */ + uint32_t state[5]; + + rval = QLA_SUCCESS; + + wait_time = 10; + + /* wait time before firmware ready */ + wtime = jiffies + (wait_time * HZ); + + /* Wait for ISP to finish init */ + if (!vha->flags.init_done) + ql_dbg(ql_dbg_init, vha, 0x013a, + "Waiting for init to complete...\n"); + + do { + rval = qlafx00_get_firmware_state(vha, state); + + if (rval == QLA_SUCCESS) { + if (state[0] == FSTATE_FX00_INITIALIZED) { + ql_dbg(ql_dbg_init, vha, 0x013b, + "fw_state=%x\n", state[0]); + rval = QLA_SUCCESS; + break; + } + } + rval = QLA_FUNCTION_FAILED; + + if (time_after_eq(jiffies, wtime)) + break; + + /* Delay for a while */ + msleep(500); + + ql_dbg(ql_dbg_init, vha, 0x013c, + "fw_state=%x curr time=%lx.\n", state[0], jiffies); + } while (1); + + + if (rval) + ql_dbg(ql_dbg_init, vha, 0x013d, + "Firmware ready **** FAILED ****.\n"); + else + ql_dbg(ql_dbg_init, vha, 0x013e, + "Firmware ready **** SUCCESS ****.\n"); + + return rval; +} + +static int +qlafx00_find_all_targets(scsi_qla_host_t *vha, + struct list_head *new_fcports) +{ + int rval; + uint16_t tgt_id; + fc_port_t *fcport, *new_fcport; + int found; + struct qla_hw_data *ha = vha->hw; + + rval = QLA_SUCCESS; + + if (!test_bit(LOOP_RESYNC_ACTIVE, &vha->dpc_flags)) + return QLA_FUNCTION_FAILED; + + if ((atomic_read(&vha->loop_down_timer) || + STATE_TRANSITION(vha))) { + atomic_set(&vha->loop_down_timer, 0); + set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); + return QLA_FUNCTION_FAILED; + } + + ql_dbg(ql_dbg_disc + ql_dbg_init, vha, 0x2088, + "Listing Target bit map...\n"); + ql_dump_buffer(ql_dbg_disc + ql_dbg_init, vha, + 0x2089, (uint8_t *)ha->gid_list, 32); + + /* Allocate temporary rmtport for any new rmtports discovered. */ + new_fcport = qla2x00_alloc_fcport(vha, GFP_KERNEL); + if (new_fcport == NULL) + return QLA_MEMORY_ALLOC_FAILED; + + for_each_set_bit(tgt_id, (void *)ha->gid_list, + QLAFX00_TGT_NODE_LIST_SIZE) { + + /* Send get target node info */ + new_fcport->tgt_id = tgt_id; + rval = qlafx00_fx_disc(vha, new_fcport, + FXDISC_GET_TGT_NODE_INFO); + if (rval != QLA_SUCCESS) { + ql_log(ql_log_warn, vha, 0x208a, + "Target info scan failed -- assuming zero-entry " + "result...\n"); + continue; + } + + /* Locate matching device in database. */ + found = 0; + list_for_each_entry(fcport, &vha->vp_fcports, list) { + if (memcmp(new_fcport->port_name, + fcport->port_name, WWN_SIZE)) + continue; + + found++; + + /* + * If tgt_id is same and state FCS_ONLINE, nothing + * changed. + */ + if (fcport->tgt_id == new_fcport->tgt_id && + atomic_read(&fcport->state) == FCS_ONLINE) + break; + + /* + * Tgt ID changed or device was marked to be updated. + */ + ql_dbg(ql_dbg_disc + ql_dbg_init, vha, 0x208b, + "TGT-ID Change(%s): Present tgt id: " + "0x%x state: 0x%x " + "wwnn = %llx wwpn = %llx.\n", + __func__, fcport->tgt_id, + atomic_read(&fcport->state), + (unsigned long long)wwn_to_u64(fcport->node_name), + (unsigned long long)wwn_to_u64(fcport->port_name)); + + ql_log(ql_log_info, vha, 0x208c, + "TGT-ID Announce(%s): Discovered tgt " + "id 0x%x wwnn = %llx " + "wwpn = %llx.\n", __func__, new_fcport->tgt_id, + (unsigned long long) + wwn_to_u64(new_fcport->node_name), + (unsigned long long) + wwn_to_u64(new_fcport->port_name)); + + if (atomic_read(&fcport->state) != FCS_ONLINE) { + fcport->old_tgt_id = fcport->tgt_id; + fcport->tgt_id = new_fcport->tgt_id; + ql_log(ql_log_info, vha, 0x208d, + "TGT-ID: New fcport Added: %p\n", fcport); + qla2x00_update_fcport(vha, fcport); + } else { + ql_log(ql_log_info, vha, 0x208e, + " Existing TGT-ID %x did not get " + " offline event from firmware.\n", + fcport->old_tgt_id); + qla2x00_mark_device_lost(vha, fcport, 0, 0); + set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); + kfree(new_fcport); + return rval; + } + break; + } + + if (found) + continue; + + /* If device was not in our fcports list, then add it. */ + list_add_tail(&new_fcport->list, new_fcports); + + /* Allocate a new replacement fcport. */ + new_fcport = qla2x00_alloc_fcport(vha, GFP_KERNEL); + if (new_fcport == NULL) + return QLA_MEMORY_ALLOC_FAILED; + } + + kfree(new_fcport); + return rval; +} + +/* + * qlafx00_configure_all_targets + * Setup target devices with node ID's. + * + * Input: + * ha = adapter block pointer. + * + * Returns: + * 0 = success. + * BIT_0 = error + */ +static int +qlafx00_configure_all_targets(scsi_qla_host_t *vha) +{ + int rval; + fc_port_t *fcport, *rmptemp; + LIST_HEAD(new_fcports); + + rval = qlafx00_fx_disc(vha, &vha->hw->mr.fcport, + FXDISC_GET_TGT_NODE_LIST); + if (rval != QLA_SUCCESS) { + set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); + return rval; + } + + rval = qlafx00_find_all_targets(vha, &new_fcports); + if (rval != QLA_SUCCESS) { + set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); + return rval; + } + + /* + * Delete all previous devices marked lost. + */ + list_for_each_entry(fcport, &vha->vp_fcports, list) { + if (test_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags)) + break; + + if (atomic_read(&fcport->state) == FCS_DEVICE_LOST) { + if (fcport->port_type != FCT_INITIATOR) + qla2x00_mark_device_lost(vha, fcport, 0, 0); + } + } + + /* + * Add the new devices to our devices list. + */ + list_for_each_entry_safe(fcport, rmptemp, &new_fcports, list) { + if (test_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags)) + break; + + qla2x00_update_fcport(vha, fcport); + list_move_tail(&fcport->list, &vha->vp_fcports); + ql_log(ql_log_info, vha, 0x208f, + "Attach new target id 0x%x wwnn = %llx " + "wwpn = %llx.\n", + fcport->tgt_id, + (unsigned long long)wwn_to_u64(fcport->node_name), + (unsigned long long)wwn_to_u64(fcport->port_name)); + } + + /* Free all new device structures not processed. */ + list_for_each_entry_safe(fcport, rmptemp, &new_fcports, list) { + list_del(&fcport->list); + kfree(fcport); + } + + return rval; +} + +/* + * qlafx00_configure_devices + * Updates Fibre Channel Device Database with what is actually on loop. + * + * Input: + * ha = adapter block pointer. + * + * Returns: + * 0 = success. + * 1 = error. + * 2 = database was full and device was not configured. + */ +int +qlafx00_configure_devices(scsi_qla_host_t *vha) +{ + int rval; + unsigned long flags, save_flags; + rval = QLA_SUCCESS; + + save_flags = flags = vha->dpc_flags; + + ql_dbg(ql_dbg_disc, vha, 0x2090, + "Configure devices -- dpc flags =0x%lx\n", flags); + + rval = qlafx00_configure_all_targets(vha); + + if (rval == QLA_SUCCESS) { + if (test_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags)) { + rval = QLA_FUNCTION_FAILED; + } else { + atomic_set(&vha->loop_state, LOOP_READY); + ql_log(ql_log_info, vha, 0x2091, + "Device Ready\n"); + } + } + + if (rval) { + ql_dbg(ql_dbg_disc, vha, 0x2092, + "%s *** FAILED ***.\n", __func__); + } else { + ql_dbg(ql_dbg_disc, vha, 0x2093, + "%s: exiting normally.\n", __func__); + } + return rval; +} + +static void +qlafx00_abort_isp_cleanup(scsi_qla_host_t *vha) +{ + struct qla_hw_data *ha = vha->hw; + fc_port_t *fcport; + + vha->flags.online = 0; + ha->flags.chip_reset_done = 0; + ha->mr.fw_hbt_en = 0; + clear_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + vha->qla_stats.total_isp_aborts++; + + ql_log(ql_log_info, vha, 0x013f, + "Performing ISP error recovery - ha = %p.\n", ha); + + ha->isp_ops->reset_chip(vha); + + if (atomic_read(&vha->loop_state) != LOOP_DOWN) { + atomic_set(&vha->loop_state, LOOP_DOWN); + atomic_set(&vha->loop_down_timer, + QLAFX00_LOOP_DOWN_TIME); + } else { + if (!atomic_read(&vha->loop_down_timer)) + atomic_set(&vha->loop_down_timer, + QLAFX00_LOOP_DOWN_TIME); + } + + /* Clear all async request states across all VPs. */ + list_for_each_entry(fcport, &vha->vp_fcports, list) { + fcport->flags = 0; + if (atomic_read(&fcport->state) == FCS_ONLINE) + qla2x00_set_fcport_state(fcport, FCS_DEVICE_LOST); + } + + if (!ha->flags.eeh_busy) { + /* Requeue all commands in outstanding command list. */ + qla2x00_abort_all_cmds(vha, DID_RESET << 16); + } + + qla2x00_free_irqs(vha); + set_bit(FX00_RESET_RECOVERY, &vha->dpc_flags); + + /* Clear the Interrupts */ + QLAFX00_CLR_INTR_REG(ha, QLAFX00_HST_INT_STS_BITS); + + ql_log(ql_log_info, vha, 0x0140, + "%s Done done - ha=%p.\n", __func__, ha); +} + +/** + * qlafx00_init_response_q_entries() - Initializes response queue entries. + * @ha: HA context + * + * Beginning of request ring has initialization control block already built + * by nvram config routine. + * + * Returns 0 on success. + */ +void +qlafx00_init_response_q_entries(struct rsp_que *rsp) +{ + uint16_t cnt; + response_t *pkt; + + rsp->ring_ptr = rsp->ring; + rsp->ring_index = 0; + rsp->status_srb = NULL; + pkt = rsp->ring_ptr; + for (cnt = 0; cnt < rsp->length; cnt++) { + pkt->signature = RESPONSE_PROCESSED; + WRT_REG_DWORD(&pkt->signature, RESPONSE_PROCESSED); + pkt++; + } +} + +int +qlafx00_rescan_isp(scsi_qla_host_t *vha) +{ + uint32_t status = QLA_FUNCTION_FAILED; + struct qla_hw_data *ha = vha->hw; + struct device_reg_fx00 __iomem *reg = &ha->iobase->ispfx00; + uint32_t aenmbx7; + + qla2x00_request_irqs(ha, ha->rsp_q_map[0]); + + aenmbx7 = RD_REG_DWORD(®->aenmailbox7); + ha->mbx_intr_code = MSW(aenmbx7); + ha->rqstq_intr_code = LSW(aenmbx7); + ha->req_que_off = RD_REG_DWORD(®->aenmailbox1); + ha->rsp_que_off = RD_REG_DWORD(®->aenmailbox3); + ha->req_que_len = RD_REG_DWORD(®->aenmailbox5); + ha->rsp_que_len = RD_REG_DWORD(®->aenmailbox6); + + ql_dbg(ql_dbg_disc, vha, 0x2094, + "fw returned mbx_intr_code: 0x%x, rqstq_intr_code: 0x%x " + " Req que offset 0x%x Rsp que offset 0x%x\n", + ha->mbx_intr_code, ha->rqstq_intr_code, + ha->req_que_off, ha->rsp_que_len); + + /* Clear the Interrupts */ + QLAFX00_CLR_INTR_REG(ha, QLAFX00_HST_INT_STS_BITS); + + status = qla2x00_init_rings(vha); + if (!status) { + vha->flags.online = 1; + + /* if no cable then assume it's good */ + if ((vha->device_flags & DFLG_NO_CABLE)) + status = 0; + /* Register system information */ + if (qlafx00_fx_disc(vha, + &vha->hw->mr.fcport, FXDISC_REG_HOST_INFO)) + ql_dbg(ql_dbg_disc, vha, 0x2095, + "failed to register host info\n"); + } + scsi_unblock_requests(vha->host); + return status; +} + +void +qlafx00_timer_routine(scsi_qla_host_t *vha) +{ + struct qla_hw_data *ha = vha->hw; + uint32_t fw_heart_beat; + uint32_t aenmbx0; + struct device_reg_fx00 __iomem *reg = &ha->iobase->ispfx00; + + /* Check firmware health */ + if (ha->mr.fw_hbt_cnt) + ha->mr.fw_hbt_cnt--; + else { + if ((!ha->flags.mr_reset_hdlr_active) && + (!test_bit(UNLOADING, &vha->dpc_flags)) && + (!test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags)) && + (ha->mr.fw_hbt_en)) { + fw_heart_beat = RD_REG_DWORD(®->fwheartbeat); + if (fw_heart_beat != ha->mr.old_fw_hbt_cnt) { + ha->mr.old_fw_hbt_cnt = fw_heart_beat; + ha->mr.fw_hbt_miss_cnt = 0; + } else { + ha->mr.fw_hbt_miss_cnt++; + if (ha->mr.fw_hbt_miss_cnt == + QLAFX00_HEARTBEAT_MISS_CNT) { + set_bit(ISP_ABORT_NEEDED, + &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + ha->mr.fw_hbt_miss_cnt = 0; + } + } + } + ha->mr.fw_hbt_cnt = QLAFX00_HEARTBEAT_INTERVAL; + } + + if (test_bit(FX00_RESET_RECOVERY, &vha->dpc_flags)) { + /* Reset recovery to be performed in timer routine */ + aenmbx0 = RD_REG_DWORD(®->aenmailbox0); + if (ha->mr.fw_reset_timer_exp) { + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + ha->mr.fw_reset_timer_exp = 0; + } else if (aenmbx0 == MBA_FW_RESTART_CMPLT) { + /* Wake up DPC to rescan the targets */ + set_bit(FX00_TARGET_SCAN, &vha->dpc_flags); + clear_bit(FX00_RESET_RECOVERY, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + ha->mr.fw_reset_timer_tick = QLAFX00_RESET_INTERVAL; + } else if ((aenmbx0 == MBA_FW_STARTING) && + (!ha->mr.fw_hbt_en)) { + ha->mr.fw_hbt_en = 1; + } else if (!ha->mr.fw_reset_timer_tick) { + if (aenmbx0 == ha->mr.old_aenmbx0_state) + ha->mr.fw_reset_timer_exp = 1; + ha->mr.fw_reset_timer_tick = QLAFX00_RESET_INTERVAL; + } else if (aenmbx0 == 0xFFFFFFFF) { + uint32_t data0, data1; + + data0 = QLAFX00_RD_REG(ha, + QLAFX00_BAR1_BASE_ADDR_REG); + data1 = QLAFX00_RD_REG(ha, + QLAFX00_PEX0_WIN0_BASE_ADDR_REG); + + data0 &= 0xffff0000; + data1 &= 0x0000ffff; + + QLAFX00_WR_REG(ha, + QLAFX00_PEX0_WIN0_BASE_ADDR_REG, + (data0 | data1)); + } else if ((aenmbx0 & 0xFF00) == MBA_FW_POLL_STATE) { + ha->mr.fw_reset_timer_tick = + QLAFX00_MAX_RESET_INTERVAL; + } + ha->mr.old_aenmbx0_state = aenmbx0; + ha->mr.fw_reset_timer_tick--; + } +} + +/* + * qlfx00a_reset_initialize + * Re-initialize after a iSA device reset. + * + * Input: + * ha = adapter block pointer. + * + * Returns: + * 0 = success + */ +int +qlafx00_reset_initialize(scsi_qla_host_t *vha) +{ + struct qla_hw_data *ha = vha->hw; + + if (vha->device_flags & DFLG_DEV_FAILED) { + ql_dbg(ql_dbg_init, vha, 0x0142, + "Device in failed state\n"); + return QLA_SUCCESS; + } + + ha->flags.mr_reset_hdlr_active = 1; + + if (vha->flags.online) { + scsi_block_requests(vha->host); + qlafx00_abort_isp_cleanup(vha); + } + + ql_log(ql_log_info, vha, 0x0143, + "(%s): succeeded.\n", __func__); + ha->flags.mr_reset_hdlr_active = 0; + return QLA_SUCCESS; +} + +/* + * qlafx00_abort_isp + * Resets ISP and aborts all outstanding commands. + * + * Input: + * ha = adapter block pointer. + * + * Returns: + * 0 = success + */ +int +qlafx00_abort_isp(scsi_qla_host_t *vha) +{ + struct qla_hw_data *ha = vha->hw; + + if (vha->flags.online) { + if (unlikely(pci_channel_offline(ha->pdev) && + ha->flags.pci_channel_io_perm_failure)) { + clear_bit(ISP_ABORT_RETRY, &vha->dpc_flags); + return QLA_SUCCESS; + } + + scsi_block_requests(vha->host); + qlafx00_abort_isp_cleanup(vha); + } + + ql_log(ql_log_info, vha, 0x0145, + "(%s): succeeded.\n", __func__); + + return QLA_SUCCESS; +} + +static inline fc_port_t* +qlafx00_get_fcport(struct scsi_qla_host *vha, int tgt_id) +{ + fc_port_t *fcport; + + /* Check for matching device in remote port list. */ + fcport = NULL; + list_for_each_entry(fcport, &vha->vp_fcports, list) { + if (fcport->tgt_id == tgt_id) { + ql_dbg(ql_dbg_async, vha, 0x5072, + "Matching fcport(%p) found with TGT-ID: 0x%x " + "and Remote TGT_ID: 0x%x\n", + fcport, fcport->tgt_id, tgt_id); + break; + } + } + return fcport; +} + +static void +qlafx00_tgt_detach(struct scsi_qla_host *vha, int tgt_id) +{ + fc_port_t *fcport; + + ql_log(ql_log_info, vha, 0x5073, + "Detach TGT-ID: 0x%x\n", tgt_id); + + fcport = qlafx00_get_fcport(vha, tgt_id); + if (!fcport) + return; + + qla2x00_mark_device_lost(vha, fcport, 0, 0); + + return; +} + +int +qlafx00_process_aen(struct scsi_qla_host *vha, struct qla_work_evt *evt) +{ + int rval = 0; + uint32_t aen_code, aen_data; + + aen_code = FCH_EVT_VENDOR_UNIQUE; + aen_data = evt->u.aenfx.evtcode; + + switch (evt->u.aenfx.evtcode) { + case QLAFX00_MBA_PORT_UPDATE: /* Port database update */ + if (evt->u.aenfx.mbx[1] == 0) { + if (evt->u.aenfx.mbx[2] == 1) { + if (!vha->flags.fw_tgt_reported) + vha->flags.fw_tgt_reported = 1; + atomic_set(&vha->loop_down_timer, 0); + atomic_set(&vha->loop_state, LOOP_UP); + set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + } else if (evt->u.aenfx.mbx[2] == 2) { + qlafx00_tgt_detach(vha, evt->u.aenfx.mbx[3]); + } + } else if (evt->u.aenfx.mbx[1] == 0xffff) { + if (evt->u.aenfx.mbx[2] == 1) { + if (!vha->flags.fw_tgt_reported) + vha->flags.fw_tgt_reported = 1; + set_bit(LOOP_RESYNC_NEEDED, &vha->dpc_flags); + } else if (evt->u.aenfx.mbx[2] == 2) { + vha->device_flags |= DFLG_NO_CABLE; + qla2x00_mark_all_devices_lost(vha, 1); + } + } + break; + case QLAFX00_MBA_LINK_UP: + aen_code = FCH_EVT_LINKUP; + aen_data = 0; + break; + case QLAFX00_MBA_LINK_DOWN: + aen_code = FCH_EVT_LINKDOWN; + aen_data = 0; + break; + } + + fc_host_post_event(vha->host, fc_get_event_number(), + aen_code, aen_data); + + return rval; +} + +static void +qlafx00_update_host_attr(scsi_qla_host_t *vha, struct port_info_data *pinfo) +{ + u64 port_name = 0, node_name = 0; + + port_name = (unsigned long long)wwn_to_u64(pinfo->port_name); + node_name = (unsigned long long)wwn_to_u64(pinfo->node_name); + + fc_host_node_name(vha->host) = node_name; + fc_host_port_name(vha->host) = port_name; + if (!pinfo->port_type) + vha->hw->current_topology = ISP_CFG_F; + if (pinfo->link_status == QLAFX00_LINK_STATUS_UP) + atomic_set(&vha->loop_state, LOOP_READY); + else if (pinfo->link_status == QLAFX00_LINK_STATUS_DOWN) + atomic_set(&vha->loop_state, LOOP_DOWN); + vha->hw->link_data_rate = (uint16_t)pinfo->link_config; +} + +static void +qla2x00_fxdisc_iocb_timeout(void *data) +{ + srb_t *sp = (srb_t *)data; + struct srb_iocb *lio = &sp->u.iocb_cmd; + + complete(&lio->u.fxiocb.fxiocb_comp); +} + +static void +qla2x00_fxdisc_sp_done(void *data, void *ptr, int res) +{ + srb_t *sp = (srb_t *)ptr; + struct srb_iocb *lio = &sp->u.iocb_cmd; + + complete(&lio->u.fxiocb.fxiocb_comp); +} + +int +qlafx00_fx_disc(scsi_qla_host_t *vha, fc_port_t *fcport, uint8_t fx_type) +{ + srb_t *sp; + struct srb_iocb *fdisc; + int rval = QLA_FUNCTION_FAILED; + struct qla_hw_data *ha = vha->hw; + struct host_system_info *phost_info; + struct register_host_info *preg_hsi; + struct new_utsname *p_sysid = NULL; + struct timeval tv; + + sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); + if (!sp) + goto done; + + fdisc = &sp->u.iocb_cmd; + switch (fx_type) { + case FXDISC_GET_CONFIG_INFO: + fdisc->u.fxiocb.flags = + SRB_FXDISC_RESP_DMA_VALID; + fdisc->u.fxiocb.rsp_len = sizeof(struct config_info_data); + break; + case FXDISC_GET_PORT_INFO: + fdisc->u.fxiocb.flags = + SRB_FXDISC_RESP_DMA_VALID | SRB_FXDISC_REQ_DWRD_VALID; + fdisc->u.fxiocb.rsp_len = QLAFX00_PORT_DATA_INFO; + fdisc->u.fxiocb.req_data = fcport->port_id; + break; + case FXDISC_GET_TGT_NODE_INFO: + fdisc->u.fxiocb.flags = + SRB_FXDISC_RESP_DMA_VALID | SRB_FXDISC_REQ_DWRD_VALID; + fdisc->u.fxiocb.rsp_len = QLAFX00_TGT_NODE_INFO; + fdisc->u.fxiocb.req_data = fcport->tgt_id; + break; + case FXDISC_GET_TGT_NODE_LIST: + fdisc->u.fxiocb.flags = + SRB_FXDISC_RESP_DMA_VALID | SRB_FXDISC_REQ_DWRD_VALID; + fdisc->u.fxiocb.rsp_len = QLAFX00_TGT_NODE_LIST_SIZE; + break; + case FXDISC_REG_HOST_INFO: + fdisc->u.fxiocb.flags = SRB_FXDISC_REQ_DMA_VALID; + fdisc->u.fxiocb.req_len = sizeof(struct register_host_info); + p_sysid = utsname(); + if (!p_sysid) { + ql_log(ql_log_warn, vha, 0x303c, + "Not able to get the system informtion\n"); + goto done_free_sp; + } + break; + default: + break; + } + + if (fdisc->u.fxiocb.flags & SRB_FXDISC_REQ_DMA_VALID) { + fdisc->u.fxiocb.req_addr = dma_alloc_coherent(&ha->pdev->dev, + fdisc->u.fxiocb.req_len, + &fdisc->u.fxiocb.req_dma_handle, GFP_KERNEL); + if (!fdisc->u.fxiocb.req_addr) + goto done_free_sp; + + if (fx_type == FXDISC_REG_HOST_INFO) { + preg_hsi = (struct register_host_info *) + fdisc->u.fxiocb.req_addr; + phost_info = &preg_hsi->hsi; + memset(preg_hsi, 0, sizeof(struct register_host_info)); + phost_info->os_type = OS_TYPE_LINUX; + strncpy(phost_info->sysname, + p_sysid->sysname, SYSNAME_LENGTH); + strncpy(phost_info->nodename, + p_sysid->nodename, NODENAME_LENGTH); + strncpy(phost_info->release, + p_sysid->release, RELEASE_LENGTH); + strncpy(phost_info->version, + p_sysid->version, VERSION_LENGTH); + strncpy(phost_info->machine, + p_sysid->machine, MACHINE_LENGTH); + strncpy(phost_info->domainname, + p_sysid->domainname, DOMNAME_LENGTH); + strncpy(phost_info->hostdriver, + QLA2XXX_VERSION, VERSION_LENGTH); + do_gettimeofday(&tv); + preg_hsi->utc = (uint64_t)tv.tv_sec; + ql_dbg(ql_dbg_init, vha, 0x0149, + "ISP%04X: Host registration with firmware\n", + ha->pdev->device); + ql_dbg(ql_dbg_init, vha, 0x014a, + "os_type = '%d', sysname = '%s', nodname = '%s'\n", + phost_info->os_type, + phost_info->sysname, + phost_info->nodename); + ql_dbg(ql_dbg_init, vha, 0x014b, + "release = '%s', version = '%s'\n", + phost_info->release, + phost_info->version); + ql_dbg(ql_dbg_init, vha, 0x014c, + "machine = '%s' " + "domainname = '%s', hostdriver = '%s'\n", + phost_info->machine, + phost_info->domainname, + phost_info->hostdriver); + ql_dump_buffer(ql_dbg_init + ql_dbg_disc, vha, 0x014d, + (uint8_t *)phost_info, + sizeof(struct host_system_info)); + } + } + + if (fdisc->u.fxiocb.flags & SRB_FXDISC_RESP_DMA_VALID) { + fdisc->u.fxiocb.rsp_addr = dma_alloc_coherent(&ha->pdev->dev, + fdisc->u.fxiocb.rsp_len, + &fdisc->u.fxiocb.rsp_dma_handle, GFP_KERNEL); + if (!fdisc->u.fxiocb.rsp_addr) + goto done_unmap_req; + } + + sp->type = SRB_FXIOCB_DCMD; + sp->name = "fxdisc"; + qla2x00_init_timer(sp, FXDISC_TIMEOUT); + fdisc->timeout = qla2x00_fxdisc_iocb_timeout; + fdisc->u.fxiocb.req_func_type = fx_type; + sp->done = qla2x00_fxdisc_sp_done; + + rval = qla2x00_start_sp(sp); + if (rval != QLA_SUCCESS) + goto done_unmap_dma; + + wait_for_completion(&fdisc->u.fxiocb.fxiocb_comp); + + if (fx_type == FXDISC_GET_CONFIG_INFO) { + struct config_info_data *pinfo = + (struct config_info_data *) fdisc->u.fxiocb.rsp_addr; + memcpy(&vha->hw->mr.product_name, pinfo->product_name, + sizeof(vha->hw->mr.product_name)); + memcpy(&vha->hw->mr.symbolic_name, pinfo->symbolic_name, + sizeof(vha->hw->mr.symbolic_name)); + memcpy(&vha->hw->mr.serial_num, pinfo->serial_num, + sizeof(vha->hw->mr.serial_num)); + memcpy(&vha->hw->mr.hw_version, pinfo->hw_version, + sizeof(vha->hw->mr.hw_version)); + memcpy(&vha->hw->mr.fw_version, pinfo->fw_version, + sizeof(vha->hw->mr.fw_version)); + strim(vha->hw->mr.fw_version); + memcpy(&vha->hw->mr.uboot_version, pinfo->uboot_version, + sizeof(vha->hw->mr.uboot_version)); + memcpy(&vha->hw->mr.fru_serial_num, pinfo->fru_serial_num, + sizeof(vha->hw->mr.fru_serial_num)); + } else if (fx_type == FXDISC_GET_PORT_INFO) { + struct port_info_data *pinfo = + (struct port_info_data *) fdisc->u.fxiocb.rsp_addr; + memcpy(vha->node_name, pinfo->node_name, WWN_SIZE); + memcpy(vha->port_name, pinfo->port_name, WWN_SIZE); + vha->d_id.b.domain = pinfo->port_id[0]; + vha->d_id.b.area = pinfo->port_id[1]; + vha->d_id.b.al_pa = pinfo->port_id[2]; + qlafx00_update_host_attr(vha, pinfo); + ql_dump_buffer(ql_dbg_init + ql_dbg_buffer, vha, 0x0141, + (uint8_t *)pinfo, 16); + } else if (fx_type == FXDISC_GET_TGT_NODE_INFO) { + struct qlafx00_tgt_node_info *pinfo = + (struct qlafx00_tgt_node_info *) fdisc->u.fxiocb.rsp_addr; + memcpy(fcport->node_name, pinfo->tgt_node_wwnn, WWN_SIZE); + memcpy(fcport->port_name, pinfo->tgt_node_wwpn, WWN_SIZE); + fcport->port_type = FCT_TARGET; + ql_dump_buffer(ql_dbg_init + ql_dbg_buffer, vha, 0x0144, + (uint8_t *)pinfo, 16); + } else if (fx_type == FXDISC_GET_TGT_NODE_LIST) { + struct qlafx00_tgt_node_info *pinfo = + (struct qlafx00_tgt_node_info *) fdisc->u.fxiocb.rsp_addr; + ql_dump_buffer(ql_dbg_init + ql_dbg_buffer, vha, 0x0146, + (uint8_t *)pinfo, 16); + memcpy(vha->hw->gid_list, pinfo, QLAFX00_TGT_NODE_LIST_SIZE); + } + rval = fdisc->u.fxiocb.result; + +done_unmap_dma: + if (fdisc->u.fxiocb.rsp_addr) + dma_free_coherent(&ha->pdev->dev, fdisc->u.fxiocb.rsp_len, + fdisc->u.fxiocb.rsp_addr, fdisc->u.fxiocb.rsp_dma_handle); + +done_unmap_req: + if (fdisc->u.fxiocb.req_addr) + dma_free_coherent(&ha->pdev->dev, fdisc->u.fxiocb.req_len, + fdisc->u.fxiocb.req_addr, fdisc->u.fxiocb.req_dma_handle); +done_free_sp: + sp->free(vha, sp); +done: + return rval; +} + +static void +qlafx00_abort_iocb_timeout(void *data) +{ + srb_t *sp = (srb_t *)data; + struct srb_iocb *abt = &sp->u.iocb_cmd; + + abt->u.abt.comp_status = CS_TIMEOUT; + complete(&abt->u.abt.comp); +} + +static void +qlafx00_abort_sp_done(void *data, void *ptr, int res) +{ + srb_t *sp = (srb_t *)ptr; + struct srb_iocb *abt = &sp->u.iocb_cmd; + + complete(&abt->u.abt.comp); +} + +static int +qlafx00_async_abt_cmd(srb_t *cmd_sp) +{ + scsi_qla_host_t *vha = cmd_sp->fcport->vha; + fc_port_t *fcport = cmd_sp->fcport; + struct srb_iocb *abt_iocb; + srb_t *sp; + int rval = QLA_FUNCTION_FAILED; + + sp = qla2x00_get_sp(vha, fcport, GFP_KERNEL); + if (!sp) + goto done; + + abt_iocb = &sp->u.iocb_cmd; + sp->type = SRB_ABT_CMD; + sp->name = "abort"; + qla2x00_init_timer(sp, FXDISC_TIMEOUT); + abt_iocb->u.abt.cmd_hndl = cmd_sp->handle; + sp->done = qlafx00_abort_sp_done; + abt_iocb->timeout = qlafx00_abort_iocb_timeout; + init_completion(&abt_iocb->u.abt.comp); + + rval = qla2x00_start_sp(sp); + if (rval != QLA_SUCCESS) + goto done_free_sp; + + ql_dbg(ql_dbg_async, vha, 0x507c, + "Abort command issued - hdl=%x, target_id=%x\n", + cmd_sp->handle, fcport->tgt_id); + + wait_for_completion(&abt_iocb->u.abt.comp); + + rval = abt_iocb->u.abt.comp_status == CS_COMPLETE ? + QLA_SUCCESS : QLA_FUNCTION_FAILED; + +done_free_sp: + sp->free(vha, sp); +done: + return rval; +} + +int +qlafx00_abort_command(srb_t *sp) +{ + unsigned long flags = 0; + + uint32_t handle; + fc_port_t *fcport = sp->fcport; + struct scsi_qla_host *vha = fcport->vha; + struct qla_hw_data *ha = vha->hw; + struct req_que *req = vha->req; + + spin_lock_irqsave(&ha->hardware_lock, flags); + for (handle = 1; handle < DEFAULT_OUTSTANDING_COMMANDS; handle++) { + if (req->outstanding_cmds[handle] == sp) + break; + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); + if (handle == DEFAULT_OUTSTANDING_COMMANDS) { + /* Command not found. */ + return QLA_FUNCTION_FAILED; + } + return qlafx00_async_abt_cmd(sp); +} + +/* + * qlafx00_initialize_adapter + * Initialize board. + * + * Input: + * ha = adapter block pointer. + * + * Returns: + * 0 = success + */ +int +qlafx00_initialize_adapter(scsi_qla_host_t *vha) +{ + int rval; + struct qla_hw_data *ha = vha->hw; + + /* Clear adapter flags. */ + vha->flags.online = 0; + ha->flags.chip_reset_done = 0; + vha->flags.reset_active = 0; + ha->flags.pci_channel_io_perm_failure = 0; + ha->flags.eeh_busy = 0; + ha->thermal_support = 0; + atomic_set(&vha->loop_down_timer, LOOP_DOWN_TIME); + atomic_set(&vha->loop_state, LOOP_DOWN); + vha->device_flags = DFLG_NO_CABLE; + vha->dpc_flags = 0; + vha->flags.management_server_logged_in = 0; + vha->marker_needed = 0; + ha->isp_abort_cnt = 0; + ha->beacon_blink_led = 0; + + set_bit(0, ha->req_qid_map); + set_bit(0, ha->rsp_qid_map); + + ql_dbg(ql_dbg_init, vha, 0x0147, + "Configuring PCI space...\n"); + + rval = ha->isp_ops->pci_config(vha); + if (rval) { + ql_log(ql_log_warn, vha, 0x0148, + "Unable to configure PCI space.\n"); + return rval; + } + + rval = qlafx00_init_fw_ready(vha); + if (rval != QLA_SUCCESS) + return rval; + + qlafx00_save_queue_ptrs(vha); + + rval = qlafx00_config_queues(vha); + if (rval != QLA_SUCCESS) + return rval; + + /* + * Allocate the array of outstanding commands + * now that we know the firmware resources. + */ + rval = qla2x00_alloc_outstanding_cmds(ha, vha->req); + if (rval != QLA_SUCCESS) + return rval; + + rval = qla2x00_init_rings(vha); + ha->flags.chip_reset_done = 1; + + return rval; +} + +uint32_t +qlafx00_fw_state_show(struct device *dev, struct device_attribute *attr, + char *buf) +{ + scsi_qla_host_t *vha = shost_priv(class_to_shost(dev)); + int rval = QLA_FUNCTION_FAILED; + uint32_t state[1]; + + if (qla2x00_reset_active(vha)) + ql_log(ql_log_warn, vha, 0x70ce, + "ISP reset active.\n"); + else if (!vha->hw->flags.eeh_busy) { + rval = qlafx00_get_firmware_state(vha, state); + } + if (rval != QLA_SUCCESS) + memset(state, -1, sizeof(state)); + + return state[0]; +} + +void +qlafx00_get_host_speed(struct Scsi_Host *shost) +{ + struct qla_hw_data *ha = ((struct scsi_qla_host *) + (shost_priv(shost)))->hw; + u32 speed = FC_PORTSPEED_UNKNOWN; + + switch (ha->link_data_rate) { + case QLAFX00_PORT_SPEED_2G: + speed = FC_PORTSPEED_2GBIT; + break; + case QLAFX00_PORT_SPEED_4G: + speed = FC_PORTSPEED_4GBIT; + break; + case QLAFX00_PORT_SPEED_8G: + speed = FC_PORTSPEED_8GBIT; + break; + case QLAFX00_PORT_SPEED_10G: + speed = FC_PORTSPEED_10GBIT; + break; + } + fc_host_speed(shost) = speed; +} + +/** QLAFX00 specific ISR implementation functions */ + +static inline void +qlafx00_handle_sense(srb_t *sp, uint8_t *sense_data, uint32_t par_sense_len, + uint32_t sense_len, struct rsp_que *rsp, int res) +{ + struct scsi_qla_host *vha = sp->fcport->vha; + struct scsi_cmnd *cp = GET_CMD_SP(sp); + uint32_t track_sense_len; + + SET_FW_SENSE_LEN(sp, sense_len); + + if (sense_len >= SCSI_SENSE_BUFFERSIZE) + sense_len = SCSI_SENSE_BUFFERSIZE; + + SET_CMD_SENSE_LEN(sp, sense_len); + SET_CMD_SENSE_PTR(sp, cp->sense_buffer); + track_sense_len = sense_len; + + if (sense_len > par_sense_len) + sense_len = par_sense_len; + + memcpy(cp->sense_buffer, sense_data, sense_len); + + SET_FW_SENSE_LEN(sp, GET_FW_SENSE_LEN(sp) - sense_len); + + SET_CMD_SENSE_PTR(sp, cp->sense_buffer + sense_len); + track_sense_len -= sense_len; + SET_CMD_SENSE_LEN(sp, track_sense_len); + + ql_dbg(ql_dbg_io, vha, 0x304d, + "sense_len=0x%x par_sense_len=0x%x track_sense_len=0x%x.\n", + sense_len, par_sense_len, track_sense_len); + if (GET_FW_SENSE_LEN(sp) > 0) { + rsp->status_srb = sp; + cp->result = res; + } + + if (sense_len) { + ql_dbg(ql_dbg_io + ql_dbg_buffer, vha, 0x3039, + "Check condition Sense data, nexus%ld:%d:%d cmd=%p.\n", + sp->fcport->vha->host_no, cp->device->id, cp->device->lun, + cp); + ql_dump_buffer(ql_dbg_io + ql_dbg_buffer, vha, 0x3049, + cp->sense_buffer, sense_len); + } +} + +static void +qlafx00_tm_iocb_entry(scsi_qla_host_t *vha, struct req_que *req, + struct tsk_mgmt_entry_fx00 *pkt, srb_t *sp, + uint16_t sstatus, uint16_t cpstatus) +{ + struct srb_iocb *tmf; + + tmf = &sp->u.iocb_cmd; + if (cpstatus != CS_COMPLETE || + (sstatus & SS_RESPONSE_INFO_LEN_VALID)) + cpstatus = CS_INCOMPLETE; + tmf->u.tmf.comp_status = cpstatus; + sp->done(vha, sp, 0); +} + +static void +qlafx00_abort_iocb_entry(scsi_qla_host_t *vha, struct req_que *req, + struct abort_iocb_entry_fx00 *pkt) +{ + const char func[] = "ABT_IOCB"; + srb_t *sp; + struct srb_iocb *abt; + + sp = qla2x00_get_sp_from_handle(vha, func, req, pkt); + if (!sp) + return; + + abt = &sp->u.iocb_cmd; + abt->u.abt.comp_status = le32_to_cpu(pkt->tgt_id_sts); + sp->done(vha, sp, 0); +} + +static void +qlafx00_ioctl_iosb_entry(scsi_qla_host_t *vha, struct req_que *req, + struct ioctl_iocb_entry_fx00 *pkt) +{ + const char func[] = "IOSB_IOCB"; + srb_t *sp; + struct fc_bsg_job *bsg_job; + struct srb_iocb *iocb_job; + int res; + struct qla_mt_iocb_rsp_fx00 fstatus; + uint8_t *fw_sts_ptr; + + sp = qla2x00_get_sp_from_handle(vha, func, req, pkt); + if (!sp) + return; + + if (sp->type == SRB_FXIOCB_DCMD) { + iocb_job = &sp->u.iocb_cmd; + iocb_job->u.fxiocb.seq_number = le32_to_cpu(pkt->seq_no); + iocb_job->u.fxiocb.fw_flags = le32_to_cpu(pkt->fw_iotcl_flags); + iocb_job->u.fxiocb.result = le32_to_cpu(pkt->status); + if (iocb_job->u.fxiocb.flags & SRB_FXDISC_RSP_DWRD_VALID) + iocb_job->u.fxiocb.req_data = + le32_to_cpu(pkt->dataword_r); + } else { + bsg_job = sp->u.bsg_job; + + memset(&fstatus, 0, sizeof(struct qla_mt_iocb_rsp_fx00)); + + fstatus.reserved_1 = pkt->reserved_0; + fstatus.func_type = pkt->comp_func_num; + fstatus.ioctl_flags = pkt->fw_iotcl_flags; + fstatus.ioctl_data = pkt->dataword_r; + fstatus.adapid = pkt->adapid; + fstatus.adapid_hi = pkt->adapid_hi; + fstatus.reserved_2 = pkt->reserved_1; + fstatus.res_count = pkt->residuallen; + fstatus.status = pkt->status; + fstatus.seq_number = pkt->seq_no; + memcpy(fstatus.reserved_3, + pkt->reserved_2, 20 * sizeof(uint8_t)); + + fw_sts_ptr = ((uint8_t *)bsg_job->req->sense) + + sizeof(struct fc_bsg_reply); + + memcpy(fw_sts_ptr, (uint8_t *)&fstatus, + sizeof(struct qla_mt_iocb_rsp_fx00)); + bsg_job->reply_len = sizeof(struct fc_bsg_reply) + + sizeof(struct qla_mt_iocb_rsp_fx00) + sizeof(uint8_t); + + ql_dump_buffer(ql_dbg_user + ql_dbg_verbose, + sp->fcport->vha, 0x5080, + (uint8_t *)pkt, sizeof(struct ioctl_iocb_entry_fx00)); + + ql_dump_buffer(ql_dbg_user + ql_dbg_verbose, + sp->fcport->vha, 0x5074, + (uint8_t *)fw_sts_ptr, sizeof(struct qla_mt_iocb_rsp_fx00)); + + res = bsg_job->reply->result = DID_OK << 16; + bsg_job->reply->reply_payload_rcv_len = + bsg_job->reply_payload.payload_len; + } + sp->done(vha, sp, res); +} + +/** + * qlafx00_status_entry() - Process a Status IOCB entry. + * @ha: SCSI driver HA context + * @pkt: Entry pointer + */ +static void +qlafx00_status_entry(scsi_qla_host_t *vha, struct rsp_que *rsp, void *pkt) +{ + srb_t *sp; + fc_port_t *fcport; + struct scsi_cmnd *cp; + struct sts_entry_fx00 *sts; + uint16_t comp_status; + uint16_t scsi_status; + uint16_t ox_id; + uint8_t lscsi_status; + int32_t resid; + uint32_t sense_len, par_sense_len, rsp_info_len, resid_len, + fw_resid_len; + uint8_t *rsp_info = NULL, *sense_data = NULL; + struct qla_hw_data *ha = vha->hw; + uint32_t hindex, handle; + uint16_t que; + struct req_que *req; + int logit = 1; + int res = 0; + + sts = (struct sts_entry_fx00 *) pkt; + + comp_status = le16_to_cpu(sts->comp_status); + scsi_status = le16_to_cpu(sts->scsi_status) & SS_MASK; + hindex = sts->handle; + handle = LSW(hindex); + + que = MSW(hindex); + req = ha->req_q_map[que]; + + /* Validate handle. */ + if (handle < req->num_outstanding_cmds) + sp = req->outstanding_cmds[handle]; + else + sp = NULL; + + if (sp == NULL) { + ql_dbg(ql_dbg_io, vha, 0x3034, + "Invalid status handle (0x%x).\n", handle); + + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + return; + } + + if (sp->type == SRB_TM_CMD) { + req->outstanding_cmds[handle] = NULL; + qlafx00_tm_iocb_entry(vha, req, pkt, sp, + scsi_status, comp_status); + return; + } + + /* Fast path completion. */ + if (comp_status == CS_COMPLETE && scsi_status == 0) { + qla2x00_do_host_ramp_up(vha); + qla2x00_process_completed_request(vha, req, handle); + return; + } + + req->outstanding_cmds[handle] = NULL; + cp = GET_CMD_SP(sp); + if (cp == NULL) { + ql_dbg(ql_dbg_io, vha, 0x3048, + "Command already returned (0x%x/%p).\n", + handle, sp); + + return; + } + + lscsi_status = scsi_status & STATUS_MASK; + + fcport = sp->fcport; + + ox_id = 0; + sense_len = par_sense_len = rsp_info_len = resid_len = + fw_resid_len = 0; + if (scsi_status & SS_SENSE_LEN_VALID) + sense_len = le32_to_cpu(sts->sense_len); + if (scsi_status & (SS_RESIDUAL_UNDER | SS_RESIDUAL_OVER)) + resid_len = le32_to_cpu(sts->residual_len); + if (comp_status == CS_DATA_UNDERRUN) + fw_resid_len = le32_to_cpu(sts->residual_len); + rsp_info = sense_data = sts->data; + par_sense_len = sizeof(sts->data); + + /* Check for overrun. */ + if (comp_status == CS_COMPLETE && + scsi_status & SS_RESIDUAL_OVER) + comp_status = CS_DATA_OVERRUN; + + /* + * Based on Host and scsi status generate status code for Linux + */ + switch (comp_status) { + case CS_COMPLETE: + case CS_QUEUE_FULL: + if (scsi_status == 0) { + res = DID_OK << 16; + break; + } + if (scsi_status & (SS_RESIDUAL_UNDER | SS_RESIDUAL_OVER)) { + resid = resid_len; + scsi_set_resid(cp, resid); + + if (!lscsi_status && + ((unsigned)(scsi_bufflen(cp) - resid) < + cp->underflow)) { + ql_dbg(ql_dbg_io, fcport->vha, 0x3050, + "Mid-layer underflow " + "detected (0x%x of 0x%x bytes).\n", + resid, scsi_bufflen(cp)); + + res = DID_ERROR << 16; + break; + } + } + res = DID_OK << 16 | lscsi_status; + + if (lscsi_status == SAM_STAT_TASK_SET_FULL) { + ql_dbg(ql_dbg_io, fcport->vha, 0x3051, + "QUEUE FULL detected.\n"); + break; + } + logit = 0; + if (lscsi_status != SS_CHECK_CONDITION) + break; + + memset(cp->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE); + if (!(scsi_status & SS_SENSE_LEN_VALID)) + break; + + qlafx00_handle_sense(sp, sense_data, par_sense_len, sense_len, + rsp, res); + break; + + case CS_DATA_UNDERRUN: + /* Use F/W calculated residual length. */ + if (IS_FWI2_CAPABLE(ha) || IS_QLAFX00(ha)) + resid = fw_resid_len; + else + resid = resid_len; + scsi_set_resid(cp, resid); + if (scsi_status & SS_RESIDUAL_UNDER) { + if ((IS_FWI2_CAPABLE(ha) || IS_QLAFX00(ha)) + && fw_resid_len != resid_len) { + ql_dbg(ql_dbg_io, fcport->vha, 0x3052, + "Dropped frame(s) detected " + "(0x%x of 0x%x bytes).\n", + resid, scsi_bufflen(cp)); + + res = DID_ERROR << 16 | lscsi_status; + goto check_scsi_status; + } + + if (!lscsi_status && + ((unsigned)(scsi_bufflen(cp) - resid) < + cp->underflow)) { + ql_dbg(ql_dbg_io, fcport->vha, 0x3053, + "Mid-layer underflow " + "detected (0x%x of 0x%x bytes, " + "cp->underflow: 0x%x).\n", + resid, scsi_bufflen(cp), cp->underflow); + + res = DID_ERROR << 16; + break; + } + } else if (lscsi_status != SAM_STAT_TASK_SET_FULL && + lscsi_status != SAM_STAT_BUSY) { + /* + * scsi status of task set and busy are considered + * to be task not completed. + */ + + ql_dbg(ql_dbg_io, fcport->vha, 0x3054, + "Dropped frame(s) detected (0x%x " + "of 0x%x bytes).\n", resid, + scsi_bufflen(cp)); + + res = DID_ERROR << 16 | lscsi_status; + goto check_scsi_status; + } else { + ql_dbg(ql_dbg_io, fcport->vha, 0x3055, + "scsi_status: 0x%x, lscsi_status: 0x%x\n", + scsi_status, lscsi_status); + } + + res = DID_OK << 16 | lscsi_status; + logit = 0; + +check_scsi_status: + /* + * Check to see if SCSI Status is non zero. If so report SCSI + * Status. + */ + if (lscsi_status != 0) { + if (lscsi_status == SAM_STAT_TASK_SET_FULL) { + ql_dbg(ql_dbg_io, fcport->vha, 0x3056, + "QUEUE FULL detected.\n"); + logit = 1; + break; + } + if (lscsi_status != SS_CHECK_CONDITION) + break; + + memset(cp->sense_buffer, 0, SCSI_SENSE_BUFFERSIZE); + if (!(scsi_status & SS_SENSE_LEN_VALID)) + break; + + qlafx00_handle_sense(sp, sense_data, par_sense_len, + sense_len, rsp, res); + } + break; + + case CS_PORT_LOGGED_OUT: + case CS_PORT_CONFIG_CHG: + case CS_PORT_BUSY: + case CS_INCOMPLETE: + case CS_PORT_UNAVAILABLE: + case CS_TIMEOUT: + case CS_RESET: + + /* + * We are going to have the fc class block the rport + * while we try to recover so instruct the mid layer + * to requeue until the class decides how to handle this. + */ + res = DID_TRANSPORT_DISRUPTED << 16; + + ql_dbg(ql_dbg_io, fcport->vha, 0x3057, + "Port down status: port-state=0x%x.\n", + atomic_read(&fcport->state)); + + if (atomic_read(&fcport->state) == FCS_ONLINE) + qla2x00_mark_device_lost(fcport->vha, fcport, 1, 1); + break; + + case CS_ABORTED: + res = DID_RESET << 16; + break; + + default: + res = DID_ERROR << 16; + break; + } + + if (logit) + ql_dbg(ql_dbg_io, fcport->vha, 0x3058, + "FCP command status: 0x%x-0x%x (0x%x) " + "nexus=%ld:%d:%d tgt_id: 0x%x lscsi_status: 0x%x" + "cdb=%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x len=0x%x " + "rsp_info=0x%x resid=0x%x fw_resid=0x%x " + "sense_len=0x%x, par_sense_len=0x%x, rsp_info_len=0x%x\n", + comp_status, scsi_status, res, vha->host_no, + cp->device->id, cp->device->lun, fcport->tgt_id, + lscsi_status, cp->cmnd[0], cp->cmnd[1], cp->cmnd[2], + cp->cmnd[3], cp->cmnd[4], cp->cmnd[5], cp->cmnd[6], + cp->cmnd[7], cp->cmnd[8], cp->cmnd[9], scsi_bufflen(cp), + rsp_info_len, resid_len, fw_resid_len, sense_len, + par_sense_len, rsp_info_len); + + if (!res) + qla2x00_do_host_ramp_up(vha); + + if (rsp->status_srb == NULL) + sp->done(ha, sp, res); +} + +/** + * qlafx00_status_cont_entry() - Process a Status Continuations entry. + * @ha: SCSI driver HA context + * @pkt: Entry pointer + * + * Extended sense data. + */ +static void +qlafx00_status_cont_entry(struct rsp_que *rsp, sts_cont_entry_t *pkt) +{ + uint8_t sense_sz = 0; + struct qla_hw_data *ha = rsp->hw; + struct scsi_qla_host *vha = pci_get_drvdata(ha->pdev); + srb_t *sp = rsp->status_srb; + struct scsi_cmnd *cp; + uint32_t sense_len; + uint8_t *sense_ptr; + + if (!sp) { + ql_dbg(ql_dbg_io, vha, 0x3037, + "no SP, sp = %p\n", sp); + return; + } + + if (!GET_FW_SENSE_LEN(sp)) { + ql_dbg(ql_dbg_io, vha, 0x304b, + "no fw sense data, sp = %p\n", sp); + return; + } + cp = GET_CMD_SP(sp); + if (cp == NULL) { + ql_log(ql_log_warn, vha, 0x303b, + "cmd is NULL: already returned to OS (sp=%p).\n", sp); + + rsp->status_srb = NULL; + return; + } + + if (!GET_CMD_SENSE_LEN(sp)) { + ql_dbg(ql_dbg_io, vha, 0x304c, + "no sense data, sp = %p\n", sp); + } else { + sense_len = GET_CMD_SENSE_LEN(sp); + sense_ptr = GET_CMD_SENSE_PTR(sp); + ql_dbg(ql_dbg_io, vha, 0x304f, + "sp=%p sense_len=0x%x sense_ptr=%p.\n", + sp, sense_len, sense_ptr); + + if (sense_len > sizeof(pkt->data)) + sense_sz = sizeof(pkt->data); + else + sense_sz = sense_len; + + /* Move sense data. */ + ql_dump_buffer(ql_dbg_io + ql_dbg_buffer, vha, 0x304e, + (uint8_t *)pkt, sizeof(sts_cont_entry_t)); + memcpy(sense_ptr, pkt->data, sense_sz); + ql_dump_buffer(ql_dbg_io + ql_dbg_buffer, vha, 0x304a, + sense_ptr, sense_sz); + + sense_len -= sense_sz; + sense_ptr += sense_sz; + + SET_CMD_SENSE_PTR(sp, sense_ptr); + SET_CMD_SENSE_LEN(sp, sense_len); + } + sense_len = GET_FW_SENSE_LEN(sp); + sense_len = (sense_len > sizeof(pkt->data)) ? + (sense_len - sizeof(pkt->data)) : 0; + SET_FW_SENSE_LEN(sp, sense_len); + + /* Place command on done queue. */ + if (sense_len == 0) { + rsp->status_srb = NULL; + sp->done(ha, sp, cp->result); + } +} + +/** + * qlafx00_multistatus_entry() - Process Multi response queue entries. + * @ha: SCSI driver HA context + */ +static void +qlafx00_multistatus_entry(struct scsi_qla_host *vha, + struct rsp_que *rsp, void *pkt) +{ + srb_t *sp; + struct multi_sts_entry_fx00 *stsmfx; + struct qla_hw_data *ha = vha->hw; + uint32_t handle, hindex, handle_count, i; + uint16_t que; + struct req_que *req; + uint32_t *handle_ptr; + + stsmfx = (struct multi_sts_entry_fx00 *) pkt; + + handle_count = stsmfx->handle_count; + + if (handle_count > MAX_HANDLE_COUNT) { + ql_dbg(ql_dbg_io, vha, 0x3035, + "Invalid handle count (0x%x).\n", handle_count); + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + return; + } + + handle_ptr = (uint32_t *) &stsmfx->handles[0]; + + for (i = 0; i < handle_count; i++) { + hindex = le32_to_cpu(*handle_ptr); + handle = LSW(hindex); + que = MSW(hindex); + req = ha->req_q_map[que]; + + /* Validate handle. */ + if (handle < req->num_outstanding_cmds) + sp = req->outstanding_cmds[handle]; + else + sp = NULL; + + if (sp == NULL) { + ql_dbg(ql_dbg_io, vha, 0x3044, + "Invalid status handle (0x%x).\n", handle); + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + return; + } + qla2x00_process_completed_request(vha, req, handle); + handle_ptr++; + } +} + +/** + * qlafx00_error_entry() - Process an error entry. + * @ha: SCSI driver HA context + * @pkt: Entry pointer + */ +static void +qlafx00_error_entry(scsi_qla_host_t *vha, struct rsp_que *rsp, + struct sts_entry_fx00 *pkt, uint8_t estatus, uint8_t etype) +{ + srb_t *sp; + struct qla_hw_data *ha = vha->hw; + const char func[] = "ERROR-IOCB"; + uint16_t que = MSW(pkt->handle); + struct req_que *req = NULL; + int res = DID_ERROR << 16; + + ql_dbg(ql_dbg_async, vha, 0x507f, + "type of error status in response: 0x%x\n", estatus); + + req = ha->req_q_map[que]; + + sp = qla2x00_get_sp_from_handle(vha, func, req, pkt); + if (sp) { + sp->done(ha, sp, res); + return; + } + + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); +} + +/** + * qlafx00_process_response_queue() - Process response queue entries. + * @ha: SCSI driver HA context + */ +static void +qlafx00_process_response_queue(struct scsi_qla_host *vha, + struct rsp_que *rsp) +{ + struct sts_entry_fx00 *pkt; + response_t *lptr; + + if (!vha->flags.online) + return; + + while (RD_REG_DWORD(&(rsp->ring_ptr->signature)) != + RESPONSE_PROCESSED) { + lptr = rsp->ring_ptr; + memcpy_fromio(rsp->rsp_pkt, lptr, sizeof(rsp->rsp_pkt)); + pkt = (struct sts_entry_fx00 *)rsp->rsp_pkt; + + rsp->ring_index++; + if (rsp->ring_index == rsp->length) { + rsp->ring_index = 0; + rsp->ring_ptr = rsp->ring; + } else { + rsp->ring_ptr++; + } + + if (pkt->entry_status != 0 && + pkt->entry_type != IOCTL_IOSB_TYPE_FX00) { + qlafx00_error_entry(vha, rsp, + (struct sts_entry_fx00 *)pkt, pkt->entry_status, + pkt->entry_type); + goto next_iter; + continue; + } + + switch (pkt->entry_type) { + case STATUS_TYPE_FX00: + qlafx00_status_entry(vha, rsp, pkt); + break; + + case STATUS_CONT_TYPE_FX00: + qlafx00_status_cont_entry(rsp, (sts_cont_entry_t *)pkt); + break; + + case MULTI_STATUS_TYPE_FX00: + qlafx00_multistatus_entry(vha, rsp, pkt); + break; + + case ABORT_IOCB_TYPE_FX00: + qlafx00_abort_iocb_entry(vha, rsp->req, + (struct abort_iocb_entry_fx00 *)pkt); + break; + + case IOCTL_IOSB_TYPE_FX00: + qlafx00_ioctl_iosb_entry(vha, rsp->req, + (struct ioctl_iocb_entry_fx00 *)pkt); + break; + default: + /* Type Not Supported. */ + ql_dbg(ql_dbg_async, vha, 0x5081, + "Received unknown response pkt type %x " + "entry status=%x.\n", + pkt->entry_type, pkt->entry_status); + break; + } +next_iter: + WRT_REG_DWORD(&lptr->signature, RESPONSE_PROCESSED); + wmb(); + } + + /* Adjust ring index */ + WRT_REG_DWORD(rsp->rsp_q_out, rsp->ring_index); +} + +/** + * qlafx00_async_event() - Process aynchronous events. + * @ha: SCSI driver HA context + */ +static void +qlafx00_async_event(scsi_qla_host_t *vha) +{ + struct qla_hw_data *ha = vha->hw; + struct device_reg_fx00 __iomem *reg; + int data_size = 1; + + reg = &ha->iobase->ispfx00; + /* Setup to process RIO completion. */ + switch (ha->aenmb[0]) { + case QLAFX00_MBA_SYSTEM_ERR: /* System Error */ + ql_log(ql_log_warn, vha, 0x5079, + "ISP System Error - mbx1=%x\n", ha->aenmb[0]); + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + break; + + case QLAFX00_MBA_SHUTDOWN_RQSTD: /* Shutdown requested */ + ql_dbg(ql_dbg_async, vha, 0x5076, + "Asynchronous FW shutdown requested.\n"); + set_bit(ISP_ABORT_NEEDED, &vha->dpc_flags); + qla2xxx_wake_dpc(vha); + break; + + case QLAFX00_MBA_PORT_UPDATE: /* Port database update */ + ha->aenmb[1] = RD_REG_WORD(®->aenmailbox1); + ha->aenmb[2] = RD_REG_WORD(®->aenmailbox2); + ha->aenmb[3] = RD_REG_WORD(®->aenmailbox3); + ql_dbg(ql_dbg_async, vha, 0x5077, + "Asynchronous port Update received " + "aenmb[0]: %x, aenmb[1]: %x, aenmb[2]: %x, aenmb[3]: %x\n", + ha->aenmb[0], ha->aenmb[1], ha->aenmb[2], ha->aenmb[3]); + data_size = 4; + break; + default: + ha->aenmb[1] = RD_REG_WORD(®->aenmailbox1); + ha->aenmb[2] = RD_REG_WORD(®->aenmailbox2); + ha->aenmb[3] = RD_REG_WORD(®->aenmailbox3); + ha->aenmb[4] = RD_REG_WORD(®->aenmailbox4); + ha->aenmb[5] = RD_REG_WORD(®->aenmailbox5); + ha->aenmb[6] = RD_REG_WORD(®->aenmailbox6); + ha->aenmb[7] = RD_REG_WORD(®->aenmailbox7); + ql_dbg(ql_dbg_async, vha, 0x5078, + "AEN:%04x %04x %04x %04x :%04x %04x %04x %04x\n", + ha->aenmb[0], ha->aenmb[1], ha->aenmb[2], ha->aenmb[3], + ha->aenmb[4], ha->aenmb[5], ha->aenmb[6], ha->aenmb[7]); + break; + } + qlafx00_post_aenfx_work(vha, ha->aenmb[0], + (uint32_t *)ha->aenmb, data_size); +} + +/** + * + * qlafx00x_mbx_completion() - Process mailbox command completions. + * @ha: SCSI driver HA context + * @mb16: Mailbox16 register + */ +static void +qlafx00_mbx_completion(scsi_qla_host_t *vha, uint32_t mb0) +{ + uint16_t cnt; + uint16_t __iomem *wptr; + struct qla_hw_data *ha = vha->hw; + struct device_reg_fx00 __iomem *reg = &ha->iobase->ispfx00; + + if (!ha->mcp32) + ql_dbg(ql_dbg_async, vha, 0x507e, "MBX pointer ERROR.\n"); + + /* Load return mailbox registers. */ + ha->flags.mbox_int = 1; + ha->mailbox_out32[0] = mb0; + wptr = (uint16_t __iomem *)®->mailbox17; + + for (cnt = 1; cnt < ha->mbx_count; cnt++) { + ha->mailbox_out32[cnt] = RD_REG_WORD(wptr); + wptr++; + } +} + +/** + * qlafx00_intr_handler() - Process interrupts for the ISPFX00. + * @irq: + * @dev_id: SCSI driver HA context + * + * Called by system whenever the host adapter generates an interrupt. + * + * Returns handled flag. + */ +irqreturn_t +qlafx00_intr_handler(int irq, void *dev_id) +{ + scsi_qla_host_t *vha; + struct qla_hw_data *ha; + struct device_reg_fx00 __iomem *reg; + int status; + unsigned long iter; + uint32_t stat; + uint32_t mb[8]; + struct rsp_que *rsp; + unsigned long flags; + uint32_t clr_intr = 0; + + rsp = (struct rsp_que *) dev_id; + if (!rsp) { + ql_log(ql_log_info, NULL, 0x507d, + "%s: NULL response queue pointer.\n", __func__); + return IRQ_NONE; + } + + ha = rsp->hw; + reg = &ha->iobase->ispfx00; + status = 0; + + if (unlikely(pci_channel_offline(ha->pdev))) + return IRQ_HANDLED; + + spin_lock_irqsave(&ha->hardware_lock, flags); + vha = pci_get_drvdata(ha->pdev); + for (iter = 50; iter--; clr_intr = 0) { + stat = QLAFX00_RD_INTR_REG(ha); + if ((stat & QLAFX00_HST_INT_STS_BITS) == 0) + break; + + switch (stat & QLAFX00_HST_INT_STS_BITS) { + case QLAFX00_INTR_MB_CMPLT: + case QLAFX00_INTR_MB_RSP_CMPLT: + case QLAFX00_INTR_MB_ASYNC_CMPLT: + case QLAFX00_INTR_ALL_CMPLT: + mb[0] = RD_REG_WORD(®->mailbox16); + qlafx00_mbx_completion(vha, mb[0]); + status |= MBX_INTERRUPT; + clr_intr |= QLAFX00_INTR_MB_CMPLT; + break; + case QLAFX00_INTR_ASYNC_CMPLT: + case QLAFX00_INTR_RSP_ASYNC_CMPLT: + ha->aenmb[0] = RD_REG_WORD(®->aenmailbox0); + qlafx00_async_event(vha); + clr_intr |= QLAFX00_INTR_ASYNC_CMPLT; + break; + case QLAFX00_INTR_RSP_CMPLT: + qlafx00_process_response_queue(vha, rsp); + clr_intr |= QLAFX00_INTR_RSP_CMPLT; + break; + default: + ql_dbg(ql_dbg_async, vha, 0x507a, + "Unrecognized interrupt type (%d).\n", stat); + break; + } + QLAFX00_CLR_INTR_REG(ha, clr_intr); + QLAFX00_RD_INTR_REG(ha); + } + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + if (test_bit(MBX_INTR_WAIT, &ha->mbx_cmd_flags) && + (status & MBX_INTERRUPT) && ha->flags.mbox_int) { + set_bit(MBX_INTERRUPT, &ha->mbx_cmd_flags); + complete(&ha->mbx_intr_comp); + } + return IRQ_HANDLED; +} + +/** QLAFX00 specific IOCB implementation functions */ + +static inline cont_a64_entry_t * +qlafx00_prep_cont_type1_iocb(struct req_que *req, + cont_a64_entry_t *lcont_pkt) +{ + cont_a64_entry_t *cont_pkt; + + /* Adjust ring index. */ + req->ring_index++; + if (req->ring_index == req->length) { + req->ring_index = 0; + req->ring_ptr = req->ring; + } else { + req->ring_ptr++; + } + + cont_pkt = (cont_a64_entry_t *)req->ring_ptr; + + /* Load packet defaults. */ + *((uint32_t *)(&lcont_pkt->entry_type)) = + __constant_cpu_to_le32(CONTINUE_A64_TYPE_FX00); + + return cont_pkt; +} + +static inline void +qlafx00_build_scsi_iocbs(srb_t *sp, struct cmd_type_7_fx00 *cmd_pkt, + uint16_t tot_dsds, struct cmd_type_7_fx00 *lcmd_pkt) +{ + uint16_t avail_dsds; + uint32_t *cur_dsd; + scsi_qla_host_t *vha; + struct scsi_cmnd *cmd; + struct scatterlist *sg; + int i, cont; + struct req_que *req; + cont_a64_entry_t lcont_pkt; + cont_a64_entry_t *cont_pkt; + + vha = sp->fcport->vha; + req = vha->req; + + cmd = GET_CMD_SP(sp); + cont = 0; + cont_pkt = NULL; + + /* Update entry type to indicate Command Type 3 IOCB */ + *((uint32_t *)(&lcmd_pkt->entry_type)) = + __constant_cpu_to_le32(FX00_COMMAND_TYPE_7); + + /* No data transfer */ + if (!scsi_bufflen(cmd) || cmd->sc_data_direction == DMA_NONE) { + lcmd_pkt->byte_count = __constant_cpu_to_le32(0); + return; + } + + /* Set transfer direction */ + if (cmd->sc_data_direction == DMA_TO_DEVICE) { + lcmd_pkt->cntrl_flags = + __constant_cpu_to_le16(TMF_WRITE_DATA); + vha->qla_stats.output_bytes += scsi_bufflen(cmd); + } else if (cmd->sc_data_direction == DMA_FROM_DEVICE) { + lcmd_pkt->cntrl_flags = + __constant_cpu_to_le16(TMF_READ_DATA); + vha->qla_stats.input_bytes += scsi_bufflen(cmd); + } + + /* One DSD is available in the Command Type 3 IOCB */ + avail_dsds = 1; + cur_dsd = (uint32_t *)&lcmd_pkt->dseg_0_address; + + /* Load data segments */ + scsi_for_each_sg(cmd, sg, tot_dsds, i) { + dma_addr_t sle_dma; + + /* Allocate additional continuation packets? */ + if (avail_dsds == 0) { + /* + * Five DSDs are available in the Continuation + * Type 1 IOCB. + */ + memset(&lcont_pkt, 0, REQUEST_ENTRY_SIZE); + cont_pkt = + qlafx00_prep_cont_type1_iocb(req, &lcont_pkt); + cur_dsd = (uint32_t *)lcont_pkt.dseg_0_address; + avail_dsds = 5; + cont = 1; + } + + sle_dma = sg_dma_address(sg); + *cur_dsd++ = cpu_to_le32(LSD(sle_dma)); + *cur_dsd++ = cpu_to_le32(MSD(sle_dma)); + *cur_dsd++ = cpu_to_le32(sg_dma_len(sg)); + avail_dsds--; + if (avail_dsds == 0 && cont == 1) { + cont = 0; + memcpy_toio((void __iomem *)cont_pkt, &lcont_pkt, + REQUEST_ENTRY_SIZE); + } + + } + if (avail_dsds != 0 && cont == 1) { + memcpy_toio((void __iomem *)cont_pkt, &lcont_pkt, + REQUEST_ENTRY_SIZE); + } +} + +/** + * qlafx00_start_scsi() - Send a SCSI command to the ISP + * @sp: command to send to the ISP + * + * Returns non-zero if a failure occurred, else zero. + */ +int +qlafx00_start_scsi(srb_t *sp) +{ + int ret, nseg; + unsigned long flags; + uint32_t index; + uint32_t handle; + uint16_t cnt; + uint16_t req_cnt; + uint16_t tot_dsds; + struct req_que *req = NULL; + struct rsp_que *rsp = NULL; + struct scsi_cmnd *cmd = GET_CMD_SP(sp); + struct scsi_qla_host *vha = sp->fcport->vha; + struct qla_hw_data *ha = vha->hw; + struct cmd_type_7_fx00 *cmd_pkt; + struct cmd_type_7_fx00 lcmd_pkt; + struct scsi_lun llun; + char tag[2]; + + /* Setup device pointers. */ + ret = 0; + + rsp = ha->rsp_q_map[0]; + req = vha->req; + + /* So we know we haven't pci_map'ed anything yet */ + tot_dsds = 0; + + /* Forcing marker needed for now */ + vha->marker_needed = 0; + + /* Send marker if required */ + if (vha->marker_needed != 0) { + if (qla2x00_marker(vha, req, rsp, 0, 0, MK_SYNC_ALL) != + QLA_SUCCESS) + return QLA_FUNCTION_FAILED; + vha->marker_needed = 0; + } + + /* Acquire ring specific lock */ + spin_lock_irqsave(&ha->hardware_lock, flags); + + /* Check for room in outstanding command list. */ + handle = req->current_outstanding_cmd; + for (index = 1; index < req->num_outstanding_cmds; index++) { + handle++; + if (handle == req->num_outstanding_cmds) + handle = 1; + if (!req->outstanding_cmds[handle]) + break; + } + if (index == req->num_outstanding_cmds) + goto queuing_error; + + /* Map the sg table so we have an accurate count of sg entries needed */ + if (scsi_sg_count(cmd)) { + nseg = dma_map_sg(&ha->pdev->dev, scsi_sglist(cmd), + scsi_sg_count(cmd), cmd->sc_data_direction); + if (unlikely(!nseg)) + goto queuing_error; + } else + nseg = 0; + + tot_dsds = nseg; + req_cnt = qla24xx_calc_iocbs(vha, tot_dsds); + if (req->cnt < (req_cnt + 2)) { + cnt = RD_REG_DWORD_RELAXED(req->req_q_out); + + if (req->ring_index < cnt) + req->cnt = cnt - req->ring_index; + else + req->cnt = req->length - + (req->ring_index - cnt); + if (req->cnt < (req_cnt + 2)) + goto queuing_error; + } + + /* Build command packet. */ + req->current_outstanding_cmd = handle; + req->outstanding_cmds[handle] = sp; + sp->handle = handle; + cmd->host_scribble = (unsigned char *)(unsigned long)handle; + req->cnt -= req_cnt; + + cmd_pkt = (struct cmd_type_7_fx00 *)req->ring_ptr; + + memset(&lcmd_pkt, 0, REQUEST_ENTRY_SIZE); + + lcmd_pkt.handle = MAKE_HANDLE(req->id, sp->handle); + lcmd_pkt.handle_hi = 0; + lcmd_pkt.dseg_count = cpu_to_le16(tot_dsds); + lcmd_pkt.tgt_idx = cpu_to_le16(sp->fcport->tgt_id); + + int_to_scsilun(cmd->device->lun, &llun); + host_to_adap((uint8_t *)&llun, (uint8_t *)&lcmd_pkt.lun, + sizeof(lcmd_pkt.lun)); + + /* Update tagged queuing modifier -- default is TSK_SIMPLE (0). */ + if (scsi_populate_tag_msg(cmd, tag)) { + switch (tag[0]) { + case HEAD_OF_QUEUE_TAG: + lcmd_pkt.task = TSK_HEAD_OF_QUEUE; + break; + case ORDERED_QUEUE_TAG: + lcmd_pkt.task = TSK_ORDERED; + break; + } + } + + /* Load SCSI command packet. */ + host_to_adap(cmd->cmnd, lcmd_pkt.fcp_cdb, sizeof(lcmd_pkt.fcp_cdb)); + lcmd_pkt.byte_count = cpu_to_le32((uint32_t)scsi_bufflen(cmd)); + + /* Build IOCB segments */ + qlafx00_build_scsi_iocbs(sp, cmd_pkt, tot_dsds, &lcmd_pkt); + + /* Set total data segment count. */ + lcmd_pkt.entry_count = (uint8_t)req_cnt; + + /* Specify response queue number where completion should happen */ + lcmd_pkt.entry_status = (uint8_t) rsp->id; + + ql_dump_buffer(ql_dbg_io + ql_dbg_buffer, vha, 0x302e, + (uint8_t *)cmd->cmnd, cmd->cmd_len); + ql_dump_buffer(ql_dbg_io + ql_dbg_buffer, vha, 0x3032, + (uint8_t *)&lcmd_pkt, REQUEST_ENTRY_SIZE); + + memcpy_toio((void __iomem *)cmd_pkt, &lcmd_pkt, REQUEST_ENTRY_SIZE); + wmb(); + + /* Adjust ring index. */ + req->ring_index++; + if (req->ring_index == req->length) { + req->ring_index = 0; + req->ring_ptr = req->ring; + } else + req->ring_ptr++; + + sp->flags |= SRB_DMA_VALID; + + /* Set chip new ring index. */ + WRT_REG_DWORD(req->req_q_in, req->ring_index); + QLAFX00_SET_HST_INTR(ha, ha->rqstq_intr_code); + + spin_unlock_irqrestore(&ha->hardware_lock, flags); + return QLA_SUCCESS; + +queuing_error: + if (tot_dsds) + scsi_dma_unmap(cmd); + + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + return QLA_FUNCTION_FAILED; +} + +void +qlafx00_tm_iocb(srb_t *sp, struct tsk_mgmt_entry_fx00 *ptm_iocb) +{ + struct srb_iocb *fxio = &sp->u.iocb_cmd; + scsi_qla_host_t *vha = sp->fcport->vha; + struct req_que *req = vha->req; + struct tsk_mgmt_entry_fx00 tm_iocb; + struct scsi_lun llun; + + memset(&tm_iocb, 0, sizeof(struct tsk_mgmt_entry_fx00)); + tm_iocb.entry_type = TSK_MGMT_IOCB_TYPE_FX00; + tm_iocb.entry_count = 1; + tm_iocb.handle = cpu_to_le32(MAKE_HANDLE(req->id, sp->handle)); + tm_iocb.handle_hi = 0; + tm_iocb.timeout = cpu_to_le16(qla2x00_get_async_timeout(vha) + 2); + tm_iocb.tgt_id = cpu_to_le16(sp->fcport->tgt_id); + tm_iocb.control_flags = cpu_to_le32(fxio->u.tmf.flags); + if (tm_iocb.control_flags == TCF_LUN_RESET) { + int_to_scsilun(fxio->u.tmf.lun, &llun); + host_to_adap((uint8_t *)&llun, (uint8_t *)&tm_iocb.lun, + sizeof(struct scsi_lun)); + } + + memcpy((void __iomem *)ptm_iocb, &tm_iocb, + sizeof(struct tsk_mgmt_entry_fx00)); + wmb(); +} + +void +qlafx00_abort_iocb(srb_t *sp, struct abort_iocb_entry_fx00 *pabt_iocb) +{ + struct srb_iocb *fxio = &sp->u.iocb_cmd; + scsi_qla_host_t *vha = sp->fcport->vha; + struct req_que *req = vha->req; + struct abort_iocb_entry_fx00 abt_iocb; + + memset(&abt_iocb, 0, sizeof(struct abort_iocb_entry_fx00)); + abt_iocb.entry_type = ABORT_IOCB_TYPE_FX00; + abt_iocb.entry_count = 1; + abt_iocb.handle = cpu_to_le32(MAKE_HANDLE(req->id, sp->handle)); + abt_iocb.abort_handle = + cpu_to_le32(MAKE_HANDLE(req->id, fxio->u.abt.cmd_hndl)); + abt_iocb.tgt_id_sts = cpu_to_le16(sp->fcport->tgt_id); + abt_iocb.req_que_no = cpu_to_le16(req->id); + + memcpy((void __iomem *)pabt_iocb, &abt_iocb, + sizeof(struct abort_iocb_entry_fx00)); + wmb(); +} + +void +qlafx00_fxdisc_iocb(srb_t *sp, struct fxdisc_entry_fx00 *pfxiocb) +{ + struct srb_iocb *fxio = &sp->u.iocb_cmd; + struct qla_mt_iocb_rqst_fx00 *piocb_rqst; + struct fc_bsg_job *bsg_job; + struct fxdisc_entry_fx00 fx_iocb; + uint8_t entry_cnt = 1; + + memset(&fx_iocb, 0, sizeof(struct fxdisc_entry_fx00)); + fx_iocb.entry_type = FX00_IOCB_TYPE; + fx_iocb.handle = cpu_to_le32(sp->handle); + fx_iocb.entry_count = entry_cnt; + + if (sp->type == SRB_FXIOCB_DCMD) { + fx_iocb.func_num = + cpu_to_le16(sp->u.iocb_cmd.u.fxiocb.req_func_type); + fx_iocb.adapid = cpu_to_le32(fxio->u.fxiocb.adapter_id); + fx_iocb.adapid_hi = cpu_to_le32(fxio->u.fxiocb.adapter_id_hi); + fx_iocb.reserved_0 = cpu_to_le32(fxio->u.fxiocb.reserved_0); + fx_iocb.reserved_1 = cpu_to_le32(fxio->u.fxiocb.reserved_1); + fx_iocb.dataword_extra = + cpu_to_le32(fxio->u.fxiocb.req_data_extra); + + if (fxio->u.fxiocb.flags & SRB_FXDISC_REQ_DMA_VALID) { + fx_iocb.req_dsdcnt = cpu_to_le16(1); + fx_iocb.req_xfrcnt = + cpu_to_le16(fxio->u.fxiocb.req_len); + fx_iocb.dseg_rq_address[0] = + cpu_to_le32(LSD(fxio->u.fxiocb.req_dma_handle)); + fx_iocb.dseg_rq_address[1] = + cpu_to_le32(MSD(fxio->u.fxiocb.req_dma_handle)); + fx_iocb.dseg_rq_len = + cpu_to_le32(fxio->u.fxiocb.req_len); + } + + if (fxio->u.fxiocb.flags & SRB_FXDISC_RESP_DMA_VALID) { + fx_iocb.rsp_dsdcnt = cpu_to_le16(1); + fx_iocb.rsp_xfrcnt = + cpu_to_le16(fxio->u.fxiocb.rsp_len); + fx_iocb.dseg_rsp_address[0] = + cpu_to_le32(LSD(fxio->u.fxiocb.rsp_dma_handle)); + fx_iocb.dseg_rsp_address[1] = + cpu_to_le32(MSD(fxio->u.fxiocb.rsp_dma_handle)); + fx_iocb.dseg_rsp_len = + cpu_to_le32(fxio->u.fxiocb.rsp_len); + } + + if (fxio->u.fxiocb.flags & SRB_FXDISC_REQ_DWRD_VALID) { + fx_iocb.dataword = + cpu_to_le32(fxio->u.fxiocb.req_data); + } + fx_iocb.flags = fxio->u.fxiocb.flags; + } else { + struct scatterlist *sg; + bsg_job = sp->u.bsg_job; + piocb_rqst = (struct qla_mt_iocb_rqst_fx00 *) + &bsg_job->request->rqst_data.h_vendor.vendor_cmd[1]; + + fx_iocb.func_num = piocb_rqst->func_type; + fx_iocb.adapid = piocb_rqst->adapid; + fx_iocb.adapid_hi = piocb_rqst->adapid_hi; + fx_iocb.reserved_0 = piocb_rqst->reserved_0; + fx_iocb.reserved_1 = piocb_rqst->reserved_1; + fx_iocb.dataword_extra = piocb_rqst->dataword_extra; + fx_iocb.dataword = piocb_rqst->dataword; + fx_iocb.req_xfrcnt = cpu_to_le16(piocb_rqst->req_len); + fx_iocb.rsp_xfrcnt = cpu_to_le16(piocb_rqst->rsp_len); + + if (piocb_rqst->flags & SRB_FXDISC_REQ_DMA_VALID) { + int avail_dsds, tot_dsds; + cont_a64_entry_t lcont_pkt; + cont_a64_entry_t *cont_pkt = NULL; + uint32_t *cur_dsd; + int index = 0, cont = 0; + + fx_iocb.req_dsdcnt = + cpu_to_le16(bsg_job->request_payload.sg_cnt); + tot_dsds = + cpu_to_le32(bsg_job->request_payload.sg_cnt); + cur_dsd = (uint32_t *)&fx_iocb.dseg_rq_address[0]; + avail_dsds = 1; + for_each_sg(bsg_job->request_payload.sg_list, sg, + tot_dsds, index) { + dma_addr_t sle_dma; + + /* Allocate additional continuation packets? */ + if (avail_dsds == 0) { + /* + * Five DSDs are available in the Cont. + * Type 1 IOCB. + */ + memset(&lcont_pkt, 0, + REQUEST_ENTRY_SIZE); + cont_pkt = + qlafx00_prep_cont_type1_iocb( + sp->fcport->vha->req, + &lcont_pkt); + cur_dsd = (uint32_t *) + lcont_pkt.dseg_0_address; + avail_dsds = 5; + cont = 1; + entry_cnt++; + } + + sle_dma = sg_dma_address(sg); + *cur_dsd++ = cpu_to_le32(LSD(sle_dma)); + *cur_dsd++ = cpu_to_le32(MSD(sle_dma)); + *cur_dsd++ = cpu_to_le32(sg_dma_len(sg)); + avail_dsds--; + + if (avail_dsds == 0 && cont == 1) { + cont = 0; + memcpy_toio( + (void __iomem *)cont_pkt, + &lcont_pkt, REQUEST_ENTRY_SIZE); + ql_dump_buffer( + ql_dbg_user + ql_dbg_verbose, + sp->fcport->vha, 0x3042, + (uint8_t *)&lcont_pkt, + REQUEST_ENTRY_SIZE); + } + } + if (avail_dsds != 0 && cont == 1) { + memcpy_toio((void __iomem *)cont_pkt, + &lcont_pkt, REQUEST_ENTRY_SIZE); + ql_dump_buffer(ql_dbg_user + ql_dbg_verbose, + sp->fcport->vha, 0x3043, + (uint8_t *)&lcont_pkt, REQUEST_ENTRY_SIZE); + } + } + + if (piocb_rqst->flags & SRB_FXDISC_RESP_DMA_VALID) { + int avail_dsds, tot_dsds; + cont_a64_entry_t lcont_pkt; + cont_a64_entry_t *cont_pkt = NULL; + uint32_t *cur_dsd; + int index = 0, cont = 0; + + fx_iocb.rsp_dsdcnt = + cpu_to_le16(bsg_job->reply_payload.sg_cnt); + tot_dsds = cpu_to_le32(bsg_job->reply_payload.sg_cnt); + cur_dsd = (uint32_t *)&fx_iocb.dseg_rsp_address[0]; + avail_dsds = 1; + + for_each_sg(bsg_job->reply_payload.sg_list, sg, + tot_dsds, index) { + dma_addr_t sle_dma; + + /* Allocate additional continuation packets? */ + if (avail_dsds == 0) { + /* + * Five DSDs are available in the Cont. + * Type 1 IOCB. + */ + memset(&lcont_pkt, 0, + REQUEST_ENTRY_SIZE); + cont_pkt = + qlafx00_prep_cont_type1_iocb( + sp->fcport->vha->req, + &lcont_pkt); + cur_dsd = (uint32_t *) + lcont_pkt.dseg_0_address; + avail_dsds = 5; + cont = 1; + entry_cnt++; + } + + sle_dma = sg_dma_address(sg); + *cur_dsd++ = cpu_to_le32(LSD(sle_dma)); + *cur_dsd++ = cpu_to_le32(MSD(sle_dma)); + *cur_dsd++ = cpu_to_le32(sg_dma_len(sg)); + avail_dsds--; + + if (avail_dsds == 0 && cont == 1) { + cont = 0; + memcpy_toio((void __iomem *)cont_pkt, + &lcont_pkt, + REQUEST_ENTRY_SIZE); + ql_dump_buffer( + ql_dbg_user + ql_dbg_verbose, + sp->fcport->vha, 0x3045, + (uint8_t *)&lcont_pkt, + REQUEST_ENTRY_SIZE); + } + } + if (avail_dsds != 0 && cont == 1) { + memcpy_toio((void __iomem *)cont_pkt, + &lcont_pkt, REQUEST_ENTRY_SIZE); + ql_dump_buffer(ql_dbg_user + ql_dbg_verbose, + sp->fcport->vha, 0x3046, + (uint8_t *)&lcont_pkt, REQUEST_ENTRY_SIZE); + } + } + + if (piocb_rqst->flags & SRB_FXDISC_REQ_DWRD_VALID) + fx_iocb.dataword = cpu_to_le32(piocb_rqst->dataword); + fx_iocb.flags = piocb_rqst->flags; + fx_iocb.entry_count = entry_cnt; + } + + ql_dump_buffer(ql_dbg_user + ql_dbg_verbose, + sp->fcport->vha, 0x3047, + (uint8_t *)&fx_iocb, sizeof(struct fxdisc_entry_fx00)); + + memcpy((void __iomem *)pfxiocb, &fx_iocb, + sizeof(struct fxdisc_entry_fx00)); + wmb(); +} diff --git a/drivers/scsi/qla2xxx/qla_mr.h b/drivers/scsi/qla2xxx/qla_mr.h new file mode 100644 index 00000000000..cc327dc2fd1 --- /dev/null +++ b/drivers/scsi/qla2xxx/qla_mr.h @@ -0,0 +1,510 @@ +/* + * QLogic Fibre Channel HBA Driver + * Copyright (c) 2003-2013 QLogic Corporation + * + * See LICENSE.qla2xxx for copyright and licensing details. + */ +#ifndef __QLA_MR_H +#define __QLA_MR_H + +/* + * The PCI VendorID and DeviceID for our board. + */ +#define PCI_DEVICE_ID_QLOGIC_ISPF001 0xF001 + +/* FX00 specific definitions */ + +#define FX00_COMMAND_TYPE_7 0x07 /* Command Type 7 entry for 7XXX */ +struct cmd_type_7_fx00 { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + + uint32_t handle; /* System handle. */ + uint32_t handle_hi; + + uint16_t tgt_idx; /* Target Idx. */ + uint16_t timeout; /* Command timeout. */ + + uint16_t dseg_count; /* Data segment count. */ + uint16_t scsi_rsp_dsd_len; + + struct scsi_lun lun; /* LUN (LE). */ + + uint8_t cntrl_flags; + + uint8_t task_mgmt_flags; /* Task management flags. */ + + uint8_t task; + + uint8_t crn; + + uint8_t fcp_cdb[MAX_CMDSZ]; /* SCSI command words. */ + uint32_t byte_count; /* Total byte count. */ + + uint32_t dseg_0_address[2]; /* Data segment 0 address. */ + uint32_t dseg_0_len; /* Data segment 0 length. */ +}; + +/* + * ISP queue - marker entry structure definition. + */ +struct mrk_entry_fx00 { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t handle_count; /* Handle count. */ + uint8_t entry_status; /* Entry Status. */ + + uint32_t handle; /* System handle. */ + uint32_t handle_hi; /* System handle. */ + + uint16_t tgt_id; /* Target ID. */ + + uint8_t modifier; /* Modifier (7-0). */ + uint8_t reserved_1; + + uint8_t reserved_2[5]; + + uint8_t lun[8]; /* FCP LUN (BE). */ + uint8_t reserved_3[36]; +}; + + +#define STATUS_TYPE_FX00 0x01 /* Status entry. */ +struct sts_entry_fx00 { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + + uint32_t handle; /* System handle. */ + uint32_t handle_hi; /* System handle. */ + + uint16_t comp_status; /* Completion status. */ + uint16_t reserved_0; /* OX_ID used by the firmware. */ + + uint32_t residual_len; /* FW calc residual transfer length. */ + + uint16_t reserved_1; + uint16_t state_flags; /* State flags. */ + + uint16_t reserved_2; + uint16_t scsi_status; /* SCSI status. */ + + uint32_t sense_len; /* FCP SENSE length. */ + uint8_t data[32]; /* FCP response/sense information. */ +}; + + +#define MAX_HANDLE_COUNT 15 +#define MULTI_STATUS_TYPE_FX00 0x0D + +struct multi_sts_entry_fx00 { + uint8_t entry_type; /* Entry type. */ + uint8_t sys_define; /* System defined. */ + uint8_t handle_count; + uint8_t entry_status; + + uint32_t handles[MAX_HANDLE_COUNT]; +}; + +#define TSK_MGMT_IOCB_TYPE_FX00 0x05 +struct tsk_mgmt_entry_fx00 { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; + uint8_t entry_status; /* Entry Status. */ + + uint32_t handle; /* System handle. */ + + uint32_t handle_hi; /* System handle. */ + + uint16_t tgt_id; /* Target Idx. */ + + uint16_t reserved_1; + + uint16_t delay; /* Activity delay in seconds. */ + + uint16_t timeout; /* Command timeout. */ + + struct scsi_lun lun; /* LUN (LE). */ + + uint32_t control_flags; /* Control Flags. */ + + uint8_t reserved_2[32]; +}; + + +#define ABORT_IOCB_TYPE_FX00 0x08 /* Abort IOCB status. */ +struct abort_iocb_entry_fx00 { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + + uint32_t handle; /* System handle. */ + uint32_t handle_hi; /* System handle. */ + + uint16_t tgt_id_sts; /* Completion status. */ + uint16_t options; + + uint32_t abort_handle; /* System handle. */ + uint32_t abort_handle_hi; /* System handle. */ + + uint16_t req_que_no; + uint8_t reserved_1[38]; +}; + +#define IOCTL_IOSB_TYPE_FX00 0x0C +struct ioctl_iocb_entry_fx00 { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System defined. */ + uint8_t entry_status; /* Entry Status. */ + + uint32_t handle; /* System handle. */ + uint32_t reserved_0; /* System handle. */ + + uint16_t comp_func_num; + uint16_t fw_iotcl_flags; + + uint32_t dataword_r; /* Data word returned */ + uint32_t adapid; /* Adapter ID */ + uint32_t adapid_hi; /* Adapter ID high */ + uint32_t reserved_1; + + uint32_t seq_no; + uint8_t reserved_2[20]; + uint32_t residuallen; + uint32_t status; +}; + +#define STATUS_CONT_TYPE_FX00 0x04 + +#define FX00_IOCB_TYPE 0x0B +struct fxdisc_entry_fx00 { + uint8_t entry_type; /* Entry type. */ + uint8_t entry_count; /* Entry count. */ + uint8_t sys_define; /* System Defined. */ + uint8_t entry_status; /* Entry Status. */ + + uint32_t handle; /* System handle. */ + uint32_t reserved_0; /* System handle. */ + + uint16_t func_num; + uint16_t req_xfrcnt; + uint16_t req_dsdcnt; + uint16_t rsp_xfrcnt; + uint16_t rsp_dsdcnt; + uint8_t flags; + uint8_t reserved_1; + + uint32_t dseg_rq_address[2]; /* Data segment 0 address. */ + uint32_t dseg_rq_len; /* Data segment 0 length. */ + uint32_t dseg_rsp_address[2]; /* Data segment 1 address. */ + uint32_t dseg_rsp_len; /* Data segment 1 length. */ + + uint32_t dataword; + uint32_t adapid; + uint32_t adapid_hi; + uint32_t dataword_extra; +}; + +struct qlafx00_tgt_node_info { + uint8_t tgt_node_wwpn[WWN_SIZE]; + uint8_t tgt_node_wwnn[WWN_SIZE]; + uint32_t tgt_node_state; + uint8_t reserved[128]; + uint32_t reserved_1[8]; + uint64_t reserved_2[4]; +} __packed; + +#define QLAFX00_TGT_NODE_INFO sizeof(struct qlafx00_tgt_node_info) + +#define QLAFX00_LINK_STATUS_DOWN 0x10 +#define QLAFX00_LINK_STATUS_UP 0x11 + +#define QLAFX00_PORT_SPEED_2G 0x2 +#define QLAFX00_PORT_SPEED_4G 0x4 +#define QLAFX00_PORT_SPEED_8G 0x8 +#define QLAFX00_PORT_SPEED_10G 0xa +struct port_info_data { + uint8_t port_state; + uint8_t port_type; + uint16_t port_identifier; + uint32_t up_port_state; + uint8_t fw_ver_num[32]; + uint8_t portal_attrib; + uint16_t host_option; + uint8_t reset_delay; + uint8_t pdwn_retry_cnt; + uint16_t max_luns2tgt; + uint8_t risc_ver; + uint8_t pconn_option; + uint16_t risc_option; + uint16_t max_frame_len; + uint16_t max_iocb_alloc; + uint16_t exec_throttle; + uint8_t retry_cnt; + uint8_t retry_delay; + uint8_t port_name[8]; + uint8_t port_id[3]; + uint8_t link_status; + uint8_t plink_rate; + uint32_t link_config; + uint16_t adap_haddr; + uint8_t tgt_disc; + uint8_t log_tout; + uint8_t node_name[8]; + uint16_t erisc_opt1; + uint8_t resp_acc_tmr; + uint8_t intr_del_tmr; + uint8_t erisc_opt2; + uint8_t alt_port_name[8]; + uint8_t alt_node_name[8]; + uint8_t link_down_tout; + uint8_t conn_type; + uint8_t fc_fw_mode; + uint32_t uiReserved[48]; +} __packed; + +/* OS Type Designations */ +#define OS_TYPE_UNKNOWN 0 +#define OS_TYPE_LINUX 2 + +/* Linux Info */ +#define SYSNAME_LENGTH 128 +#define NODENAME_LENGTH 64 +#define RELEASE_LENGTH 64 +#define VERSION_LENGTH 64 +#define MACHINE_LENGTH 64 +#define DOMNAME_LENGTH 64 + +struct host_system_info { + uint32_t os_type; + char sysname[SYSNAME_LENGTH]; + char nodename[NODENAME_LENGTH]; + char release[RELEASE_LENGTH]; + char version[VERSION_LENGTH]; + char machine[MACHINE_LENGTH]; + char domainname[DOMNAME_LENGTH]; + char hostdriver[VERSION_LENGTH]; + uint32_t reserved[64]; +} __packed; + +struct register_host_info { + struct host_system_info hsi; /* host system info */ + uint64_t utc; /* UTC (system time) */ + uint32_t reserved[64]; /* future additions */ +} __packed; + + +#define QLAFX00_PORT_DATA_INFO (sizeof(struct port_info_data)) +#define QLAFX00_TGT_NODE_LIST_SIZE (sizeof(uint32_t) * 32) + +struct config_info_data { + uint8_t product_name[256]; + uint8_t symbolic_name[64]; + uint8_t serial_num[32]; + uint8_t hw_version[16]; + uint8_t fw_version[16]; + uint8_t uboot_version[16]; + uint8_t fru_serial_num[32]; + + uint8_t fc_port_count; + uint8_t iscsi_port_count; + uint8_t reserved1[2]; + + uint8_t mode; + uint8_t log_level; + uint8_t reserved2[2]; + + uint32_t log_size; + + uint8_t tgt_pres_mode; + uint8_t iqn_flags; + uint8_t lun_mapping; + + uint64_t adapter_id; + + uint32_t cluster_key_len; + uint8_t cluster_key[10]; + + uint64_t cluster_master_id; + uint64_t cluster_slave_id; + uint8_t cluster_flags; +} __packed; + +#define FXDISC_GET_CONFIG_INFO 0x01 +#define FXDISC_GET_PORT_INFO 0x02 +#define FXDISC_GET_TGT_NODE_INFO 0x80 +#define FXDISC_GET_TGT_NODE_LIST 0x81 +#define FXDISC_REG_HOST_INFO 0x99 + +#define QLAFX00_HBA_ICNTRL_REG 0x21B08 +#define QLAFX00_ICR_ENB_MASK 0x80000000 +#define QLAFX00_ICR_DIS_MASK 0x7fffffff +#define QLAFX00_HST_RST_REG 0x18264 +#define QLAFX00_HST_TO_HBA_REG 0x20A04 +#define QLAFX00_HBA_TO_HOST_REG 0x21B70 +#define QLAFX00_HST_INT_STS_BITS 0x7 +#define QLAFX00_BAR1_BASE_ADDR_REG 0x40018 +#define QLAFX00_PEX0_WIN0_BASE_ADDR_REG 0x41824 + +#define QLAFX00_INTR_MB_CMPLT 0x1 +#define QLAFX00_INTR_RSP_CMPLT 0x2 +#define QLAFX00_INTR_MB_RSP_CMPLT 0x3 +#define QLAFX00_INTR_ASYNC_CMPLT 0x4 +#define QLAFX00_INTR_MB_ASYNC_CMPLT 0x5 +#define QLAFX00_INTR_RSP_ASYNC_CMPLT 0x6 +#define QLAFX00_INTR_ALL_CMPLT 0x7 + +#define QLAFX00_MBA_SYSTEM_ERR 0x8002 +#define QLAFX00_MBA_LINK_UP 0x8011 +#define QLAFX00_MBA_LINK_DOWN 0x8012 +#define QLAFX00_MBA_PORT_UPDATE 0x8014 +#define QLAFX00_MBA_SHUTDOWN_RQSTD 0x8062 + +#define SOC_SW_RST_CONTROL_REG_CORE0 0x0020800 +#define SOC_FABRIC_RST_CONTROL_REG 0x0020840 +#define SOC_FABRIC_CONTROL_REG 0x0020200 +#define SOC_FABRIC_CONFIG_REG 0x0020204 + +#define SOC_INTERRUPT_SOURCE_I_CONTROL_REG 0x0020B00 +#define SOC_CORE_TIMER_REG 0x0021850 +#define SOC_IRQ_ACK_REG 0x00218b4 + +#define CONTINUE_A64_TYPE_FX00 0x03 /* Continuation entry. */ + +#define QLAFX00_SET_HST_INTR(ha, value) \ + WRT_REG_DWORD((ha)->cregbase + QLAFX00_HST_TO_HBA_REG, \ + value) + +#define QLAFX00_CLR_HST_INTR(ha, value) \ + WRT_REG_DWORD((ha)->cregbase + QLAFX00_HBA_TO_HOST_REG, \ + ~value) + +#define QLAFX00_RD_INTR_REG(ha) \ + RD_REG_DWORD((ha)->cregbase + QLAFX00_HBA_TO_HOST_REG) + +#define QLAFX00_CLR_INTR_REG(ha, value) \ + WRT_REG_DWORD((ha)->cregbase + QLAFX00_HBA_TO_HOST_REG, \ + ~value) + +#define QLAFX00_SET_HBA_SOC_REG(ha, off, val)\ + WRT_REG_DWORD((ha)->cregbase + off, val) + +#define QLAFX00_GET_HBA_SOC_REG(ha, off)\ + RD_REG_DWORD((ha)->cregbase + off) + +#define QLAFX00_HBA_RST_REG(ha, val)\ + WRT_REG_DWORD((ha)->cregbase + QLAFX00_HST_RST_REG, val) + +#define QLAFX00_RD_ICNTRL_REG(ha) \ + RD_REG_DWORD((ha)->cregbase + QLAFX00_HBA_ICNTRL_REG) + +#define QLAFX00_ENABLE_ICNTRL_REG(ha) \ + WRT_REG_DWORD((ha)->cregbase + QLAFX00_HBA_ICNTRL_REG, \ + (QLAFX00_GET_HBA_SOC_REG(ha, QLAFX00_HBA_ICNTRL_REG) | \ + QLAFX00_ICR_ENB_MASK)) + +#define QLAFX00_DISABLE_ICNTRL_REG(ha) \ + WRT_REG_DWORD((ha)->cregbase + QLAFX00_HBA_ICNTRL_REG, \ + (QLAFX00_GET_HBA_SOC_REG(ha, QLAFX00_HBA_ICNTRL_REG) & \ + QLAFX00_ICR_DIS_MASK)) + +#define QLAFX00_RD_REG(ha, off) \ + RD_REG_DWORD((ha)->cregbase + off) + +#define QLAFX00_WR_REG(ha, off, val) \ + WRT_REG_DWORD((ha)->cregbase + off, val) + +struct qla_mt_iocb_rqst_fx00 { + uint32_t reserved_0; + + uint16_t func_type; + uint8_t flags; + uint8_t reserved_1; + + uint32_t dataword; + + uint32_t adapid; + uint32_t adapid_hi; + + uint32_t dataword_extra; + + uint32_t req_len; + + uint32_t rsp_len; +}; + +struct qla_mt_iocb_rsp_fx00 { + uint32_t reserved_1; + + uint16_t func_type; + uint16_t ioctl_flags; + + uint32_t ioctl_data; + + uint32_t adapid; + uint32_t adapid_hi; + + uint32_t reserved_2; + uint32_t seq_number; + + uint8_t reserved_3[20]; + + int32_t res_count; + + uint32_t status; +}; + + +#define MAILBOX_REGISTER_COUNT_FX00 16 +#define AEN_MAILBOX_REGISTER_COUNT_FX00 8 +#define MAX_FIBRE_DEVICES_FX00 512 +#define MAX_LUNS_FX00 0x1024 +#define MAX_TARGETS_FX00 MAX_ISA_DEVICES +#define REQUEST_ENTRY_CNT_FX00 512 /* Number of request entries. */ +#define RESPONSE_ENTRY_CNT_FX00 256 /* Number of response entries.*/ + +/* + * Firmware state codes for QLAFX00 adapters + */ +#define FSTATE_FX00_CONFIG_WAIT 0x0000 /* Waiting for driver to issue + * Initialize FW Mbox cmd + */ +#define FSTATE_FX00_INITIALIZED 0x1000 /* FW has been initialized by + * the driver + */ + +#define FX00_DEF_RATOV 10 + +struct mr_data_fx00 { + uint8_t product_name[256]; + uint8_t symbolic_name[64]; + uint8_t serial_num[32]; + uint8_t hw_version[16]; + uint8_t fw_version[16]; + uint8_t uboot_version[16]; + uint8_t fru_serial_num[32]; + fc_port_t fcport; /* fcport used for requests + * that are not linked + * to a particular target + */ + uint8_t fw_hbt_en; + uint8_t fw_hbt_cnt; + uint8_t fw_hbt_miss_cnt; + uint32_t old_fw_hbt_cnt; + uint16_t fw_reset_timer_tick; + uint8_t fw_reset_timer_exp; + uint32_t old_aenmbx0_state; +}; + +#define QLAFX00_LOOP_DOWN_TIME 615 /* 600 */ +#define QLAFX00_HEARTBEAT_INTERVAL 6 /* number of seconds */ +#define QLAFX00_HEARTBEAT_MISS_CNT 3 /* number of miss */ +#define QLAFX00_RESET_INTERVAL 120 /* number of seconds */ +#define QLAFX00_MAX_RESET_INTERVAL 600 /* number of seconds */ +#endif diff --git a/drivers/scsi/qla2xxx/qla_os.c b/drivers/scsi/qla2xxx/qla_os.c index 2c6dd3dfe0f..a083715843b 100644 --- a/drivers/scsi/qla2xxx/qla_os.c +++ b/drivers/scsi/qla2xxx/qla_os.c @@ -47,6 +47,7 @@ MODULE_PARM_DESC(ql2xenableclass2, "Specify if Class 2 operations are supported from the very " "beginning. Default is 0 - class 2 not supported."); + int ql2xlogintimeout = 20; module_param(ql2xlogintimeout, int, S_IRUGO); MODULE_PARM_DESC(ql2xlogintimeout, @@ -354,7 +355,12 @@ fail_req_map: static void qla2x00_free_req_que(struct qla_hw_data *ha, struct req_que *req) { - if (req && req->ring) + if (IS_QLAFX00(ha)) { + if (req && req->ring_fx00) + dma_free_coherent(&ha->pdev->dev, + (req->length_fx00 + 1) * sizeof(request_t), + req->ring_fx00, req->dma_fx00); + } else if (req && req->ring) dma_free_coherent(&ha->pdev->dev, (req->length + 1) * sizeof(request_t), req->ring, req->dma); @@ -368,11 +374,16 @@ static void qla2x00_free_req_que(struct qla_hw_data *ha, struct req_que *req) static void qla2x00_free_rsp_que(struct qla_hw_data *ha, struct rsp_que *rsp) { - if (rsp && rsp->ring) + if (IS_QLAFX00(ha)) { + if (rsp && rsp->ring) + dma_free_coherent(&ha->pdev->dev, + (rsp->length_fx00 + 1) * sizeof(request_t), + rsp->ring_fx00, rsp->dma_fx00); + } else if (rsp && rsp->ring) { dma_free_coherent(&ha->pdev->dev, (rsp->length + 1) * sizeof(response_t), rsp->ring, rsp->dma); - + } kfree(rsp); rsp = NULL; } @@ -633,7 +644,7 @@ qla2x00_sp_free_dma(void *vha, void *ptr) qla2x00_rel_sp(sp->fcport->vha, sp); } -static void +void qla2x00_sp_compl(void *data, void *ptr, int res) { struct qla_hw_data *ha = (struct qla_hw_data *)data; @@ -657,6 +668,9 @@ qla2x00_sp_compl(void *data, void *ptr, int res) cmd->scsi_done(cmd); } +/* If we are SP1 here, we need to still take and release the host_lock as SP1 + * does not have the changes necessary to avoid taking host->host_lock. + */ static int qla2xxx_queuecommand(struct Scsi_Host *host, struct scsi_cmnd *cmd) { @@ -1304,6 +1318,9 @@ qla2x00_loop_reset(scsi_qla_host_t *vha) } } + if (IS_QLAFX00(ha)) + return QLA_SUCCESS; + if (ha->flags.enable_lip_full_login && !IS_CNA_CAPABLE(ha)) { atomic_set(&vha->loop_state, LOOP_DOWN); atomic_set(&vha->loop_down_timer, LOOP_DOWN_TIME); @@ -1858,6 +1875,7 @@ static struct isp_operations qla2100_isp_ops = { .start_scsi = qla2x00_start_scsi, .abort_isp = qla2x00_abort_isp, .iospace_config = qla2x00_iospace_config, + .initialize_adapter = qla2x00_initialize_adapter, }; static struct isp_operations qla2300_isp_ops = { @@ -1895,6 +1913,7 @@ static struct isp_operations qla2300_isp_ops = { .start_scsi = qla2x00_start_scsi, .abort_isp = qla2x00_abort_isp, .iospace_config = qla2x00_iospace_config, + .initialize_adapter = qla2x00_initialize_adapter, }; static struct isp_operations qla24xx_isp_ops = { @@ -1932,6 +1951,7 @@ static struct isp_operations qla24xx_isp_ops = { .start_scsi = qla24xx_start_scsi, .abort_isp = qla2x00_abort_isp, .iospace_config = qla2x00_iospace_config, + .initialize_adapter = qla2x00_initialize_adapter, }; static struct isp_operations qla25xx_isp_ops = { @@ -1969,6 +1989,7 @@ static struct isp_operations qla25xx_isp_ops = { .start_scsi = qla24xx_dif_start_scsi, .abort_isp = qla2x00_abort_isp, .iospace_config = qla2x00_iospace_config, + .initialize_adapter = qla2x00_initialize_adapter, }; static struct isp_operations qla81xx_isp_ops = { @@ -2006,6 +2027,7 @@ static struct isp_operations qla81xx_isp_ops = { .start_scsi = qla24xx_dif_start_scsi, .abort_isp = qla2x00_abort_isp, .iospace_config = qla2x00_iospace_config, + .initialize_adapter = qla2x00_initialize_adapter, }; static struct isp_operations qla82xx_isp_ops = { @@ -2043,6 +2065,7 @@ static struct isp_operations qla82xx_isp_ops = { .start_scsi = qla82xx_start_scsi, .abort_isp = qla82xx_abort_isp, .iospace_config = qla82xx_iospace_config, + .initialize_adapter = qla2x00_initialize_adapter, }; static struct isp_operations qla83xx_isp_ops = { @@ -2080,6 +2103,45 @@ static struct isp_operations qla83xx_isp_ops = { .start_scsi = qla24xx_dif_start_scsi, .abort_isp = qla2x00_abort_isp, .iospace_config = qla83xx_iospace_config, + .initialize_adapter = qla2x00_initialize_adapter, +}; + +static struct isp_operations qlafx00_isp_ops = { + .pci_config = qlafx00_pci_config, + .reset_chip = qlafx00_soft_reset, + .chip_diag = qlafx00_chip_diag, + .config_rings = qlafx00_config_rings, + .reset_adapter = qlafx00_soft_reset, + .nvram_config = NULL, + .update_fw_options = NULL, + .load_risc = NULL, + .pci_info_str = qlafx00_pci_info_str, + .fw_version_str = qlafx00_fw_version_str, + .intr_handler = qlafx00_intr_handler, + .enable_intrs = qlafx00_enable_intrs, + .disable_intrs = qlafx00_disable_intrs, + .abort_command = qlafx00_abort_command, + .target_reset = qlafx00_abort_target, + .lun_reset = qlafx00_lun_reset, + .fabric_login = NULL, + .fabric_logout = NULL, + .calc_req_entries = NULL, + .build_iocbs = NULL, + .prep_ms_iocb = qla24xx_prep_ms_iocb, + .prep_ms_fdmi_iocb = qla24xx_prep_ms_fdmi_iocb, + .read_nvram = qla24xx_read_nvram_data, + .write_nvram = qla24xx_write_nvram_data, + .fw_dump = NULL, + .beacon_on = qla24xx_beacon_on, + .beacon_off = qla24xx_beacon_off, + .beacon_blink = NULL, + .read_optrom = qla24xx_read_optrom_data, + .write_optrom = qla24xx_write_optrom_data, + .get_flash_version = qla24xx_get_flash_version, + .start_scsi = qlafx00_start_scsi, + .abort_isp = qlafx00_abort_isp, + .iospace_config = qlafx00_iospace_config, + .initialize_adapter = qlafx00_initialize_adapter, }; static inline void @@ -2192,6 +2254,9 @@ qla2x00_set_isp_flags(struct qla_hw_data *ha) ha->device_type |= DT_T10_PI; ha->fw_srisc_address = RISC_START_ADDRESS_2400; break; + case PCI_DEVICE_ID_QLOGIC_ISPF001: + ha->device_type |= DT_ISPFX00; + break; } if (IS_QLA82XX(ha)) @@ -2265,7 +2330,8 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) pdev->device == PCI_DEVICE_ID_QLOGIC_ISP8001 || pdev->device == PCI_DEVICE_ID_QLOGIC_ISP8021 || pdev->device == PCI_DEVICE_ID_QLOGIC_ISP2031 || - pdev->device == PCI_DEVICE_ID_QLOGIC_ISP8031) { + pdev->device == PCI_DEVICE_ID_QLOGIC_ISP8031 || + pdev->device == PCI_DEVICE_ID_QLOGIC_ISPF001) { bars = pci_select_bars(pdev, IORESOURCE_MEM); mem_only = 1; ql_dbg_pci(ql_dbg_init, pdev, 0x0007, @@ -2436,6 +2502,18 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) ha->flash_data_off = FARX_ACCESS_FLASH_DATA_81XX; ha->nvram_conf_off = ~0; ha->nvram_data_off = ~0; + } else if (IS_QLAFX00(ha)) { + ha->max_fibre_devices = MAX_FIBRE_DEVICES_FX00; + ha->mbx_count = MAILBOX_REGISTER_COUNT_FX00; + ha->aen_mbx_count = AEN_MAILBOX_REGISTER_COUNT_FX00; + req_length = REQUEST_ENTRY_CNT_FX00; + rsp_length = RESPONSE_ENTRY_CNT_FX00; + ha->init_cb_size = sizeof(struct init_cb_fx); + ha->isp_ops = &qlafx00_isp_ops; + ha->port_down_retry_count = 30; /* default value */ + ha->mr.fw_hbt_cnt = QLAFX00_HEARTBEAT_INTERVAL; + ha->mr.fw_reset_timer_tick = QLAFX00_RESET_INTERVAL; + ha->mr.fw_hbt_en = 1; } ql_dbg_pci(ql_dbg_init, pdev, 0x001e, @@ -2500,13 +2578,24 @@ qla2x00_probe_one(struct pci_dev *pdev, const struct pci_device_id *id) host = base_vha->host; base_vha->req = req; - host->can_queue = req->length + 128; + if (IS_QLAFX00(ha)) + host->can_queue = 1024; + else + host->can_queue = req->length + 128; if (IS_QLA2XXX_MIDTYPE(ha)) base_vha->mgmt_svr_loop_id = 10 + base_vha->vp_idx; else base_vha->mgmt_svr_loop_id = MANAGEMENT_SERVER + base_vha->vp_idx; + /* Setup fcport template structure. */ + ha->mr.fcport.vha = base_vha; + ha->mr.fcport.port_type = FCT_UNKNOWN; + ha->mr.fcport.loop_id = FC_NO_LOOP_ID; + qla2x00_set_fcport_state(&ha->mr.fcport, FCS_UNCONFIGURED); + ha->mr.fcport.supported_classes = FC_COS_UNSPECIFIED; + ha->mr.fcport.scan_state = 1; + /* Set the SG table size based on ISP type */ if (!IS_FWI2_CAPABLE(ha)) { if (IS_QLA2100(ha)) @@ -2562,6 +2651,13 @@ que_init: rsp->req = req; req->rsp = rsp; + if (IS_QLAFX00(ha)) { + ha->rsp_q_map[0] = rsp; + ha->req_q_map[0] = req; + set_bit(0, ha->req_qid_map); + set_bit(0, ha->rsp_qid_map); + } + /* FWI2-capable only. */ req->req_q_in = &ha->iobase->isp24.req_q_in; req->req_q_out = &ha->iobase->isp24.req_q_out; @@ -2574,6 +2670,13 @@ que_init: rsp->rsp_q_out = &ha->mqiobase->isp25mq.rsp_q_out; } + if (IS_QLAFX00(ha)) { + req->req_q_in = &ha->iobase->ispfx00.req_q_in; + req->req_q_out = &ha->iobase->ispfx00.req_q_out; + rsp->rsp_q_in = &ha->iobase->ispfx00.rsp_q_in; + rsp->rsp_q_out = &ha->iobase->ispfx00.rsp_q_out; + } + if (IS_QLA82XX(ha)) { req->req_q_out = &ha->iobase->isp82.req_q_out[0]; rsp->rsp_q_in = &ha->iobase->isp82.rsp_q_in[0]; @@ -2595,7 +2698,7 @@ que_init: "req->req_q_in=%p req->req_q_out=%p rsp->rsp_q_in=%p rsp->rsp_q_out=%p.\n", req->req_q_in, req->req_q_out, rsp->rsp_q_in, rsp->rsp_q_out); - if (qla2x00_initialize_adapter(base_vha)) { + if (ha->isp_ops->initialize_adapter(base_vha)) { ql_log(ql_log_fatal, base_vha, 0x00d6, "Failed to initialize adapter - Adapter flags %x.\n", base_vha->device_flags); @@ -2720,6 +2823,18 @@ skip_dpc: qla2x00_alloc_sysfs_attr(base_vha); + if (IS_QLAFX00(ha)) { + ret = qlafx00_fx_disc(base_vha, + &base_vha->hw->mr.fcport, FXDISC_GET_CONFIG_INFO); + + ret = qlafx00_fx_disc(base_vha, + &base_vha->hw->mr.fcport, FXDISC_GET_PORT_INFO); + + /* Register system information */ + ret = qlafx00_fx_disc(base_vha, + &base_vha->hw->mr.fcport, FXDISC_REG_HOST_INFO); + } + qla2x00_init_host_attr(base_vha); qla2x00_dfs_setup(base_vha); @@ -2777,6 +2892,8 @@ iospace_config_failed: } else { if (ha->iobase) iounmap(ha->iobase); + if (ha->cregbase) + iounmap(ha->cregbase); } pci_release_selected_regions(ha->pdev, ha->bars); kfree(ha); @@ -2960,6 +3077,9 @@ qla2x00_remove_one(struct pci_dev *pdev) if (ha->iobase) iounmap(ha->iobase); + if (ha->cregbase) + iounmap(ha->cregbase); + if (ha->mqiobase) iounmap(ha->mqiobase); @@ -3068,6 +3188,12 @@ qla2x00_schedule_rport_del(struct scsi_qla_host *vha, fc_port_t *fcport, void qla2x00_mark_device_lost(scsi_qla_host_t *vha, fc_port_t *fcport, int do_login, int defer) { + if (IS_QLAFX00(vha->hw)) { + qla2x00_set_fcport_state(fcport, FCS_DEVICE_LOST); + qla2x00_schedule_rport_del(vha, fcport, defer); + return; + } + if (atomic_read(&fcport->state) == FCS_ONLINE && vha->vp_idx == fcport->vha->vp_idx) { qla2x00_set_fcport_state(fcport, FCS_DEVICE_LOST); @@ -3710,6 +3836,22 @@ qla2x00_uevent_emit(struct scsi_qla_host *vha, u32 code) kobject_uevent_env(&vha->hw->pdev->dev.kobj, KOBJ_CHANGE, envp); } +int +qlafx00_post_aenfx_work(struct scsi_qla_host *vha, uint32_t evtcode, + uint32_t *data, int cnt) +{ + struct qla_work_evt *e; + + e = qla2x00_alloc_work(vha, QLA_EVT_AENFX); + if (!e) + return QLA_FUNCTION_FAILED; + + e->u.aenfx.evtcode = evtcode; + e->u.aenfx.count = cnt; + memcpy(e->u.aenfx.mbx, data, sizeof(*data) * cnt); + return qla2x00_post_work(vha, e); +} + void qla2x00_do_work(struct scsi_qla_host *vha) { @@ -3758,6 +3900,9 @@ qla2x00_do_work(struct scsi_qla_host *vha) case QLA_EVT_UEVENT: qla2x00_uevent_emit(vha, e->u.uevent.code); break; + case QLA_EVT_AENFX: + qlafx00_process_aen(vha, e); + break; } if (e->flags & QLA_EVT_FLAG_FREE) kfree(e); @@ -4592,6 +4737,38 @@ qla2x00_do_dpc(void *data) ql_dbg(ql_dbg_dpc, base_vha, 0x4006, "FCoE context reset end.\n"); } + } else if (IS_QLAFX00(ha)) { + if (test_and_clear_bit(ISP_UNRECOVERABLE, + &base_vha->dpc_flags)) { + ql_dbg(ql_dbg_dpc, base_vha, 0x4020, + "Firmware Reset Recovery\n"); + if (qlafx00_reset_initialize(base_vha)) { + /* Failed. Abort isp later. */ + if (!test_bit(UNLOADING, + &base_vha->dpc_flags)) + set_bit(ISP_UNRECOVERABLE, + &base_vha->dpc_flags); + ql_dbg(ql_dbg_dpc, base_vha, + 0x4021, + "Reset Recovery Failed\n"); + } + } + + if (test_and_clear_bit(FX00_TARGET_SCAN, + &base_vha->dpc_flags)) { + ql_dbg(ql_dbg_dpc, base_vha, 0x4022, + "ISPFx00 Target Scan scheduled\n"); + if (qlafx00_rescan_isp(base_vha)) { + if (!test_bit(UNLOADING, + &base_vha->dpc_flags)) + set_bit(ISP_UNRECOVERABLE, + &base_vha->dpc_flags); + ql_dbg(ql_dbg_dpc, base_vha, 0x401e, + "ISPFx00 Target Scan Failed\n"); + } + ql_dbg(ql_dbg_dpc, base_vha, 0x401f, + "ISPFx00 Target Scan End\n"); + } } if (test_and_clear_bit(ISP_ABORT_NEEDED, @@ -4630,6 +4807,9 @@ qla2x00_do_dpc(void *data) clear_bit(SCR_PENDING, &base_vha->dpc_flags); } + if (IS_QLAFX00(ha)) + goto loop_resync_check; + if (test_bit(ISP_QUIESCE_NEEDED, &base_vha->dpc_flags)) { ql_dbg(ql_dbg_dpc, base_vha, 0x4009, "Quiescence mode scheduled.\n"); @@ -4654,7 +4834,7 @@ qla2x00_do_dpc(void *data) } if (test_and_clear_bit(RESET_MARKER_NEEDED, - &base_vha->dpc_flags) && + &base_vha->dpc_flags) && (!(test_and_set_bit(RESET_ACTIVE, &base_vha->dpc_flags)))) { ql_dbg(ql_dbg_dpc, base_vha, 0x400b, @@ -4677,9 +4857,9 @@ qla2x00_do_dpc(void *data) ql_dbg(ql_dbg_dpc, base_vha, 0x400e, "Relogin end.\n"); } - +loop_resync_check: if (test_and_clear_bit(LOOP_RESYNC_NEEDED, - &base_vha->dpc_flags)) { + &base_vha->dpc_flags)) { ql_dbg(ql_dbg_dpc, base_vha, 0x400f, "Loop resync scheduled.\n"); @@ -4697,6 +4877,9 @@ qla2x00_do_dpc(void *data) "Loop resync end.\n"); } + if (IS_QLAFX00(ha)) + goto intr_on_check; + if (test_bit(NPIV_CONFIG_NEEDED, &base_vha->dpc_flags) && atomic_read(&base_vha->loop_state) == LOOP_READY) { clear_bit(NPIV_CONFIG_NEEDED, &base_vha->dpc_flags); @@ -4714,7 +4897,7 @@ qla2x00_do_dpc(void *data) if (test_and_clear_bit(HOST_RAMP_UP_QUEUE_DEPTH, &base_vha->dpc_flags)) qla2x00_host_ramp_up_queuedepth(base_vha); - +intr_on_check: if (!ha->interrupts_on) ha->isp_ops->enable_intrs(ha); @@ -4722,7 +4905,8 @@ qla2x00_do_dpc(void *data) &base_vha->dpc_flags)) ha->isp_ops->beacon_blink(base_vha); - qla2x00_do_dpc_all_vps(base_vha); + if (!IS_QLAFX00(ha)) + qla2x00_do_dpc_all_vps(base_vha); ha->dpc_active = 0; end_loop: @@ -4818,6 +5002,9 @@ qla2x00_timer(scsi_qla_host_t *vha) qla82xx_watchdog(vha); } + if (!vha->vp_idx && IS_QLAFX00(ha)) + qlafx00_timer_routine(vha); + /* Loop down handler. */ if (atomic_read(&vha->loop_down_timer) > 0 && !(test_bit(ABORT_ISP_ACTIVE, &vha->dpc_flags)) && @@ -5335,6 +5522,7 @@ static struct pci_device_id qla2xxx_pci_tbl[] = { { PCI_DEVICE(PCI_VENDOR_ID_QLOGIC, PCI_DEVICE_ID_QLOGIC_ISP8001) }, { PCI_DEVICE(PCI_VENDOR_ID_QLOGIC, PCI_DEVICE_ID_QLOGIC_ISP8021) }, { PCI_DEVICE(PCI_VENDOR_ID_QLOGIC, PCI_DEVICE_ID_QLOGIC_ISP8031) }, + { PCI_DEVICE(PCI_VENDOR_ID_QLOGIC, PCI_DEVICE_ID_QLOGIC_ISPF001) }, { 0 }, }; MODULE_DEVICE_TABLE(pci, qla2xxx_pci_tbl); diff --git a/drivers/scsi/qla2xxx/qla_target.c b/drivers/scsi/qla2xxx/qla_target.c index 61b5d8c2b5d..fcdc22306ca 100644 --- a/drivers/scsi/qla2xxx/qla_target.c +++ b/drivers/scsi/qla2xxx/qla_target.c @@ -2585,25 +2585,6 @@ static void qlt_do_ctio_completion(struct scsi_qla_host *vha, uint32_t handle, ha->tgt.tgt_ops->free_cmd(cmd); } -/* ha->hardware_lock supposed to be held on entry */ -/* called via callback from qla2xxx */ -void qlt_ctio_completion(struct scsi_qla_host *vha, uint32_t handle) -{ - struct qla_hw_data *ha = vha->hw; - struct qla_tgt *tgt = ha->tgt.qla_tgt; - - if (likely(tgt == NULL)) { - ql_dbg(ql_dbg_tgt, vha, 0xe021, - "CTIO, but target mode not enabled" - " (ha %d %p handle %#x)", vha->vp_idx, ha, handle); - return; - } - - tgt->irq_cmd_count++; - qlt_do_ctio_completion(vha, handle, CTIO_SUCCESS, NULL); - tgt->irq_cmd_count--; -} - static inline int qlt_get_fcp_task_attr(struct scsi_qla_host *vha, uint8_t task_codes) { diff --git a/drivers/scsi/qla2xxx/qla_target.h b/drivers/scsi/qla2xxx/qla_target.h index ff9ccb9fd03..b33e411f28a 100644 --- a/drivers/scsi/qla2xxx/qla_target.h +++ b/drivers/scsi/qla2xxx/qla_target.h @@ -980,7 +980,6 @@ extern int qlt_xmit_response(struct qla_tgt_cmd *, int, uint8_t); extern void qlt_xmit_tm_rsp(struct qla_tgt_mgmt_cmd *); extern void qlt_free_mcmd(struct qla_tgt_mgmt_cmd *); extern void qlt_free_cmd(struct qla_tgt_cmd *cmd); -extern void qlt_ctio_completion(struct scsi_qla_host *, uint32_t); extern void qlt_async_event(uint16_t, struct scsi_qla_host *, uint16_t *); extern void qlt_enable_vha(struct scsi_qla_host *); extern void qlt_vport_create(struct scsi_qla_host *, struct qla_hw_data *); diff --git a/drivers/scsi/qla2xxx/qla_version.h b/drivers/scsi/qla2xxx/qla_version.h index ec54036d1e1..6c66d22eb1b 100644 --- a/drivers/scsi/qla2xxx/qla_version.h +++ b/drivers/scsi/qla2xxx/qla_version.h @@ -7,9 +7,9 @@ /* * Driver version */ -#define QLA2XXX_VERSION "8.04.00.13-k" +#define QLA2XXX_VERSION "8.05.00.03-k" #define QLA_DRIVER_MAJOR_VER 8 -#define QLA_DRIVER_MINOR_VER 4 +#define QLA_DRIVER_MINOR_VER 5 #define QLA_DRIVER_PATCH_VER 0 #define QLA_DRIVER_BETA_VER 0 diff --git a/drivers/scsi/qla4xxx/ql4_83xx.c b/drivers/scsi/qla4xxx/ql4_83xx.c index 5d8fe4f7565..d607eb8e24c 100644 --- a/drivers/scsi/qla4xxx/ql4_83xx.c +++ b/drivers/scsi/qla4xxx/ql4_83xx.c @@ -1629,9 +1629,37 @@ static void __qla4_83xx_disable_pause(struct scsi_qla_host *ha) ql4_printk(KERN_INFO, ha, "Disabled pause frames successfully.\n"); } +/** + * qla4_83xx_eport_init - Initialize EPort. + * @ha: Pointer to host adapter structure. + * + * If EPort hardware is in reset state before disabling pause, there would be + * serious hardware wedging issues. To prevent this perform eport init everytime + * before disabling pause frames. + **/ +static void qla4_83xx_eport_init(struct scsi_qla_host *ha) +{ + /* Clear the 8 registers */ + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_REG, 0x0); + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_PORT0, 0x0); + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_PORT1, 0x0); + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_PORT2, 0x0); + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_PORT3, 0x0); + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_SRE_SHIM, 0x0); + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_EPG_SHIM, 0x0); + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_ETHER_PCS, 0x0); + + /* Write any value to Reset Control register */ + qla4_83xx_wr_reg_indirect(ha, QLA83XX_RESET_CONTROL, 0xFF); + + ql4_printk(KERN_INFO, ha, "EPORT is out of reset.\n"); +} + void qla4_83xx_disable_pause(struct scsi_qla_host *ha) { ha->isp_ops->idc_lock(ha); + /* Before disabling pause frames, ensure that eport is not in reset */ + qla4_83xx_eport_init(ha); qla4_83xx_dump_pause_control_regs(ha); __qla4_83xx_disable_pause(ha); ha->isp_ops->idc_unlock(ha); diff --git a/drivers/scsi/qla4xxx/ql4_83xx.h b/drivers/scsi/qla4xxx/ql4_83xx.h index 6a00f903f2a..fab237fa32c 100644 --- a/drivers/scsi/qla4xxx/ql4_83xx.h +++ b/drivers/scsi/qla4xxx/ql4_83xx.h @@ -55,6 +55,16 @@ #define QLA83XX_SET_PAUSE_VAL 0x0 #define QLA83XX_SET_TC_MAX_CELL_VAL 0x03FF03FF +#define QLA83XX_RESET_CONTROL 0x28084E50 +#define QLA83XX_RESET_REG 0x28084E60 +#define QLA83XX_RESET_PORT0 0x28084E70 +#define QLA83XX_RESET_PORT1 0x28084E80 +#define QLA83XX_RESET_PORT2 0x28084E90 +#define QLA83XX_RESET_PORT3 0x28084EA0 +#define QLA83XX_RESET_SRE_SHIM 0x28084EB0 +#define QLA83XX_RESET_EPG_SHIM 0x28084EC0 +#define QLA83XX_RESET_ETHER_PCS 0x28084ED0 + /* qla_83xx_reg_tbl registers */ #define QLA83XX_PEG_HALT_STATUS1 0x34A8 #define QLA83XX_PEG_HALT_STATUS2 0x34AC diff --git a/drivers/scsi/qla4xxx/ql4_dbg.h b/drivers/scsi/qla4xxx/ql4_dbg.h index 5b0afc18ef1..51c365bcf91 100644 --- a/drivers/scsi/qla4xxx/ql4_dbg.h +++ b/drivers/scsi/qla4xxx/ql4_dbg.h @@ -12,6 +12,7 @@ /* #define QL_DEBUG_LEVEL_3 */ /* Output function tracing */ /* #define QL_DEBUG_LEVEL_4 */ /* #define QL_DEBUG_LEVEL_5 */ +/* #define QL_DEBUG_LEVEL_7 */ /* #define QL_DEBUG_LEVEL_9 */ #define QL_DEBUG_LEVEL_2 /* ALways enable error messagess */ @@ -48,6 +49,12 @@ #define DEBUG5(x) do {} while (0); #endif /* */ +#if defined(QL_DEBUG_LEVEL_7) +#define DEBUG7(x) do {x; } while (0) +#else /* */ +#define DEBUG7(x) do {} while (0) +#endif /* */ + #if defined(QL_DEBUG_LEVEL_9) #define DEBUG9(x) do {x;} while (0); #else /* */ diff --git a/drivers/scsi/qla4xxx/ql4_def.h b/drivers/scsi/qla4xxx/ql4_def.h index 129f5dd0282..ddf16a86bbf 100644 --- a/drivers/scsi/qla4xxx/ql4_def.h +++ b/drivers/scsi/qla4xxx/ql4_def.h @@ -159,6 +159,22 @@ #define LSDW(x) ((u32)((u64)(x))) #define MSDW(x) ((u32)((((u64)(x)) >> 16) >> 16)) +#define DEV_DB_NON_PERSISTENT 0 +#define DEV_DB_PERSISTENT 1 + +#define COPY_ISID(dst_isid, src_isid) { \ + int i, j; \ + for (i = 0, j = ISID_SIZE - 1; i < ISID_SIZE;) \ + dst_isid[i++] = src_isid[j--]; \ +} + +#define SET_BITVAL(o, n, v) { \ + if (o) \ + n |= v; \ + else \ + n &= ~v; \ +} + /* * Retry & Timeout Values */ @@ -363,6 +379,8 @@ struct ql82xx_hw_data { uint32_t flt_iscsi_param; uint32_t flt_region_chap; uint32_t flt_chap_size; + uint32_t flt_region_ddb; + uint32_t flt_ddb_size; }; struct qla4_8xxx_legacy_intr_set { @@ -501,6 +519,7 @@ struct scsi_qla_host { #define AF_INIT_DONE 1 /* 0x00000002 */ #define AF_MBOX_COMMAND 2 /* 0x00000004 */ #define AF_MBOX_COMMAND_DONE 3 /* 0x00000008 */ +#define AF_ST_DISCOVERY_IN_PROGRESS 4 /* 0x00000010 */ #define AF_INTERRUPTS_ON 6 /* 0x00000040 */ #define AF_GET_CRASH_RECORD 7 /* 0x00000080 */ #define AF_LINK_UP 8 /* 0x00000100 */ diff --git a/drivers/scsi/qla4xxx/ql4_fw.h b/drivers/scsi/qla4xxx/ql4_fw.h index ad9d2e2d370..c7b8892b5a8 100644 --- a/drivers/scsi/qla4xxx/ql4_fw.h +++ b/drivers/scsi/qla4xxx/ql4_fw.h @@ -288,6 +288,8 @@ union external_hw_config_reg { #define FA_GOLD_RISC_CODE_ADDR_82 0x80000 #define FA_FLASH_ISCSI_CHAP 0x540000 #define FA_FLASH_CHAP_SIZE 0xC0000 +#define FA_FLASH_ISCSI_DDB 0x420000 +#define FA_FLASH_DDB_SIZE 0x080000 /* Flash Description Table */ struct qla_fdt_layout { @@ -348,6 +350,7 @@ struct qla_flt_header { #define FLT_REG_BOOT_CODE_82 0x78 #define FLT_REG_ISCSI_PARAM 0x65 #define FLT_REG_ISCSI_CHAP 0x63 +#define FLT_REG_ISCSI_DDB 0x6A struct qla_flt_region { uint32_t code; @@ -490,12 +493,16 @@ struct qla_flt_region { #define MBOX_ASTS_SUBNET_STATE_CHANGE 0x8027 #define MBOX_ASTS_RESPONSE_QUEUE_FULL 0x8028 #define MBOX_ASTS_IP_ADDR_STATE_CHANGED 0x8029 +#define MBOX_ASTS_IPV6_DEFAULT_ROUTER_CHANGED 0x802A #define MBOX_ASTS_IPV6_PREFIX_EXPIRED 0x802B #define MBOX_ASTS_IPV6_ND_PREFIX_IGNORED 0x802C #define MBOX_ASTS_IPV6_LCL_PREFIX_IGNORED 0x802D #define MBOX_ASTS_ICMPV6_ERROR_MSG_RCVD 0x802E +#define MBOX_ASTS_INITIALIZATION_FAILED 0x8031 +#define MBOX_ASTS_SYSTEM_WARNING_EVENT 0x8036 #define MBOX_ASTS_IDC_COMPLETE 0x8100 #define MBOX_ASTS_IDC_REQUEST_NOTIFICATION 0x8101 +#define MBOX_ASTS_DCBX_CONF_CHANGE 0x8110 #define MBOX_ASTS_TXSCVR_INSERTED 0x8130 #define MBOX_ASTS_TXSCVR_REMOVED 0x8131 @@ -779,12 +786,41 @@ struct dev_db_entry { #define DDB_OPT_IPV6_NULL_LINK_LOCAL 0x800 /* post connection */ #define DDB_OPT_IPV6_FW_DEFINED_LINK_LOCAL 0x800 /* pre connection */ +#define OPT_IS_FW_ASSIGNED_IPV6 11 +#define OPT_IPV6_DEVICE 8 +#define OPT_AUTO_SENDTGTS_DISABLE 6 +#define OPT_DISC_SESSION 4 +#define OPT_ENTRY_STATE 3 uint16_t exec_throttle; /* 02-03 */ uint16_t exec_count; /* 04-05 */ uint16_t res0; /* 06-07 */ uint16_t iscsi_options; /* 08-09 */ +#define ISCSIOPT_HEADER_DIGEST_EN 13 +#define ISCSIOPT_DATA_DIGEST_EN 12 +#define ISCSIOPT_IMMEDIATE_DATA_EN 11 +#define ISCSIOPT_INITIAL_R2T_EN 10 +#define ISCSIOPT_DATA_SEQ_IN_ORDER 9 +#define ISCSIOPT_DATA_PDU_IN_ORDER 8 +#define ISCSIOPT_CHAP_AUTH_EN 7 +#define ISCSIOPT_SNACK_REQ_EN 6 +#define ISCSIOPT_DISCOVERY_LOGOUT_EN 5 +#define ISCSIOPT_BIDI_CHAP_EN 4 +#define ISCSIOPT_DISCOVERY_AUTH_OPTIONAL 3 +#define ISCSIOPT_ERL1 1 +#define ISCSIOPT_ERL0 0 + uint16_t tcp_options; /* 0A-0B */ +#define TCPOPT_TIMESTAMP_STAT 6 +#define TCPOPT_NAGLE_DISABLE 5 +#define TCPOPT_WSF_DISABLE 4 +#define TCPOPT_TIMER_SCALE3 3 +#define TCPOPT_TIMER_SCALE2 2 +#define TCPOPT_TIMER_SCALE1 1 +#define TCPOPT_TIMESTAMP_EN 0 + uint16_t ip_options; /* 0C-0D */ +#define IPOPT_FRAGMENT_DISABLE 4 + uint16_t iscsi_max_rcv_data_seg_len; /* 0E-0F */ #define BYTE_UNITS 512 uint32_t res1; /* 10-13 */ @@ -816,6 +852,8 @@ struct dev_db_entry { * much RAM */ uint8_t link_local_ipv6_addr[0x10]; /* 1A0-1AF */ uint8_t res5[0x10]; /* 1B0-1BF */ +#define DDB_NO_LINK 0xFFFF +#define DDB_ISNS 0xFFFD uint16_t ddb_link; /* 1C0-1C1 */ uint16_t chap_tbl_idx; /* 1C2-1C3 */ uint16_t tgt_portal_grp; /* 1C4-1C5 */ diff --git a/drivers/scsi/qla4xxx/ql4_glbl.h b/drivers/scsi/qla4xxx/ql4_glbl.h index 982293edf02..4a428009f69 100644 --- a/drivers/scsi/qla4xxx/ql4_glbl.h +++ b/drivers/scsi/qla4xxx/ql4_glbl.h @@ -191,6 +191,9 @@ int qla4xxx_ping_iocb(struct scsi_qla_host *ha, uint32_t options, int qla4xxx_post_ping_evt_work(struct scsi_qla_host *ha, uint32_t status, uint32_t pid, uint32_t data_size, uint8_t *data); +int qla4xxx_flashdb_by_index(struct scsi_qla_host *ha, + struct dev_db_entry *fw_ddb_entry, + dma_addr_t fw_ddb_entry_dma, uint16_t ddb_index); /* BSG Functions */ int qla4xxx_bsg_request(struct bsg_job *bsg_job); @@ -224,8 +227,6 @@ void qla4_83xx_interrupt_service_routine(struct scsi_qla_host *ha, int qla4_83xx_isp_reset(struct scsi_qla_host *ha); void qla4_83xx_queue_iocb(struct scsi_qla_host *ha); void qla4_83xx_complete_iocb(struct scsi_qla_host *ha); -uint16_t qla4_83xx_rd_shdw_req_q_out(struct scsi_qla_host *ha); -uint16_t qla4_83xx_rd_shdw_rsp_q_in(struct scsi_qla_host *ha); uint32_t qla4_83xx_rd_reg(struct scsi_qla_host *ha, ulong addr); void qla4_83xx_wr_reg(struct scsi_qla_host *ha, ulong addr, uint32_t val); int qla4_83xx_rd_reg_indirect(struct scsi_qla_host *ha, uint32_t addr, @@ -261,6 +262,10 @@ int qla4_83xx_post_idc_ack(struct scsi_qla_host *ha); void qla4_83xx_disable_pause(struct scsi_qla_host *ha); void qla4_83xx_enable_mbox_intrs(struct scsi_qla_host *ha); int qla4_83xx_can_perform_reset(struct scsi_qla_host *ha); +int qla4xxx_get_default_ddb(struct scsi_qla_host *ha, uint32_t options, + dma_addr_t dma_addr); +int qla4xxx_get_uni_chap_at_index(struct scsi_qla_host *ha, char *username, + char *password, uint16_t chap_index); extern int ql4xextended_error_logging; extern int ql4xdontresethba; diff --git a/drivers/scsi/qla4xxx/ql4_isr.c b/drivers/scsi/qla4xxx/ql4_isr.c index 1b83dc283d2..482287f4005 100644 --- a/drivers/scsi/qla4xxx/ql4_isr.c +++ b/drivers/scsi/qla4xxx/ql4_isr.c @@ -396,7 +396,6 @@ static void qla4xxx_passthru_status_entry(struct scsi_qla_host *ha, task_data = task->dd_data; memcpy(&task_data->sts, sts_entry, sizeof(struct passthru_status)); - ha->req_q_count += task_data->iocb_req_cnt; ha->iocb_cnt -= task_data->iocb_req_cnt; queue_work(ha->task_wq, &task_data->task_work); } @@ -416,7 +415,6 @@ static struct mrb *qla4xxx_del_mrb_from_active_array(struct scsi_qla_host *ha, return mrb; /* update counters */ - ha->req_q_count += mrb->iocb_cnt; ha->iocb_cnt -= mrb->iocb_cnt; return mrb; @@ -877,6 +875,43 @@ static void qla4xxx_isr_decode_mailbox(struct scsi_qla_host * ha, } break; + case MBOX_ASTS_IPV6_DEFAULT_ROUTER_CHANGED: + DEBUG2(ql4_printk(KERN_INFO, ha, + "scsi%ld: AEN %04x, mbox_sts[1]=%08x, mbox_sts[2]=%08x, mbox_sts[3]=%08x, mbox_sts[4]=%08x mbox_sts[5]=%08x\n", + ha->host_no, mbox_sts[0], mbox_sts[1], + mbox_sts[2], mbox_sts[3], mbox_sts[4], + mbox_sts[5])); + DEBUG2(ql4_printk(KERN_INFO, ha, + "scsi%ld: AEN %04x Received IPv6 default router changed notification\n", + ha->host_no, mbox_sts[0])); + break; + + case MBOX_ASTS_INITIALIZATION_FAILED: + DEBUG2(ql4_printk(KERN_INFO, ha, + "scsi%ld: AEN %04x, mbox_sts[3]=%08x\n", + ha->host_no, mbox_sts[0], + mbox_sts[3])); + break; + + case MBOX_ASTS_SYSTEM_WARNING_EVENT: + DEBUG2(ql4_printk(KERN_WARNING, ha, + "scsi%ld: AEN %04x, mbox_sts[1]=%08x, mbox_sts[2]=%08x, mbox_sts[3]=%08x, mbox_sts[4]=%08x mbox_sts[5]=%08x\n", + ha->host_no, mbox_sts[0], mbox_sts[1], + mbox_sts[2], mbox_sts[3], mbox_sts[4], + mbox_sts[5])); + break; + + case MBOX_ASTS_DCBX_CONF_CHANGE: + DEBUG2(ql4_printk(KERN_INFO, ha, + "scsi%ld: AEN %04x, mbox_sts[1]=%08x, mbox_sts[2]=%08x, mbox_sts[3]=%08x, mbox_sts[4]=%08x mbox_sts[5]=%08x\n", + ha->host_no, mbox_sts[0], mbox_sts[1], + mbox_sts[2], mbox_sts[3], mbox_sts[4], + mbox_sts[5])); + DEBUG2(ql4_printk(KERN_INFO, ha, + "scsi%ld: AEN %04x Received DCBX configuration changed notification\n", + ha->host_no, mbox_sts[0])); + break; + default: DEBUG2(printk(KERN_WARNING "scsi%ld: AEN %04x UNKNOWN\n", @@ -1099,8 +1134,8 @@ irqreturn_t qla4_82xx_intr_handler(int irq, void *dev_id) status = qla4_82xx_rd_32(ha, ISR_INT_STATE_REG); if (!ISR_IS_LEGACY_INTR_TRIGGERED(status)) { - DEBUG2(ql4_printk(KERN_INFO, ha, - "%s legacy Int not triggered\n", __func__)); + DEBUG7(ql4_printk(KERN_INFO, ha, + "%s legacy Int not triggered\n", __func__)); return IRQ_NONE; } @@ -1158,7 +1193,7 @@ irqreturn_t qla4_83xx_intr_handler(int irq, void *dev_id) /* Legacy interrupt is valid if bit31 of leg_int_ptr is set */ if (!(leg_int_ptr & LEG_INT_PTR_B31)) { - DEBUG2(ql4_printk(KERN_ERR, ha, + DEBUG7(ql4_printk(KERN_ERR, ha, "%s: Legacy Interrupt Bit 31 not set, spurious interrupt!\n", __func__)); return IRQ_NONE; @@ -1166,7 +1201,7 @@ irqreturn_t qla4_83xx_intr_handler(int irq, void *dev_id) /* Validate the PCIE function ID set in leg_int_ptr bits [19..16] */ if ((leg_int_ptr & PF_BITS_MASK) != ha->pf_bit) { - DEBUG2(ql4_printk(KERN_ERR, ha, + DEBUG7(ql4_printk(KERN_ERR, ha, "%s: Incorrect function ID 0x%x in legacy interrupt register, ha->pf_bit = 0x%x\n", __func__, (leg_int_ptr & PF_BITS_MASK), ha->pf_bit)); diff --git a/drivers/scsi/qla4xxx/ql4_mbx.c b/drivers/scsi/qla4xxx/ql4_mbx.c index 160d3369721..a501beab3ff 100644 --- a/drivers/scsi/qla4xxx/ql4_mbx.c +++ b/drivers/scsi/qla4xxx/ql4_mbx.c @@ -1129,6 +1129,7 @@ int qla4xxx_reset_lun(struct scsi_qla_host * ha, struct ddb_entry * ddb_entry, { uint32_t mbox_cmd[MBOX_REG_COUNT]; uint32_t mbox_sts[MBOX_REG_COUNT]; + uint32_t scsi_lun[2]; int status = QLA_SUCCESS; DEBUG2(printk("scsi%ld:%d:%d: lun reset issued\n", ha->host_no, @@ -1140,10 +1141,16 @@ int qla4xxx_reset_lun(struct scsi_qla_host * ha, struct ddb_entry * ddb_entry, */ memset(&mbox_cmd, 0, sizeof(mbox_cmd)); memset(&mbox_sts, 0, sizeof(mbox_sts)); + int_to_scsilun(lun, (struct scsi_lun *) scsi_lun); mbox_cmd[0] = MBOX_CMD_LUN_RESET; mbox_cmd[1] = ddb_entry->fw_ddb_index; - mbox_cmd[2] = lun << 8; + /* FW expects LUN bytes 0-3 in Incoming Mailbox 2 + * (LUN byte 0 is LSByte, byte 3 is MSByte) */ + mbox_cmd[2] = cpu_to_le32(scsi_lun[0]); + /* FW expects LUN bytes 4-7 in Incoming Mailbox 3 + * (LUN byte 4 is LSByte, byte 7 is MSByte) */ + mbox_cmd[3] = cpu_to_le32(scsi_lun[1]); mbox_cmd[5] = 0x01; /* Immediate Command Enable */ qla4xxx_mailbox_command(ha, MBOX_REG_COUNT, 1, &mbox_cmd[0], &mbox_sts[0]); @@ -1281,8 +1288,8 @@ exit_about_fw: return status; } -static int qla4xxx_get_default_ddb(struct scsi_qla_host *ha, uint32_t options, - dma_addr_t dma_addr) +int qla4xxx_get_default_ddb(struct scsi_qla_host *ha, uint32_t options, + dma_addr_t dma_addr) { uint32_t mbox_cmd[MBOX_REG_COUNT]; uint32_t mbox_sts[MBOX_REG_COUNT]; @@ -1410,6 +1417,55 @@ exit_bootdb_failed: return status; } +int qla4xxx_flashdb_by_index(struct scsi_qla_host *ha, + struct dev_db_entry *fw_ddb_entry, + dma_addr_t fw_ddb_entry_dma, uint16_t ddb_index) +{ + uint32_t dev_db_start_offset; + uint32_t dev_db_end_offset; + int status = QLA_ERROR; + + memset(fw_ddb_entry, 0, sizeof(*fw_ddb_entry)); + + if (is_qla40XX(ha)) { + dev_db_start_offset = FLASH_OFFSET_DB_INFO; + dev_db_end_offset = FLASH_OFFSET_DB_END; + } else { + dev_db_start_offset = FLASH_RAW_ACCESS_ADDR + + (ha->hw.flt_region_ddb << 2); + /* flt_ddb_size is DDB table size for both ports + * so divide it by 2 to calculate the offset for second port + */ + if (ha->port_num == 1) + dev_db_start_offset += (ha->hw.flt_ddb_size / 2); + + dev_db_end_offset = dev_db_start_offset + + (ha->hw.flt_ddb_size / 2); + } + + dev_db_start_offset += (ddb_index * sizeof(*fw_ddb_entry)); + + if (dev_db_start_offset > dev_db_end_offset) { + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s:Invalid DDB index %d", __func__, + ddb_index)); + goto exit_fdb_failed; + } + + if (qla4xxx_get_flash(ha, fw_ddb_entry_dma, dev_db_start_offset, + sizeof(*fw_ddb_entry)) != QLA_SUCCESS) { + ql4_printk(KERN_ERR, ha, "scsi%ld: %s: Get Flash failed\n", + ha->host_no, __func__); + goto exit_fdb_failed; + } + + if (fw_ddb_entry->cookie == DDB_VALID_COOKIE) + status = QLA_SUCCESS; + +exit_fdb_failed: + return status; +} + int qla4xxx_get_chap(struct scsi_qla_host *ha, char *username, char *password, uint16_t idx) { @@ -1503,6 +1559,62 @@ exit_set_chap: return ret; } + +int qla4xxx_get_uni_chap_at_index(struct scsi_qla_host *ha, char *username, + char *password, uint16_t chap_index) +{ + int rval = QLA_ERROR; + struct ql4_chap_table *chap_table = NULL; + int max_chap_entries; + + if (!ha->chap_list) { + ql4_printk(KERN_ERR, ha, "Do not have CHAP table cache\n"); + rval = QLA_ERROR; + goto exit_uni_chap; + } + + if (!username || !password) { + ql4_printk(KERN_ERR, ha, "No memory for username & secret\n"); + rval = QLA_ERROR; + goto exit_uni_chap; + } + + if (is_qla80XX(ha)) + max_chap_entries = (ha->hw.flt_chap_size / 2) / + sizeof(struct ql4_chap_table); + else + max_chap_entries = MAX_CHAP_ENTRIES_40XX; + + if (chap_index > max_chap_entries) { + ql4_printk(KERN_ERR, ha, "Invalid Chap index\n"); + rval = QLA_ERROR; + goto exit_uni_chap; + } + + mutex_lock(&ha->chap_sem); + chap_table = (struct ql4_chap_table *)ha->chap_list + chap_index; + if (chap_table->cookie != __constant_cpu_to_le16(CHAP_VALID_COOKIE)) { + rval = QLA_ERROR; + goto exit_unlock_uni_chap; + } + + if (!(chap_table->flags & BIT_6)) { + ql4_printk(KERN_ERR, ha, "Unidirectional entry not set\n"); + rval = QLA_ERROR; + goto exit_unlock_uni_chap; + } + + strncpy(password, chap_table->secret, MAX_CHAP_SECRET_LEN); + strncpy(username, chap_table->name, MAX_CHAP_NAME_LEN); + + rval = QLA_SUCCESS; + +exit_unlock_uni_chap: + mutex_unlock(&ha->chap_sem); +exit_uni_chap: + return rval; +} + /** * qla4xxx_get_chap_index - Get chap index given username and secret * @ha: pointer to adapter structure @@ -1524,7 +1636,7 @@ int qla4xxx_get_chap_index(struct scsi_qla_host *ha, char *username, int max_chap_entries = 0; struct ql4_chap_table *chap_table; - if (is_qla8022(ha)) + if (is_qla80XX(ha)) max_chap_entries = (ha->hw.flt_chap_size / 2) / sizeof(struct ql4_chap_table); else diff --git a/drivers/scsi/qla4xxx/ql4_nx.c b/drivers/scsi/qla4xxx/ql4_nx.c index 9299400d3c9..eaf00c162eb 100644 --- a/drivers/scsi/qla4xxx/ql4_nx.c +++ b/drivers/scsi/qla4xxx/ql4_nx.c @@ -3154,6 +3154,10 @@ qla4_8xxx_get_flt_info(struct scsi_qla_host *ha, uint32_t flt_addr) hw->flt_region_chap = start; hw->flt_chap_size = le32_to_cpu(region->size); break; + case FLT_REG_ISCSI_DDB: + hw->flt_region_ddb = start; + hw->flt_ddb_size = le32_to_cpu(region->size); + break; } } goto done; @@ -3166,14 +3170,19 @@ no_flash_data: hw->flt_region_boot = FA_BOOT_CODE_ADDR_82; hw->flt_region_bootload = FA_BOOT_LOAD_ADDR_82; hw->flt_region_fw = FA_RISC_CODE_ADDR_82; - hw->flt_region_chap = FA_FLASH_ISCSI_CHAP; + hw->flt_region_chap = FA_FLASH_ISCSI_CHAP >> 2; hw->flt_chap_size = FA_FLASH_CHAP_SIZE; + hw->flt_region_ddb = FA_FLASH_ISCSI_DDB >> 2; + hw->flt_ddb_size = FA_FLASH_DDB_SIZE; done: - DEBUG2(ql4_printk(KERN_INFO, ha, "FLT[%s]: flt=0x%x fdt=0x%x " - "boot=0x%x bootload=0x%x fw=0x%x\n", loc, hw->flt_region_flt, - hw->flt_region_fdt, hw->flt_region_boot, hw->flt_region_bootload, - hw->flt_region_fw)); + DEBUG2(ql4_printk(KERN_INFO, ha, + "FLT[%s]: flt=0x%x fdt=0x%x boot=0x%x bootload=0x%x fw=0x%x chap=0x%x chap_size=0x%x ddb=0x%x ddb_size=0x%x\n", + loc, hw->flt_region_flt, hw->flt_region_fdt, + hw->flt_region_boot, hw->flt_region_bootload, + hw->flt_region_fw, hw->flt_region_chap, + hw->flt_chap_size, hw->flt_region_ddb, + hw->flt_ddb_size)); } static void diff --git a/drivers/scsi/qla4xxx/ql4_os.c b/drivers/scsi/qla4xxx/ql4_os.c index 6142729167f..a47f99957ba 100644 --- a/drivers/scsi/qla4xxx/ql4_os.c +++ b/drivers/scsi/qla4xxx/ql4_os.c @@ -166,6 +166,26 @@ static int qla4xxx_host_reset(struct Scsi_Host *shost, int reset_type); static int qla4xxx_change_queue_depth(struct scsi_device *sdev, int qdepth, int reason); +/* + * iSCSI Flash DDB sysfs entry points + */ +static int +qla4xxx_sysfs_ddb_set_param(struct iscsi_bus_flash_session *fnode_sess, + struct iscsi_bus_flash_conn *fnode_conn, + void *data, int len); +static int +qla4xxx_sysfs_ddb_get_param(struct iscsi_bus_flash_session *fnode_sess, + int param, char *buf); +static int qla4xxx_sysfs_ddb_add(struct Scsi_Host *shost, const char *buf, + int len); +static int +qla4xxx_sysfs_ddb_delete(struct iscsi_bus_flash_session *fnode_sess); +static int qla4xxx_sysfs_ddb_login(struct iscsi_bus_flash_session *fnode_sess, + struct iscsi_bus_flash_conn *fnode_conn); +static int qla4xxx_sysfs_ddb_logout(struct iscsi_bus_flash_session *fnode_sess, + struct iscsi_bus_flash_conn *fnode_conn); +static int qla4xxx_sysfs_ddb_logout_sid(struct iscsi_cls_session *cls_sess); + static struct qla4_8xxx_legacy_intr_set legacy_intr[] = QLA82XX_LEGACY_INTR_CONFIG; @@ -232,6 +252,13 @@ static struct iscsi_transport qla4xxx_iscsi_transport = { .send_ping = qla4xxx_send_ping, .get_chap = qla4xxx_get_chap_list, .delete_chap = qla4xxx_delete_chap, + .get_flashnode_param = qla4xxx_sysfs_ddb_get_param, + .set_flashnode_param = qla4xxx_sysfs_ddb_set_param, + .new_flashnode = qla4xxx_sysfs_ddb_add, + .del_flashnode = qla4xxx_sysfs_ddb_delete, + .login_flashnode = qla4xxx_sysfs_ddb_login, + .logout_flashnode = qla4xxx_sysfs_ddb_logout, + .logout_flashnode_sid = qla4xxx_sysfs_ddb_logout_sid, }; static struct scsi_transport_template *qla4xxx_scsi_transport; @@ -376,6 +403,68 @@ static umode_t qla4_attr_is_visible(int param_type, int param) default: return 0; } + case ISCSI_FLASHNODE_PARAM: + switch (param) { + case ISCSI_FLASHNODE_IS_FW_ASSIGNED_IPV6: + case ISCSI_FLASHNODE_PORTAL_TYPE: + case ISCSI_FLASHNODE_AUTO_SND_TGT_DISABLE: + case ISCSI_FLASHNODE_DISCOVERY_SESS: + case ISCSI_FLASHNODE_ENTRY_EN: + case ISCSI_FLASHNODE_HDR_DGST_EN: + case ISCSI_FLASHNODE_DATA_DGST_EN: + case ISCSI_FLASHNODE_IMM_DATA_EN: + case ISCSI_FLASHNODE_INITIAL_R2T_EN: + case ISCSI_FLASHNODE_DATASEQ_INORDER: + case ISCSI_FLASHNODE_PDU_INORDER: + case ISCSI_FLASHNODE_CHAP_AUTH_EN: + case ISCSI_FLASHNODE_SNACK_REQ_EN: + case ISCSI_FLASHNODE_DISCOVERY_LOGOUT_EN: + case ISCSI_FLASHNODE_BIDI_CHAP_EN: + case ISCSI_FLASHNODE_DISCOVERY_AUTH_OPTIONAL: + case ISCSI_FLASHNODE_ERL: + case ISCSI_FLASHNODE_TCP_TIMESTAMP_STAT: + case ISCSI_FLASHNODE_TCP_NAGLE_DISABLE: + case ISCSI_FLASHNODE_TCP_WSF_DISABLE: + case ISCSI_FLASHNODE_TCP_TIMER_SCALE: + case ISCSI_FLASHNODE_TCP_TIMESTAMP_EN: + case ISCSI_FLASHNODE_IP_FRAG_DISABLE: + case ISCSI_FLASHNODE_MAX_RECV_DLENGTH: + case ISCSI_FLASHNODE_MAX_XMIT_DLENGTH: + case ISCSI_FLASHNODE_FIRST_BURST: + case ISCSI_FLASHNODE_DEF_TIME2WAIT: + case ISCSI_FLASHNODE_DEF_TIME2RETAIN: + case ISCSI_FLASHNODE_MAX_R2T: + case ISCSI_FLASHNODE_KEEPALIVE_TMO: + case ISCSI_FLASHNODE_ISID: + case ISCSI_FLASHNODE_TSID: + case ISCSI_FLASHNODE_PORT: + case ISCSI_FLASHNODE_MAX_BURST: + case ISCSI_FLASHNODE_DEF_TASKMGMT_TMO: + case ISCSI_FLASHNODE_IPADDR: + case ISCSI_FLASHNODE_ALIAS: + case ISCSI_FLASHNODE_REDIRECT_IPADDR: + case ISCSI_FLASHNODE_MAX_SEGMENT_SIZE: + case ISCSI_FLASHNODE_LOCAL_PORT: + case ISCSI_FLASHNODE_IPV4_TOS: + case ISCSI_FLASHNODE_IPV6_TC: + case ISCSI_FLASHNODE_IPV6_FLOW_LABEL: + case ISCSI_FLASHNODE_NAME: + case ISCSI_FLASHNODE_TPGT: + case ISCSI_FLASHNODE_LINK_LOCAL_IPV6: + case ISCSI_FLASHNODE_DISCOVERY_PARENT_IDX: + case ISCSI_FLASHNODE_DISCOVERY_PARENT_TYPE: + case ISCSI_FLASHNODE_TCP_XMIT_WSF: + case ISCSI_FLASHNODE_TCP_RECV_WSF: + case ISCSI_FLASHNODE_CHAP_OUT_IDX: + case ISCSI_FLASHNODE_USERNAME: + case ISCSI_FLASHNODE_PASSWORD: + case ISCSI_FLASHNODE_STATSN: + case ISCSI_FLASHNODE_EXP_STATSN: + case ISCSI_FLASHNODE_IS_BOOT_TGT: + return S_IRUGO; + default: + return 0; + } } return 0; @@ -391,7 +480,7 @@ static int qla4xxx_get_chap_list(struct Scsi_Host *shost, uint16_t chap_tbl_idx, int valid_chap_entries = 0; int ret = 0, i; - if (is_qla8022(ha)) + if (is_qla80XX(ha)) max_chap_entries = (ha->hw.flt_chap_size / 2) / sizeof(struct ql4_chap_table); else @@ -495,7 +584,7 @@ static int qla4xxx_delete_chap(struct Scsi_Host *shost, uint16_t chap_tbl_idx) memset(chap_table, 0, sizeof(struct ql4_chap_table)); - if (is_qla8022(ha)) + if (is_qla80XX(ha)) max_chap_entries = (ha->hw.flt_chap_size / 2) / sizeof(struct ql4_chap_table); else @@ -1922,6 +2011,252 @@ static int qla4xxx_task_xmit(struct iscsi_task *task) return -ENOSYS; } +static int qla4xxx_copy_from_fwddb_param(struct iscsi_bus_flash_session *sess, + struct iscsi_bus_flash_conn *conn, + struct dev_db_entry *fw_ddb_entry) +{ + unsigned long options = 0; + int rc = 0; + + options = le16_to_cpu(fw_ddb_entry->options); + conn->is_fw_assigned_ipv6 = test_bit(OPT_IS_FW_ASSIGNED_IPV6, &options); + if (test_bit(OPT_IPV6_DEVICE, &options)) { + rc = iscsi_switch_str_param(&sess->portal_type, + PORTAL_TYPE_IPV6); + if (rc) + goto exit_copy; + } else { + rc = iscsi_switch_str_param(&sess->portal_type, + PORTAL_TYPE_IPV4); + if (rc) + goto exit_copy; + } + + sess->auto_snd_tgt_disable = test_bit(OPT_AUTO_SENDTGTS_DISABLE, + &options); + sess->discovery_sess = test_bit(OPT_DISC_SESSION, &options); + sess->entry_state = test_bit(OPT_ENTRY_STATE, &options); + + options = le16_to_cpu(fw_ddb_entry->iscsi_options); + conn->hdrdgst_en = test_bit(ISCSIOPT_HEADER_DIGEST_EN, &options); + conn->datadgst_en = test_bit(ISCSIOPT_DATA_DIGEST_EN, &options); + sess->imm_data_en = test_bit(ISCSIOPT_IMMEDIATE_DATA_EN, &options); + sess->initial_r2t_en = test_bit(ISCSIOPT_INITIAL_R2T_EN, &options); + sess->dataseq_inorder_en = test_bit(ISCSIOPT_DATA_SEQ_IN_ORDER, + &options); + sess->pdu_inorder_en = test_bit(ISCSIOPT_DATA_PDU_IN_ORDER, &options); + sess->chap_auth_en = test_bit(ISCSIOPT_CHAP_AUTH_EN, &options); + conn->snack_req_en = test_bit(ISCSIOPT_SNACK_REQ_EN, &options); + sess->discovery_logout_en = test_bit(ISCSIOPT_DISCOVERY_LOGOUT_EN, + &options); + sess->bidi_chap_en = test_bit(ISCSIOPT_BIDI_CHAP_EN, &options); + sess->discovery_auth_optional = + test_bit(ISCSIOPT_DISCOVERY_AUTH_OPTIONAL, &options); + if (test_bit(ISCSIOPT_ERL1, &options)) + sess->erl |= BIT_1; + if (test_bit(ISCSIOPT_ERL0, &options)) + sess->erl |= BIT_0; + + options = le16_to_cpu(fw_ddb_entry->tcp_options); + conn->tcp_timestamp_stat = test_bit(TCPOPT_TIMESTAMP_STAT, &options); + conn->tcp_nagle_disable = test_bit(TCPOPT_NAGLE_DISABLE, &options); + conn->tcp_wsf_disable = test_bit(TCPOPT_WSF_DISABLE, &options); + if (test_bit(TCPOPT_TIMER_SCALE3, &options)) + conn->tcp_timer_scale |= BIT_3; + if (test_bit(TCPOPT_TIMER_SCALE2, &options)) + conn->tcp_timer_scale |= BIT_2; + if (test_bit(TCPOPT_TIMER_SCALE1, &options)) + conn->tcp_timer_scale |= BIT_1; + + conn->tcp_timer_scale >>= 1; + conn->tcp_timestamp_en = test_bit(TCPOPT_TIMESTAMP_EN, &options); + + options = le16_to_cpu(fw_ddb_entry->ip_options); + conn->fragment_disable = test_bit(IPOPT_FRAGMENT_DISABLE, &options); + + conn->max_recv_dlength = BYTE_UNITS * + le16_to_cpu(fw_ddb_entry->iscsi_max_rcv_data_seg_len); + conn->max_xmit_dlength = BYTE_UNITS * + le16_to_cpu(fw_ddb_entry->iscsi_max_snd_data_seg_len); + sess->first_burst = BYTE_UNITS * + le16_to_cpu(fw_ddb_entry->iscsi_first_burst_len); + sess->max_burst = BYTE_UNITS * + le16_to_cpu(fw_ddb_entry->iscsi_max_burst_len); + sess->max_r2t = le16_to_cpu(fw_ddb_entry->iscsi_max_outsnd_r2t); + sess->time2wait = le16_to_cpu(fw_ddb_entry->iscsi_def_time2wait); + sess->time2retain = le16_to_cpu(fw_ddb_entry->iscsi_def_time2retain); + sess->tpgt = le32_to_cpu(fw_ddb_entry->tgt_portal_grp); + conn->max_segment_size = le16_to_cpu(fw_ddb_entry->mss); + conn->tcp_xmit_wsf = fw_ddb_entry->tcp_xmt_wsf; + conn->tcp_recv_wsf = fw_ddb_entry->tcp_rcv_wsf; + conn->ipv6_flow_label = le16_to_cpu(fw_ddb_entry->ipv6_flow_lbl); + conn->keepalive_timeout = le16_to_cpu(fw_ddb_entry->ka_timeout); + conn->local_port = le16_to_cpu(fw_ddb_entry->lcl_port); + conn->statsn = le32_to_cpu(fw_ddb_entry->stat_sn); + conn->exp_statsn = le32_to_cpu(fw_ddb_entry->exp_stat_sn); + sess->discovery_parent_idx = le16_to_cpu(fw_ddb_entry->ddb_link); + sess->discovery_parent_type = le16_to_cpu(fw_ddb_entry->ddb_link); + sess->chap_out_idx = le16_to_cpu(fw_ddb_entry->chap_tbl_idx); + sess->tsid = le16_to_cpu(fw_ddb_entry->tsid); + + sess->default_taskmgmt_timeout = + le16_to_cpu(fw_ddb_entry->def_timeout); + conn->port = le16_to_cpu(fw_ddb_entry->port); + + options = le16_to_cpu(fw_ddb_entry->options); + conn->ipaddress = kzalloc(IPv6_ADDR_LEN, GFP_KERNEL); + if (!conn->ipaddress) { + rc = -ENOMEM; + goto exit_copy; + } + + conn->redirect_ipaddr = kzalloc(IPv6_ADDR_LEN, GFP_KERNEL); + if (!conn->redirect_ipaddr) { + rc = -ENOMEM; + goto exit_copy; + } + + memcpy(conn->ipaddress, fw_ddb_entry->ip_addr, IPv6_ADDR_LEN); + memcpy(conn->redirect_ipaddr, fw_ddb_entry->tgt_addr, IPv6_ADDR_LEN); + + if (test_bit(OPT_IPV6_DEVICE, &options)) { + conn->ipv6_traffic_class = fw_ddb_entry->ipv4_tos; + + conn->link_local_ipv6_addr = kzalloc(IPv6_ADDR_LEN, GFP_KERNEL); + if (!conn->link_local_ipv6_addr) { + rc = -ENOMEM; + goto exit_copy; + } + + memcpy(conn->link_local_ipv6_addr, + fw_ddb_entry->link_local_ipv6_addr, IPv6_ADDR_LEN); + } else { + conn->ipv4_tos = fw_ddb_entry->ipv4_tos; + } + + if (fw_ddb_entry->iscsi_name[0]) { + rc = iscsi_switch_str_param(&sess->targetname, + (char *)fw_ddb_entry->iscsi_name); + if (rc) + goto exit_copy; + } + + if (fw_ddb_entry->iscsi_alias[0]) { + rc = iscsi_switch_str_param(&sess->targetalias, + (char *)fw_ddb_entry->iscsi_alias); + if (rc) + goto exit_copy; + } + + COPY_ISID(sess->isid, fw_ddb_entry->isid); + +exit_copy: + return rc; +} + +static int qla4xxx_copy_to_fwddb_param(struct iscsi_bus_flash_session *sess, + struct iscsi_bus_flash_conn *conn, + struct dev_db_entry *fw_ddb_entry) +{ + uint16_t options; + int rc = 0; + + options = le16_to_cpu(fw_ddb_entry->options); + SET_BITVAL(conn->is_fw_assigned_ipv6, options, BIT_11); + if (!strncmp(sess->portal_type, PORTAL_TYPE_IPV6, 4)) + options |= BIT_8; + else + options &= ~BIT_8; + + SET_BITVAL(sess->auto_snd_tgt_disable, options, BIT_6); + SET_BITVAL(sess->discovery_sess, options, BIT_4); + SET_BITVAL(sess->entry_state, options, BIT_3); + fw_ddb_entry->options = cpu_to_le16(options); + + options = le16_to_cpu(fw_ddb_entry->iscsi_options); + SET_BITVAL(conn->hdrdgst_en, options, BIT_13); + SET_BITVAL(conn->datadgst_en, options, BIT_12); + SET_BITVAL(sess->imm_data_en, options, BIT_11); + SET_BITVAL(sess->initial_r2t_en, options, BIT_10); + SET_BITVAL(sess->dataseq_inorder_en, options, BIT_9); + SET_BITVAL(sess->pdu_inorder_en, options, BIT_8); + SET_BITVAL(sess->chap_auth_en, options, BIT_7); + SET_BITVAL(conn->snack_req_en, options, BIT_6); + SET_BITVAL(sess->discovery_logout_en, options, BIT_5); + SET_BITVAL(sess->bidi_chap_en, options, BIT_4); + SET_BITVAL(sess->discovery_auth_optional, options, BIT_3); + SET_BITVAL(sess->erl & BIT_1, options, BIT_1); + SET_BITVAL(sess->erl & BIT_0, options, BIT_0); + fw_ddb_entry->iscsi_options = cpu_to_le16(options); + + options = le16_to_cpu(fw_ddb_entry->tcp_options); + SET_BITVAL(conn->tcp_timestamp_stat, options, BIT_6); + SET_BITVAL(conn->tcp_nagle_disable, options, BIT_5); + SET_BITVAL(conn->tcp_wsf_disable, options, BIT_4); + SET_BITVAL(conn->tcp_timer_scale & BIT_2, options, BIT_3); + SET_BITVAL(conn->tcp_timer_scale & BIT_1, options, BIT_2); + SET_BITVAL(conn->tcp_timer_scale & BIT_0, options, BIT_1); + SET_BITVAL(conn->tcp_timestamp_en, options, BIT_0); + fw_ddb_entry->tcp_options = cpu_to_le16(options); + + options = le16_to_cpu(fw_ddb_entry->ip_options); + SET_BITVAL(conn->fragment_disable, options, BIT_4); + fw_ddb_entry->ip_options = cpu_to_le16(options); + + fw_ddb_entry->iscsi_max_outsnd_r2t = cpu_to_le16(sess->max_r2t); + fw_ddb_entry->iscsi_max_rcv_data_seg_len = + cpu_to_le16(conn->max_recv_dlength / BYTE_UNITS); + fw_ddb_entry->iscsi_max_snd_data_seg_len = + cpu_to_le16(conn->max_xmit_dlength / BYTE_UNITS); + fw_ddb_entry->iscsi_first_burst_len = + cpu_to_le16(sess->first_burst / BYTE_UNITS); + fw_ddb_entry->iscsi_max_burst_len = cpu_to_le16(sess->max_burst / + BYTE_UNITS); + fw_ddb_entry->iscsi_def_time2wait = cpu_to_le16(sess->time2wait); + fw_ddb_entry->iscsi_def_time2retain = cpu_to_le16(sess->time2retain); + fw_ddb_entry->tgt_portal_grp = cpu_to_le16(sess->tpgt); + fw_ddb_entry->mss = cpu_to_le16(conn->max_segment_size); + fw_ddb_entry->tcp_xmt_wsf = cpu_to_le16(conn->tcp_xmit_wsf); + fw_ddb_entry->tcp_rcv_wsf = cpu_to_le16(conn->tcp_recv_wsf); + fw_ddb_entry->ipv4_tos = conn->ipv4_tos; + fw_ddb_entry->ipv6_flow_lbl = cpu_to_le16(conn->ipv6_flow_label); + fw_ddb_entry->ka_timeout = cpu_to_le16(conn->keepalive_timeout); + fw_ddb_entry->lcl_port = cpu_to_le16(conn->local_port); + fw_ddb_entry->stat_sn = cpu_to_le16(conn->statsn); + fw_ddb_entry->exp_stat_sn = cpu_to_le16(conn->exp_statsn); + fw_ddb_entry->ddb_link = cpu_to_le16(sess->discovery_parent_type); + fw_ddb_entry->chap_tbl_idx = cpu_to_le16(sess->chap_out_idx); + fw_ddb_entry->tsid = cpu_to_le16(sess->tsid); + fw_ddb_entry->port = cpu_to_le16(conn->port); + fw_ddb_entry->def_timeout = + cpu_to_le16(sess->default_taskmgmt_timeout); + + if (conn->ipaddress) + memcpy(fw_ddb_entry->ip_addr, conn->ipaddress, + sizeof(fw_ddb_entry->ip_addr)); + + if (conn->redirect_ipaddr) + memcpy(fw_ddb_entry->tgt_addr, conn->redirect_ipaddr, + sizeof(fw_ddb_entry->tgt_addr)); + + if (conn->link_local_ipv6_addr) + memcpy(fw_ddb_entry->link_local_ipv6_addr, + conn->link_local_ipv6_addr, + sizeof(fw_ddb_entry->link_local_ipv6_addr)); + + if (sess->targetname) + memcpy(fw_ddb_entry->iscsi_name, sess->targetname, + sizeof(fw_ddb_entry->iscsi_name)); + + if (sess->targetalias) + memcpy(fw_ddb_entry->iscsi_alias, sess->targetalias, + sizeof(fw_ddb_entry->iscsi_alias)); + + COPY_ISID(fw_ddb_entry->isid, sess->isid); + + return rc; +} + static void qla4xxx_copy_fwddb_param(struct scsi_qla_host *ha, struct dev_db_entry *fw_ddb_entry, struct iscsi_cls_session *cls_sess, @@ -2543,6 +2878,7 @@ static void qla4_8xxx_process_fw_error(struct scsi_qla_host *ha) void qla4_8xxx_watchdog(struct scsi_qla_host *ha) { uint32_t dev_state; + uint32_t idc_ctrl; /* don't poll if reset is going on */ if (!(test_bit(DPC_RESET_ACTIVE, &ha->dpc_flags) || @@ -2561,10 +2897,23 @@ void qla4_8xxx_watchdog(struct scsi_qla_host *ha) qla4xxx_wake_dpc(ha); } else if (dev_state == QLA8XXX_DEV_NEED_RESET && !test_bit(DPC_RESET_HA, &ha->dpc_flags)) { + + ql4_printk(KERN_INFO, ha, "%s: HW State: NEED RESET!\n", + __func__); + + if (is_qla8032(ha)) { + idc_ctrl = qla4_83xx_rd_reg(ha, + QLA83XX_IDC_DRV_CTRL); + if (!(idc_ctrl & GRACEFUL_RESET_BIT1)) { + ql4_printk(KERN_INFO, ha, "%s: Graceful reset bit is not set\n", + __func__); + qla4xxx_mailbox_premature_completion( + ha); + } + } + if (is_qla8032(ha) || (is_qla8022(ha) && !ql4xdontresethba)) { - ql4_printk(KERN_INFO, ha, "%s: HW State: " - "NEED RESET!\n", __func__); set_bit(DPC_RESET_HA, &ha->dpc_flags); qla4xxx_wake_dpc(ha); } @@ -3737,8 +4086,8 @@ static struct isp_operations qla4_83xx_isp_ops = { .reset_firmware = qla4_8xxx_stop_firmware, .queue_iocb = qla4_83xx_queue_iocb, .complete_iocb = qla4_83xx_complete_iocb, - .rd_shdw_req_q_out = qla4_83xx_rd_shdw_req_q_out, - .rd_shdw_rsp_q_in = qla4_83xx_rd_shdw_rsp_q_in, + .rd_shdw_req_q_out = qla4xxx_rd_shdw_req_q_out, + .rd_shdw_rsp_q_in = qla4xxx_rd_shdw_rsp_q_in, .get_sys_info = qla4_8xxx_get_sys_info, .rd_reg_direct = qla4_83xx_rd_reg, .wr_reg_direct = qla4_83xx_wr_reg, @@ -3761,11 +4110,6 @@ uint16_t qla4_82xx_rd_shdw_req_q_out(struct scsi_qla_host *ha) return (uint16_t)le32_to_cpu(readl(&ha->qla4_82xx_reg->req_q_out)); } -uint16_t qla4_83xx_rd_shdw_req_q_out(struct scsi_qla_host *ha) -{ - return (uint16_t)le32_to_cpu(readl(&ha->qla4_83xx_reg->req_q_out)); -} - uint16_t qla4xxx_rd_shdw_rsp_q_in(struct scsi_qla_host *ha) { return (uint16_t)le32_to_cpu(ha->shadow_regs->rsp_q_in); @@ -3776,11 +4120,6 @@ uint16_t qla4_82xx_rd_shdw_rsp_q_in(struct scsi_qla_host *ha) return (uint16_t)le32_to_cpu(readl(&ha->qla4_82xx_reg->rsp_q_in)); } -uint16_t qla4_83xx_rd_shdw_rsp_q_in(struct scsi_qla_host *ha) -{ - return (uint16_t)le32_to_cpu(readl(&ha->qla4_83xx_reg->rsp_q_in)); -} - static ssize_t qla4xxx_show_boot_eth_info(void *data, int type, char *buf) { struct scsi_qla_host *ha = data; @@ -4005,7 +4344,7 @@ static int get_fw_boot_info(struct scsi_qla_host *ha, uint16_t ddb_index[]) if (val & BIT_7) ddb_index[1] = (val & 0x7f); - } else if (is_qla8022(ha)) { + } else if (is_qla80XX(ha)) { buf = dma_alloc_coherent(&ha->pdev->dev, size, &buf_dma, GFP_KERNEL); if (!buf) { @@ -4083,7 +4422,7 @@ static int qla4xxx_get_bidi_chap(struct scsi_qla_host *ha, char *username, int max_chap_entries = 0; struct ql4_chap_table *chap_table; - if (is_qla8022(ha)) + if (is_qla80XX(ha)) max_chap_entries = (ha->hw.flt_chap_size / 2) / sizeof(struct ql4_chap_table); else @@ -5058,6 +5397,1342 @@ exit_nt_list: dma_pool_free(ha->fw_ddb_dma_pool, fw_ddb_entry, fw_ddb_dma); } +static void qla4xxx_build_new_nt_list(struct scsi_qla_host *ha, + struct list_head *list_nt) +{ + struct dev_db_entry *fw_ddb_entry; + dma_addr_t fw_ddb_dma; + int max_ddbs; + int fw_idx_size; + int ret; + uint32_t idx = 0, next_idx = 0; + uint32_t state = 0, conn_err = 0; + uint16_t conn_id = 0; + struct qla_ddb_index *nt_ddb_idx; + + fw_ddb_entry = dma_pool_alloc(ha->fw_ddb_dma_pool, GFP_KERNEL, + &fw_ddb_dma); + if (fw_ddb_entry == NULL) { + DEBUG2(ql4_printk(KERN_ERR, ha, "Out of memory\n")); + goto exit_new_nt_list; + } + max_ddbs = is_qla40XX(ha) ? MAX_DEV_DB_ENTRIES_40XX : + MAX_DEV_DB_ENTRIES; + fw_idx_size = sizeof(struct qla_ddb_index); + + for (idx = 0; idx < max_ddbs; idx = next_idx) { + ret = qla4xxx_get_fwddb_entry(ha, idx, fw_ddb_entry, fw_ddb_dma, + NULL, &next_idx, &state, + &conn_err, NULL, &conn_id); + if (ret == QLA_ERROR) + break; + + /* Check if NT, then add it to list */ + if (strlen((char *)fw_ddb_entry->iscsi_name) == 0) + goto continue_next_new_nt; + + if (!(state == DDB_DS_NO_CONNECTION_ACTIVE)) + goto continue_next_new_nt; + + DEBUG2(ql4_printk(KERN_INFO, ha, + "Adding DDB to session = 0x%x\n", idx)); + + nt_ddb_idx = vmalloc(fw_idx_size); + if (!nt_ddb_idx) + break; + + nt_ddb_idx->fw_ddb_idx = idx; + + ret = qla4xxx_is_session_exists(ha, fw_ddb_entry); + if (ret == QLA_SUCCESS) { + /* free nt_ddb_idx and do not add to list_nt */ + vfree(nt_ddb_idx); + goto continue_next_new_nt; + } + + list_add_tail(&nt_ddb_idx->list, list_nt); + + ret = qla4xxx_sess_conn_setup(ha, fw_ddb_entry, RESET_ADAPTER, + idx); + if (ret == QLA_ERROR) + goto exit_new_nt_list; + +continue_next_new_nt: + if (next_idx == 0) + break; + } + +exit_new_nt_list: + if (fw_ddb_entry) + dma_pool_free(ha->fw_ddb_dma_pool, fw_ddb_entry, fw_ddb_dma); +} + +/** + * qla4xxx_sysfs_ddb_is_non_persistent - check for non-persistence of ddb entry + * @dev: dev associated with the sysfs entry + * @data: pointer to flashnode session object + * + * Returns: + * 1: if flashnode entry is non-persistent + * 0: if flashnode entry is persistent + **/ +static int qla4xxx_sysfs_ddb_is_non_persistent(struct device *dev, void *data) +{ + struct iscsi_bus_flash_session *fnode_sess; + + if (!iscsi_flashnode_bus_match(dev, NULL)) + return 0; + + fnode_sess = iscsi_dev_to_flash_session(dev); + + return (fnode_sess->flash_state == DEV_DB_NON_PERSISTENT); +} + +/** + * qla4xxx_sysfs_ddb_tgt_create - Create sysfs entry for target + * @ha: pointer to host + * @fw_ddb_entry: flash ddb data + * @idx: target index + * @user: if set then this call is made from userland else from kernel + * + * Returns: + * On sucess: QLA_SUCCESS + * On failure: QLA_ERROR + * + * This create separate sysfs entries for session and connection attributes of + * the given fw ddb entry. + * If this is invoked as a result of a userspace call then the entry is marked + * as nonpersistent using flash_state field. + **/ +int qla4xxx_sysfs_ddb_tgt_create(struct scsi_qla_host *ha, + struct dev_db_entry *fw_ddb_entry, + uint16_t *idx, int user) +{ + struct iscsi_bus_flash_session *fnode_sess = NULL; + struct iscsi_bus_flash_conn *fnode_conn = NULL; + int rc = QLA_ERROR; + + fnode_sess = iscsi_create_flashnode_sess(ha->host, *idx, + &qla4xxx_iscsi_transport, 0); + if (!fnode_sess) { + ql4_printk(KERN_ERR, ha, + "%s: Unable to create session sysfs entry for flashnode %d of host%lu\n", + __func__, *idx, ha->host_no); + goto exit_tgt_create; + } + + fnode_conn = iscsi_create_flashnode_conn(ha->host, fnode_sess, + &qla4xxx_iscsi_transport, 0); + if (!fnode_conn) { + ql4_printk(KERN_ERR, ha, + "%s: Unable to create conn sysfs entry for flashnode %d of host%lu\n", + __func__, *idx, ha->host_no); + goto free_sess; + } + + if (user) { + fnode_sess->flash_state = DEV_DB_NON_PERSISTENT; + } else { + fnode_sess->flash_state = DEV_DB_PERSISTENT; + + if (*idx == ha->pri_ddb_idx || *idx == ha->sec_ddb_idx) + fnode_sess->is_boot_target = 1; + else + fnode_sess->is_boot_target = 0; + } + + rc = qla4xxx_copy_from_fwddb_param(fnode_sess, fnode_conn, + fw_ddb_entry); + + ql4_printk(KERN_INFO, ha, "%s: sysfs entry %s created\n", + __func__, fnode_sess->dev.kobj.name); + + ql4_printk(KERN_INFO, ha, "%s: sysfs entry %s created\n", + __func__, fnode_conn->dev.kobj.name); + + return QLA_SUCCESS; + +free_sess: + iscsi_destroy_flashnode_sess(fnode_sess); + +exit_tgt_create: + return QLA_ERROR; +} + +/** + * qla4xxx_sysfs_ddb_add - Add new ddb entry in flash + * @shost: pointer to host + * @buf: type of ddb entry (ipv4/ipv6) + * @len: length of buf + * + * This creates new ddb entry in the flash by finding first free index and + * storing default ddb there. And then create sysfs entry for the new ddb entry. + **/ +static int qla4xxx_sysfs_ddb_add(struct Scsi_Host *shost, const char *buf, + int len) +{ + struct scsi_qla_host *ha = to_qla_host(shost); + struct dev_db_entry *fw_ddb_entry = NULL; + dma_addr_t fw_ddb_entry_dma; + struct device *dev; + uint16_t idx = 0; + uint16_t max_ddbs = 0; + uint32_t options = 0; + uint32_t rval = QLA_ERROR; + + if (strncasecmp(PORTAL_TYPE_IPV4, buf, 4) && + strncasecmp(PORTAL_TYPE_IPV6, buf, 4)) { + DEBUG2(ql4_printk(KERN_ERR, ha, "%s: Invalid portal type\n", + __func__)); + goto exit_ddb_add; + } + + max_ddbs = is_qla40XX(ha) ? MAX_PRST_DEV_DB_ENTRIES : + MAX_DEV_DB_ENTRIES; + + fw_ddb_entry = dma_alloc_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + &fw_ddb_entry_dma, GFP_KERNEL); + if (!fw_ddb_entry) { + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate dma buffer\n", + __func__)); + goto exit_ddb_add; + } + + dev = iscsi_find_flashnode_sess(ha->host, NULL, + qla4xxx_sysfs_ddb_is_non_persistent); + if (dev) { + ql4_printk(KERN_ERR, ha, + "%s: A non-persistent entry %s found\n", + __func__, dev->kobj.name); + goto exit_ddb_add; + } + + for (idx = 0; idx < max_ddbs; idx++) { + if (qla4xxx_flashdb_by_index(ha, fw_ddb_entry, + fw_ddb_entry_dma, idx)) + break; + } + + if (idx == max_ddbs) + goto exit_ddb_add; + + if (!strncasecmp("ipv6", buf, 4)) + options |= IPV6_DEFAULT_DDB_ENTRY; + + rval = qla4xxx_get_default_ddb(ha, options, fw_ddb_entry_dma); + if (rval == QLA_ERROR) + goto exit_ddb_add; + + rval = qla4xxx_sysfs_ddb_tgt_create(ha, fw_ddb_entry, &idx, 1); + +exit_ddb_add: + if (fw_ddb_entry) + dma_free_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + fw_ddb_entry, fw_ddb_entry_dma); + if (rval == QLA_SUCCESS) + return idx; + else + return -EIO; +} + +/** + * qla4xxx_sysfs_ddb_apply - write the target ddb contents to Flash + * @fnode_sess: pointer to session attrs of flash ddb entry + * @fnode_conn: pointer to connection attrs of flash ddb entry + * + * This writes the contents of target ddb buffer to Flash with a valid cookie + * value in order to make the ddb entry persistent. + **/ +static int qla4xxx_sysfs_ddb_apply(struct iscsi_bus_flash_session *fnode_sess, + struct iscsi_bus_flash_conn *fnode_conn) +{ + struct Scsi_Host *shost = iscsi_flash_session_to_shost(fnode_sess); + struct scsi_qla_host *ha = to_qla_host(shost); + uint32_t dev_db_start_offset = FLASH_OFFSET_DB_INFO; + struct dev_db_entry *fw_ddb_entry = NULL; + dma_addr_t fw_ddb_entry_dma; + uint32_t options = 0; + int rval = 0; + + fw_ddb_entry = dma_alloc_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + &fw_ddb_entry_dma, GFP_KERNEL); + if (!fw_ddb_entry) { + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate dma buffer\n", + __func__)); + rval = -ENOMEM; + goto exit_ddb_apply; + } + + if (!strncasecmp(fnode_sess->portal_type, PORTAL_TYPE_IPV6, 4)) + options |= IPV6_DEFAULT_DDB_ENTRY; + + rval = qla4xxx_get_default_ddb(ha, options, fw_ddb_entry_dma); + if (rval == QLA_ERROR) + goto exit_ddb_apply; + + dev_db_start_offset += (fnode_sess->target_id * + sizeof(*fw_ddb_entry)); + + qla4xxx_copy_to_fwddb_param(fnode_sess, fnode_conn, fw_ddb_entry); + fw_ddb_entry->cookie = DDB_VALID_COOKIE; + + rval = qla4xxx_set_flash(ha, fw_ddb_entry_dma, dev_db_start_offset, + sizeof(*fw_ddb_entry), FLASH_OPT_RMW_COMMIT); + + if (rval == QLA_SUCCESS) { + fnode_sess->flash_state = DEV_DB_PERSISTENT; + ql4_printk(KERN_INFO, ha, + "%s: flash node %u of host %lu written to flash\n", + __func__, fnode_sess->target_id, ha->host_no); + } else { + rval = -EIO; + ql4_printk(KERN_ERR, ha, + "%s: Error while writing flash node %u of host %lu to flash\n", + __func__, fnode_sess->target_id, ha->host_no); + } + +exit_ddb_apply: + if (fw_ddb_entry) + dma_free_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + fw_ddb_entry, fw_ddb_entry_dma); + return rval; +} + +static ssize_t qla4xxx_sysfs_ddb_conn_open(struct scsi_qla_host *ha, + struct dev_db_entry *fw_ddb_entry, + uint16_t idx) +{ + struct dev_db_entry *ddb_entry = NULL; + dma_addr_t ddb_entry_dma; + unsigned long wtime; + uint32_t mbx_sts = 0; + uint32_t state = 0, conn_err = 0; + uint16_t tmo = 0; + int ret = 0; + + ddb_entry = dma_alloc_coherent(&ha->pdev->dev, sizeof(*ddb_entry), + &ddb_entry_dma, GFP_KERNEL); + if (!ddb_entry) { + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate dma buffer\n", + __func__)); + return QLA_ERROR; + } + + memcpy(ddb_entry, fw_ddb_entry, sizeof(*ddb_entry)); + + ret = qla4xxx_set_ddb_entry(ha, idx, ddb_entry_dma, &mbx_sts); + if (ret != QLA_SUCCESS) { + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Unable to set ddb entry for index %d\n", + __func__, idx)); + goto exit_ddb_conn_open; + } + + qla4xxx_conn_open(ha, idx); + + /* To ensure that sendtargets is done, wait for at least 12 secs */ + tmo = ((ha->def_timeout > LOGIN_TOV) && + (ha->def_timeout < LOGIN_TOV * 10) ? + ha->def_timeout : LOGIN_TOV); + + DEBUG2(ql4_printk(KERN_INFO, ha, + "Default time to wait for login to ddb %d\n", tmo)); + + wtime = jiffies + (HZ * tmo); + do { + ret = qla4xxx_get_fwddb_entry(ha, idx, NULL, 0, NULL, + NULL, &state, &conn_err, NULL, + NULL); + if (ret == QLA_ERROR) + continue; + + if (state == DDB_DS_NO_CONNECTION_ACTIVE || + state == DDB_DS_SESSION_FAILED) + break; + + schedule_timeout_uninterruptible(HZ / 10); + } while (time_after(wtime, jiffies)); + +exit_ddb_conn_open: + if (ddb_entry) + dma_free_coherent(&ha->pdev->dev, sizeof(*ddb_entry), + ddb_entry, ddb_entry_dma); + return ret; +} + +static int qla4xxx_ddb_login_st(struct scsi_qla_host *ha, + struct dev_db_entry *fw_ddb_entry) +{ + struct qla_ddb_index *ddb_idx, *ddb_idx_tmp; + struct list_head list_nt; + uint16_t ddb_index; + int ret = 0; + + if (test_bit(AF_ST_DISCOVERY_IN_PROGRESS, &ha->flags)) { + ql4_printk(KERN_WARNING, ha, + "%s: A discovery already in progress!\n", __func__); + return QLA_ERROR; + } + + INIT_LIST_HEAD(&list_nt); + + set_bit(AF_ST_DISCOVERY_IN_PROGRESS, &ha->flags); + + ret = qla4xxx_get_ddb_index(ha, &ddb_index); + if (ret == QLA_ERROR) + goto exit_login_st_clr_bit; + + ret = qla4xxx_sysfs_ddb_conn_open(ha, fw_ddb_entry, ddb_index); + if (ret == QLA_ERROR) + goto exit_login_st; + + qla4xxx_build_new_nt_list(ha, &list_nt); + + list_for_each_entry_safe(ddb_idx, ddb_idx_tmp, &list_nt, list) { + list_del_init(&ddb_idx->list); + qla4xxx_clear_ddb_entry(ha, ddb_idx->fw_ddb_idx); + vfree(ddb_idx); + } + +exit_login_st: + if (qla4xxx_clear_ddb_entry(ha, ddb_index) == QLA_ERROR) { + ql4_printk(KERN_ERR, ha, + "Unable to clear DDB index = 0x%x\n", ddb_index); + } + + clear_bit(ddb_index, ha->ddb_idx_map); + +exit_login_st_clr_bit: + clear_bit(AF_ST_DISCOVERY_IN_PROGRESS, &ha->flags); + return ret; +} + +static int qla4xxx_ddb_login_nt(struct scsi_qla_host *ha, + struct dev_db_entry *fw_ddb_entry, + uint16_t idx) +{ + int ret = QLA_ERROR; + + ret = qla4xxx_is_session_exists(ha, fw_ddb_entry); + if (ret != QLA_SUCCESS) + ret = qla4xxx_sess_conn_setup(ha, fw_ddb_entry, RESET_ADAPTER, + idx); + else + ret = -EPERM; + + return ret; +} + +/** + * qla4xxx_sysfs_ddb_login - Login to the specified target + * @fnode_sess: pointer to session attrs of flash ddb entry + * @fnode_conn: pointer to connection attrs of flash ddb entry + * + * This logs in to the specified target + **/ +static int qla4xxx_sysfs_ddb_login(struct iscsi_bus_flash_session *fnode_sess, + struct iscsi_bus_flash_conn *fnode_conn) +{ + struct Scsi_Host *shost = iscsi_flash_session_to_shost(fnode_sess); + struct scsi_qla_host *ha = to_qla_host(shost); + struct dev_db_entry *fw_ddb_entry = NULL; + dma_addr_t fw_ddb_entry_dma; + uint32_t options = 0; + int ret = 0; + + if (fnode_sess->flash_state == DEV_DB_NON_PERSISTENT) { + ql4_printk(KERN_ERR, ha, + "%s: Target info is not persistent\n", __func__); + ret = -EIO; + goto exit_ddb_login; + } + + fw_ddb_entry = dma_alloc_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + &fw_ddb_entry_dma, GFP_KERNEL); + if (!fw_ddb_entry) { + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate dma buffer\n", + __func__)); + ret = -ENOMEM; + goto exit_ddb_login; + } + + if (!strncasecmp(fnode_sess->portal_type, PORTAL_TYPE_IPV6, 4)) + options |= IPV6_DEFAULT_DDB_ENTRY; + + ret = qla4xxx_get_default_ddb(ha, options, fw_ddb_entry_dma); + if (ret == QLA_ERROR) + goto exit_ddb_login; + + qla4xxx_copy_to_fwddb_param(fnode_sess, fnode_conn, fw_ddb_entry); + fw_ddb_entry->cookie = DDB_VALID_COOKIE; + + if (strlen((char *)fw_ddb_entry->iscsi_name) == 0) + ret = qla4xxx_ddb_login_st(ha, fw_ddb_entry); + else + ret = qla4xxx_ddb_login_nt(ha, fw_ddb_entry, + fnode_sess->target_id); + + if (ret > 0) + ret = -EIO; + +exit_ddb_login: + if (fw_ddb_entry) + dma_free_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + fw_ddb_entry, fw_ddb_entry_dma); + return ret; +} + +/** + * qla4xxx_sysfs_ddb_logout_sid - Logout session for the specified target + * @cls_sess: pointer to session to be logged out + * + * This performs session log out from the specified target + **/ +static int qla4xxx_sysfs_ddb_logout_sid(struct iscsi_cls_session *cls_sess) +{ + struct iscsi_session *sess; + struct ddb_entry *ddb_entry = NULL; + struct scsi_qla_host *ha; + struct dev_db_entry *fw_ddb_entry = NULL; + dma_addr_t fw_ddb_entry_dma; + unsigned long flags; + unsigned long wtime; + uint32_t ddb_state; + int options; + int ret = 0; + + sess = cls_sess->dd_data; + ddb_entry = sess->dd_data; + ha = ddb_entry->ha; + + if (ddb_entry->ddb_type != FLASH_DDB) { + ql4_printk(KERN_ERR, ha, "%s: Not a flash node session\n", + __func__); + ret = -ENXIO; + goto exit_ddb_logout; + } + + if (test_bit(DF_BOOT_TGT, &ddb_entry->flags)) { + ql4_printk(KERN_ERR, ha, + "%s: Logout from boot target entry is not permitted.\n", + __func__); + ret = -EPERM; + goto exit_ddb_logout; + } + + options = LOGOUT_OPTION_CLOSE_SESSION; + if (qla4xxx_session_logout_ddb(ha, ddb_entry, options) == QLA_ERROR) { + ql4_printk(KERN_ERR, ha, "%s: Logout failed\n", __func__); + ret = -EIO; + goto exit_ddb_logout; + } + + fw_ddb_entry = dma_alloc_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + &fw_ddb_entry_dma, GFP_KERNEL); + if (!fw_ddb_entry) { + ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate dma buffer\n", __func__); + ret = -ENOMEM; + goto exit_ddb_logout; + } + + wtime = jiffies + (HZ * LOGOUT_TOV); + do { + ret = qla4xxx_get_fwddb_entry(ha, ddb_entry->fw_ddb_index, + fw_ddb_entry, fw_ddb_entry_dma, + NULL, NULL, &ddb_state, NULL, + NULL, NULL); + if (ret == QLA_ERROR) + goto ddb_logout_clr_sess; + + if ((ddb_state == DDB_DS_NO_CONNECTION_ACTIVE) || + (ddb_state == DDB_DS_SESSION_FAILED)) + goto ddb_logout_clr_sess; + + schedule_timeout_uninterruptible(HZ); + } while ((time_after(wtime, jiffies))); + +ddb_logout_clr_sess: + qla4xxx_clear_ddb_entry(ha, ddb_entry->fw_ddb_index); + /* + * we have decremented the reference count of the driver + * when we setup the session to have the driver unload + * to be seamless without actually destroying the + * session + **/ + try_module_get(qla4xxx_iscsi_transport.owner); + iscsi_destroy_endpoint(ddb_entry->conn->ep); + + spin_lock_irqsave(&ha->hardware_lock, flags); + qla4xxx_free_ddb(ha, ddb_entry); + spin_unlock_irqrestore(&ha->hardware_lock, flags); + + iscsi_session_teardown(ddb_entry->sess); + + ret = QLA_SUCCESS; + +exit_ddb_logout: + if (fw_ddb_entry) + dma_free_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), + fw_ddb_entry, fw_ddb_entry_dma); + return ret; +} + +/** + * qla4xxx_sysfs_ddb_logout - Logout from the specified target + * @fnode_sess: pointer to session attrs of flash ddb entry + * @fnode_conn: pointer to connection attrs of flash ddb entry + * + * This performs log out from the specified target + **/ +static int qla4xxx_sysfs_ddb_logout(struct iscsi_bus_flash_session *fnode_sess, + struct iscsi_bus_flash_conn *fnode_conn) +{ + struct Scsi_Host *shost = iscsi_flash_session_to_shost(fnode_sess); + struct scsi_qla_host *ha = to_qla_host(shost); + struct ql4_tuple_ddb *flash_tddb = NULL; + struct ql4_tuple_ddb *tmp_tddb = NULL; + struct dev_db_entry *fw_ddb_entry = NULL; + struct ddb_entry *ddb_entry = NULL; + dma_addr_t fw_ddb_dma; + uint32_t next_idx = 0; + uint32_t state = 0, conn_err = 0; + uint16_t conn_id = 0; + int idx, index; + int status, ret = 0; + + fw_ddb_entry = dma_pool_alloc(ha->fw_ddb_dma_pool, GFP_KERNEL, + &fw_ddb_dma); + if (fw_ddb_entry == NULL) { + ql4_printk(KERN_ERR, ha, "%s:Out of memory\n", __func__); + ret = -ENOMEM; + goto exit_ddb_logout; + } + + flash_tddb = vzalloc(sizeof(*flash_tddb)); + if (!flash_tddb) { + ql4_printk(KERN_WARNING, ha, + "%s:Memory Allocation failed.\n", __func__); + ret = -ENOMEM; + goto exit_ddb_logout; + } + + tmp_tddb = vzalloc(sizeof(*tmp_tddb)); + if (!tmp_tddb) { + ql4_printk(KERN_WARNING, ha, + "%s:Memory Allocation failed.\n", __func__); + ret = -ENOMEM; + goto exit_ddb_logout; + } + + if (!fnode_sess->targetname) { + ql4_printk(KERN_ERR, ha, + "%s:Cannot logout from SendTarget entry\n", + __func__); + ret = -EPERM; + goto exit_ddb_logout; + } + + if (fnode_sess->is_boot_target) { + ql4_printk(KERN_ERR, ha, + "%s: Logout from boot target entry is not permitted.\n", + __func__); + ret = -EPERM; + goto exit_ddb_logout; + } + + strncpy(flash_tddb->iscsi_name, fnode_sess->targetname, + ISCSI_NAME_SIZE); + + if (!strncmp(fnode_sess->portal_type, PORTAL_TYPE_IPV6, 4)) + sprintf(flash_tddb->ip_addr, "%pI6", fnode_conn->ipaddress); + else + sprintf(flash_tddb->ip_addr, "%pI4", fnode_conn->ipaddress); + + flash_tddb->tpgt = fnode_sess->tpgt; + flash_tddb->port = fnode_conn->port; + + COPY_ISID(flash_tddb->isid, fnode_sess->isid); + + for (idx = 0; idx < MAX_DDB_ENTRIES; idx++) { + ddb_entry = qla4xxx_lookup_ddb_by_fw_index(ha, idx); + if (ddb_entry == NULL) + continue; + + if (ddb_entry->ddb_type != FLASH_DDB) + continue; + + index = ddb_entry->sess->target_id; + status = qla4xxx_get_fwddb_entry(ha, index, fw_ddb_entry, + fw_ddb_dma, NULL, &next_idx, + &state, &conn_err, NULL, + &conn_id); + if (status == QLA_ERROR) { + ret = -ENOMEM; + break; + } + + qla4xxx_convert_param_ddb(fw_ddb_entry, tmp_tddb, NULL); + + status = qla4xxx_compare_tuple_ddb(ha, flash_tddb, tmp_tddb, + true); + if (status == QLA_SUCCESS) { + ret = qla4xxx_sysfs_ddb_logout_sid(ddb_entry->sess); + break; + } + } + + if (idx == MAX_DDB_ENTRIES) + ret = -ESRCH; + +exit_ddb_logout: + if (flash_tddb) + vfree(flash_tddb); + if (tmp_tddb) + vfree(tmp_tddb); + if (fw_ddb_entry) + dma_pool_free(ha->fw_ddb_dma_pool, fw_ddb_entry, fw_ddb_dma); + + return ret; +} + +static int +qla4xxx_sysfs_ddb_get_param(struct iscsi_bus_flash_session *fnode_sess, + int param, char *buf) +{ + struct Scsi_Host *shost = iscsi_flash_session_to_shost(fnode_sess); + struct scsi_qla_host *ha = to_qla_host(shost); + struct iscsi_bus_flash_conn *fnode_conn; + struct ql4_chap_table chap_tbl; + struct device *dev; + int parent_type, parent_index = 0xffff; + int rc = 0; + + dev = iscsi_find_flashnode_conn(fnode_sess, NULL, + iscsi_is_flashnode_conn_dev); + if (!dev) + return -EIO; + + fnode_conn = iscsi_dev_to_flash_conn(dev); + + switch (param) { + case ISCSI_FLASHNODE_IS_FW_ASSIGNED_IPV6: + rc = sprintf(buf, "%u\n", fnode_conn->is_fw_assigned_ipv6); + break; + case ISCSI_FLASHNODE_PORTAL_TYPE: + rc = sprintf(buf, "%s\n", fnode_sess->portal_type); + break; + case ISCSI_FLASHNODE_AUTO_SND_TGT_DISABLE: + rc = sprintf(buf, "%u\n", fnode_sess->auto_snd_tgt_disable); + break; + case ISCSI_FLASHNODE_DISCOVERY_SESS: + rc = sprintf(buf, "%u\n", fnode_sess->discovery_sess); + break; + case ISCSI_FLASHNODE_ENTRY_EN: + rc = sprintf(buf, "%u\n", fnode_sess->entry_state); + break; + case ISCSI_FLASHNODE_HDR_DGST_EN: + rc = sprintf(buf, "%u\n", fnode_conn->hdrdgst_en); + break; + case ISCSI_FLASHNODE_DATA_DGST_EN: + rc = sprintf(buf, "%u\n", fnode_conn->datadgst_en); + break; + case ISCSI_FLASHNODE_IMM_DATA_EN: + rc = sprintf(buf, "%u\n", fnode_sess->imm_data_en); + break; + case ISCSI_FLASHNODE_INITIAL_R2T_EN: + rc = sprintf(buf, "%u\n", fnode_sess->initial_r2t_en); + break; + case ISCSI_FLASHNODE_DATASEQ_INORDER: + rc = sprintf(buf, "%u\n", fnode_sess->dataseq_inorder_en); + break; + case ISCSI_FLASHNODE_PDU_INORDER: + rc = sprintf(buf, "%u\n", fnode_sess->pdu_inorder_en); + break; + case ISCSI_FLASHNODE_CHAP_AUTH_EN: + rc = sprintf(buf, "%u\n", fnode_sess->chap_auth_en); + break; + case ISCSI_FLASHNODE_SNACK_REQ_EN: + rc = sprintf(buf, "%u\n", fnode_conn->snack_req_en); + break; + case ISCSI_FLASHNODE_DISCOVERY_LOGOUT_EN: + rc = sprintf(buf, "%u\n", fnode_sess->discovery_logout_en); + break; + case ISCSI_FLASHNODE_BIDI_CHAP_EN: + rc = sprintf(buf, "%u\n", fnode_sess->bidi_chap_en); + break; + case ISCSI_FLASHNODE_DISCOVERY_AUTH_OPTIONAL: + rc = sprintf(buf, "%u\n", fnode_sess->discovery_auth_optional); + break; + case ISCSI_FLASHNODE_ERL: + rc = sprintf(buf, "%u\n", fnode_sess->erl); + break; + case ISCSI_FLASHNODE_TCP_TIMESTAMP_STAT: + rc = sprintf(buf, "%u\n", fnode_conn->tcp_timestamp_stat); + break; + case ISCSI_FLASHNODE_TCP_NAGLE_DISABLE: + rc = sprintf(buf, "%u\n", fnode_conn->tcp_nagle_disable); + break; + case ISCSI_FLASHNODE_TCP_WSF_DISABLE: + rc = sprintf(buf, "%u\n", fnode_conn->tcp_wsf_disable); + break; + case ISCSI_FLASHNODE_TCP_TIMER_SCALE: + rc = sprintf(buf, "%u\n", fnode_conn->tcp_timer_scale); + break; + case ISCSI_FLASHNODE_TCP_TIMESTAMP_EN: + rc = sprintf(buf, "%u\n", fnode_conn->tcp_timestamp_en); + break; + case ISCSI_FLASHNODE_IP_FRAG_DISABLE: + rc = sprintf(buf, "%u\n", fnode_conn->fragment_disable); + break; + case ISCSI_FLASHNODE_MAX_RECV_DLENGTH: + rc = sprintf(buf, "%u\n", fnode_conn->max_recv_dlength); + break; + case ISCSI_FLASHNODE_MAX_XMIT_DLENGTH: + rc = sprintf(buf, "%u\n", fnode_conn->max_xmit_dlength); + break; + case ISCSI_FLASHNODE_FIRST_BURST: + rc = sprintf(buf, "%u\n", fnode_sess->first_burst); + break; + case ISCSI_FLASHNODE_DEF_TIME2WAIT: + rc = sprintf(buf, "%u\n", fnode_sess->time2wait); + break; + case ISCSI_FLASHNODE_DEF_TIME2RETAIN: + rc = sprintf(buf, "%u\n", fnode_sess->time2retain); + break; + case ISCSI_FLASHNODE_MAX_R2T: + rc = sprintf(buf, "%u\n", fnode_sess->max_r2t); + break; + case ISCSI_FLASHNODE_KEEPALIVE_TMO: + rc = sprintf(buf, "%u\n", fnode_conn->keepalive_timeout); + break; + case ISCSI_FLASHNODE_ISID: + rc = sprintf(buf, "%02x%02x%02x%02x%02x%02x\n", + fnode_sess->isid[0], fnode_sess->isid[1], + fnode_sess->isid[2], fnode_sess->isid[3], + fnode_sess->isid[4], fnode_sess->isid[5]); + break; + case ISCSI_FLASHNODE_TSID: + rc = sprintf(buf, "%u\n", fnode_sess->tsid); + break; + case ISCSI_FLASHNODE_PORT: + rc = sprintf(buf, "%d\n", fnode_conn->port); + break; + case ISCSI_FLASHNODE_MAX_BURST: + rc = sprintf(buf, "%u\n", fnode_sess->max_burst); + break; + case ISCSI_FLASHNODE_DEF_TASKMGMT_TMO: + rc = sprintf(buf, "%u\n", + fnode_sess->default_taskmgmt_timeout); + break; + case ISCSI_FLASHNODE_IPADDR: + if (!strncmp(fnode_sess->portal_type, PORTAL_TYPE_IPV6, 4)) + rc = sprintf(buf, "%pI6\n", fnode_conn->ipaddress); + else + rc = sprintf(buf, "%pI4\n", fnode_conn->ipaddress); + break; + case ISCSI_FLASHNODE_ALIAS: + if (fnode_sess->targetalias) + rc = sprintf(buf, "%s\n", fnode_sess->targetalias); + else + rc = sprintf(buf, "\n"); + break; + case ISCSI_FLASHNODE_REDIRECT_IPADDR: + if (!strncmp(fnode_sess->portal_type, PORTAL_TYPE_IPV6, 4)) + rc = sprintf(buf, "%pI6\n", + fnode_conn->redirect_ipaddr); + else + rc = sprintf(buf, "%pI4\n", + fnode_conn->redirect_ipaddr); + break; + case ISCSI_FLASHNODE_MAX_SEGMENT_SIZE: + rc = sprintf(buf, "%u\n", fnode_conn->max_segment_size); + break; + case ISCSI_FLASHNODE_LOCAL_PORT: + rc = sprintf(buf, "%u\n", fnode_conn->local_port); + break; + case ISCSI_FLASHNODE_IPV4_TOS: + rc = sprintf(buf, "%u\n", fnode_conn->ipv4_tos); + break; + case ISCSI_FLASHNODE_IPV6_TC: + if (!strncmp(fnode_sess->portal_type, PORTAL_TYPE_IPV6, 4)) + rc = sprintf(buf, "%u\n", + fnode_conn->ipv6_traffic_class); + else + rc = sprintf(buf, "\n"); + break; + case ISCSI_FLASHNODE_IPV6_FLOW_LABEL: + rc = sprintf(buf, "%u\n", fnode_conn->ipv6_flow_label); + break; + case ISCSI_FLASHNODE_LINK_LOCAL_IPV6: + if (!strncmp(fnode_sess->portal_type, PORTAL_TYPE_IPV6, 4)) + rc = sprintf(buf, "%pI6\n", + fnode_conn->link_local_ipv6_addr); + else + rc = sprintf(buf, "\n"); + break; + case ISCSI_FLASHNODE_DISCOVERY_PARENT_IDX: + if ((fnode_sess->discovery_parent_idx) >= 0 && + (fnode_sess->discovery_parent_idx < MAX_DDB_ENTRIES)) + parent_index = fnode_sess->discovery_parent_idx; + + rc = sprintf(buf, "%u\n", parent_index); + break; + case ISCSI_FLASHNODE_DISCOVERY_PARENT_TYPE: + if (fnode_sess->discovery_parent_type == DDB_ISNS) + parent_type = ISCSI_DISC_PARENT_ISNS; + else if (fnode_sess->discovery_parent_type == DDB_NO_LINK) + parent_type = ISCSI_DISC_PARENT_UNKNOWN; + else if (fnode_sess->discovery_parent_type >= 0 && + fnode_sess->discovery_parent_type < MAX_DDB_ENTRIES) + parent_type = ISCSI_DISC_PARENT_SENDTGT; + else + parent_type = ISCSI_DISC_PARENT_UNKNOWN; + + rc = sprintf(buf, "%s\n", + iscsi_get_discovery_parent_name(parent_type)); + break; + case ISCSI_FLASHNODE_NAME: + if (fnode_sess->targetname) + rc = sprintf(buf, "%s\n", fnode_sess->targetname); + else + rc = sprintf(buf, "\n"); + break; + case ISCSI_FLASHNODE_TPGT: + rc = sprintf(buf, "%u\n", fnode_sess->tpgt); + break; + case ISCSI_FLASHNODE_TCP_XMIT_WSF: + rc = sprintf(buf, "%u\n", fnode_conn->tcp_xmit_wsf); + break; + case ISCSI_FLASHNODE_TCP_RECV_WSF: + rc = sprintf(buf, "%u\n", fnode_conn->tcp_recv_wsf); + break; + case ISCSI_FLASHNODE_CHAP_OUT_IDX: + rc = sprintf(buf, "%u\n", fnode_sess->chap_out_idx); + break; + case ISCSI_FLASHNODE_USERNAME: + if (fnode_sess->chap_auth_en) { + qla4xxx_get_uni_chap_at_index(ha, + chap_tbl.name, + chap_tbl.secret, + fnode_sess->chap_out_idx); + rc = sprintf(buf, "%s\n", chap_tbl.name); + } else { + rc = sprintf(buf, "\n"); + } + break; + case ISCSI_FLASHNODE_PASSWORD: + if (fnode_sess->chap_auth_en) { + qla4xxx_get_uni_chap_at_index(ha, + chap_tbl.name, + chap_tbl.secret, + fnode_sess->chap_out_idx); + rc = sprintf(buf, "%s\n", chap_tbl.secret); + } else { + rc = sprintf(buf, "\n"); + } + break; + case ISCSI_FLASHNODE_STATSN: + rc = sprintf(buf, "%u\n", fnode_conn->statsn); + break; + case ISCSI_FLASHNODE_EXP_STATSN: + rc = sprintf(buf, "%u\n", fnode_conn->exp_statsn); + break; + case ISCSI_FLASHNODE_IS_BOOT_TGT: + rc = sprintf(buf, "%u\n", fnode_sess->is_boot_target); + break; + default: + rc = -ENOSYS; + break; + } + return rc; +} + +/** + * qla4xxx_sysfs_ddb_set_param - Set parameter for firmware DDB entry + * @fnode_sess: pointer to session attrs of flash ddb entry + * @fnode_conn: pointer to connection attrs of flash ddb entry + * @data: Parameters and their values to update + * @len: len of data + * + * This sets the parameter of flash ddb entry and writes them to flash + **/ +static int +qla4xxx_sysfs_ddb_set_param(struct iscsi_bus_flash_session *fnode_sess, + struct iscsi_bus_flash_conn *fnode_conn, + void *data, int len) +{ + struct Scsi_Host *shost = iscsi_flash_session_to_shost(fnode_sess); + struct scsi_qla_host *ha = to_qla_host(shost); + struct dev_db_entry *fw_ddb_entry = NULL; + struct iscsi_flashnode_param_info *fnode_param; + struct nlattr *attr; + int rc = QLA_ERROR; + uint32_t rem = len; + + fw_ddb_entry = kzalloc(sizeof(*fw_ddb_entry), GFP_KERNEL); + if (!fw_ddb_entry) { + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate ddb buffer\n", + __func__)); + return -ENOMEM; + } + + nla_for_each_attr(attr, data, len, rem) { + fnode_param = nla_data(attr); + + switch (fnode_param->param) { + case ISCSI_FLASHNODE_IS_FW_ASSIGNED_IPV6: + fnode_conn->is_fw_assigned_ipv6 = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_PORTAL_TYPE: + memcpy(fnode_sess->portal_type, fnode_param->value, + strlen(fnode_sess->portal_type)); + break; + case ISCSI_FLASHNODE_AUTO_SND_TGT_DISABLE: + fnode_sess->auto_snd_tgt_disable = + fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_DISCOVERY_SESS: + fnode_sess->discovery_sess = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_ENTRY_EN: + fnode_sess->entry_state = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_HDR_DGST_EN: + fnode_conn->hdrdgst_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_DATA_DGST_EN: + fnode_conn->datadgst_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_IMM_DATA_EN: + fnode_sess->imm_data_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_INITIAL_R2T_EN: + fnode_sess->initial_r2t_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_DATASEQ_INORDER: + fnode_sess->dataseq_inorder_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_PDU_INORDER: + fnode_sess->pdu_inorder_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_CHAP_AUTH_EN: + fnode_sess->chap_auth_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_SNACK_REQ_EN: + fnode_conn->snack_req_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_DISCOVERY_LOGOUT_EN: + fnode_sess->discovery_logout_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_BIDI_CHAP_EN: + fnode_sess->bidi_chap_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_DISCOVERY_AUTH_OPTIONAL: + fnode_sess->discovery_auth_optional = + fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_ERL: + fnode_sess->erl = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_TCP_TIMESTAMP_STAT: + fnode_conn->tcp_timestamp_stat = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_TCP_NAGLE_DISABLE: + fnode_conn->tcp_nagle_disable = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_TCP_WSF_DISABLE: + fnode_conn->tcp_wsf_disable = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_TCP_TIMER_SCALE: + fnode_conn->tcp_timer_scale = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_TCP_TIMESTAMP_EN: + fnode_conn->tcp_timestamp_en = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_IP_FRAG_DISABLE: + fnode_conn->fragment_disable = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_MAX_RECV_DLENGTH: + fnode_conn->max_recv_dlength = + *(unsigned *)fnode_param->value; + break; + case ISCSI_FLASHNODE_MAX_XMIT_DLENGTH: + fnode_conn->max_xmit_dlength = + *(unsigned *)fnode_param->value; + break; + case ISCSI_FLASHNODE_FIRST_BURST: + fnode_sess->first_burst = + *(unsigned *)fnode_param->value; + break; + case ISCSI_FLASHNODE_DEF_TIME2WAIT: + fnode_sess->time2wait = *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_DEF_TIME2RETAIN: + fnode_sess->time2retain = + *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_MAX_R2T: + fnode_sess->max_r2t = + *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_KEEPALIVE_TMO: + fnode_conn->keepalive_timeout = + *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_ISID: + memcpy(fnode_sess->isid, fnode_param->value, + sizeof(fnode_sess->isid)); + break; + case ISCSI_FLASHNODE_TSID: + fnode_sess->tsid = *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_PORT: + fnode_conn->port = *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_MAX_BURST: + fnode_sess->max_burst = *(unsigned *)fnode_param->value; + break; + case ISCSI_FLASHNODE_DEF_TASKMGMT_TMO: + fnode_sess->default_taskmgmt_timeout = + *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_IPADDR: + memcpy(fnode_conn->ipaddress, fnode_param->value, + IPv6_ADDR_LEN); + break; + case ISCSI_FLASHNODE_ALIAS: + rc = iscsi_switch_str_param(&fnode_sess->targetalias, + (char *)fnode_param->value); + break; + case ISCSI_FLASHNODE_REDIRECT_IPADDR: + memcpy(fnode_conn->redirect_ipaddr, fnode_param->value, + IPv6_ADDR_LEN); + break; + case ISCSI_FLASHNODE_MAX_SEGMENT_SIZE: + fnode_conn->max_segment_size = + *(unsigned *)fnode_param->value; + break; + case ISCSI_FLASHNODE_LOCAL_PORT: + fnode_conn->local_port = + *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_IPV4_TOS: + fnode_conn->ipv4_tos = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_IPV6_TC: + fnode_conn->ipv6_traffic_class = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_IPV6_FLOW_LABEL: + fnode_conn->ipv6_flow_label = fnode_param->value[0]; + break; + case ISCSI_FLASHNODE_NAME: + rc = iscsi_switch_str_param(&fnode_sess->targetname, + (char *)fnode_param->value); + break; + case ISCSI_FLASHNODE_TPGT: + fnode_sess->tpgt = *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_LINK_LOCAL_IPV6: + memcpy(fnode_conn->link_local_ipv6_addr, + fnode_param->value, IPv6_ADDR_LEN); + break; + case ISCSI_FLASHNODE_DISCOVERY_PARENT_TYPE: + fnode_sess->discovery_parent_type = + *(uint16_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_TCP_XMIT_WSF: + fnode_conn->tcp_xmit_wsf = + *(uint8_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_TCP_RECV_WSF: + fnode_conn->tcp_recv_wsf = + *(uint8_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_STATSN: + fnode_conn->statsn = *(uint32_t *)fnode_param->value; + break; + case ISCSI_FLASHNODE_EXP_STATSN: + fnode_conn->exp_statsn = + *(uint32_t *)fnode_param->value; + break; + default: + ql4_printk(KERN_ERR, ha, + "%s: No such sysfs attribute\n", __func__); + rc = -ENOSYS; + goto exit_set_param; + } + } + + rc = qla4xxx_sysfs_ddb_apply(fnode_sess, fnode_conn); + +exit_set_param: + return rc; +} + +/** + * qla4xxx_sysfs_ddb_delete - Delete firmware DDB entry + * @fnode_sess: pointer to session attrs of flash ddb entry + * + * This invalidates the flash ddb entry at the given index + **/ +static int qla4xxx_sysfs_ddb_delete(struct iscsi_bus_flash_session *fnode_sess) +{ + struct Scsi_Host *shost = iscsi_flash_session_to_shost(fnode_sess); + struct scsi_qla_host *ha = to_qla_host(shost); + uint32_t dev_db_start_offset; + uint32_t dev_db_end_offset; + struct dev_db_entry *fw_ddb_entry = NULL; + dma_addr_t fw_ddb_entry_dma; + uint16_t *ddb_cookie = NULL; + size_t ddb_size; + void *pddb = NULL; + int target_id; + int rc = 0; + + if (!fnode_sess) { + rc = -EINVAL; + goto exit_ddb_del; + } + + if (fnode_sess->is_boot_target) { + rc = -EPERM; + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Deletion of boot target entry is not permitted.\n", + __func__)); + goto exit_ddb_del; + } + + if (fnode_sess->flash_state == DEV_DB_NON_PERSISTENT) + goto sysfs_ddb_del; + + if (is_qla40XX(ha)) { + dev_db_start_offset = FLASH_OFFSET_DB_INFO; + dev_db_end_offset = FLASH_OFFSET_DB_END; + dev_db_start_offset += (fnode_sess->target_id * + sizeof(*fw_ddb_entry)); + ddb_size = sizeof(*fw_ddb_entry); + } else { + dev_db_start_offset = FLASH_RAW_ACCESS_ADDR + + (ha->hw.flt_region_ddb << 2); + /* flt_ddb_size is DDB table size for both ports + * so divide it by 2 to calculate the offset for second port + */ + if (ha->port_num == 1) + dev_db_start_offset += (ha->hw.flt_ddb_size / 2); + + dev_db_end_offset = dev_db_start_offset + + (ha->hw.flt_ddb_size / 2); + + dev_db_start_offset += (fnode_sess->target_id * + sizeof(*fw_ddb_entry)); + dev_db_start_offset += (void *)&(fw_ddb_entry->cookie) - + (void *)fw_ddb_entry; + + ddb_size = sizeof(*ddb_cookie); + } + + DEBUG2(ql4_printk(KERN_ERR, ha, "%s: start offset=%u, end offset=%u\n", + __func__, dev_db_start_offset, dev_db_end_offset)); + + if (dev_db_start_offset > dev_db_end_offset) { + rc = -EIO; + DEBUG2(ql4_printk(KERN_ERR, ha, "%s:Invalid DDB index %u\n", + __func__, fnode_sess->target_id)); + goto exit_ddb_del; + } + + pddb = dma_alloc_coherent(&ha->pdev->dev, ddb_size, + &fw_ddb_entry_dma, GFP_KERNEL); + if (!pddb) { + rc = -ENOMEM; + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate dma buffer\n", + __func__)); + goto exit_ddb_del; + } + + if (is_qla40XX(ha)) { + fw_ddb_entry = pddb; + memset(fw_ddb_entry, 0, ddb_size); + ddb_cookie = &fw_ddb_entry->cookie; + } else { + ddb_cookie = pddb; + } + + /* invalidate the cookie */ + *ddb_cookie = 0xFFEE; + qla4xxx_set_flash(ha, fw_ddb_entry_dma, dev_db_start_offset, + ddb_size, FLASH_OPT_RMW_COMMIT); + +sysfs_ddb_del: + target_id = fnode_sess->target_id; + iscsi_destroy_flashnode_sess(fnode_sess); + ql4_printk(KERN_INFO, ha, + "%s: session and conn entries for flashnode %u of host %lu deleted\n", + __func__, target_id, ha->host_no); +exit_ddb_del: + if (pddb) + dma_free_coherent(&ha->pdev->dev, ddb_size, pddb, + fw_ddb_entry_dma); + return rc; +} + +/** + * qla4xxx_sysfs_ddb_export - Create sysfs entries for firmware DDBs + * @ha: pointer to adapter structure + * + * Export the firmware DDB for all send targets and normal targets to sysfs. + **/ +static int qla4xxx_sysfs_ddb_export(struct scsi_qla_host *ha) +{ + struct dev_db_entry *fw_ddb_entry = NULL; + dma_addr_t fw_ddb_entry_dma; + uint16_t max_ddbs; + uint16_t idx = 0; + int ret = QLA_SUCCESS; + + fw_ddb_entry = dma_alloc_coherent(&ha->pdev->dev, + sizeof(*fw_ddb_entry), + &fw_ddb_entry_dma, GFP_KERNEL); + if (!fw_ddb_entry) { + DEBUG2(ql4_printk(KERN_ERR, ha, + "%s: Unable to allocate dma buffer\n", + __func__)); + return -ENOMEM; + } + + max_ddbs = is_qla40XX(ha) ? MAX_PRST_DEV_DB_ENTRIES : + MAX_DEV_DB_ENTRIES; + + for (idx = 0; idx < max_ddbs; idx++) { + if (qla4xxx_flashdb_by_index(ha, fw_ddb_entry, fw_ddb_entry_dma, + idx)) + continue; + + ret = qla4xxx_sysfs_ddb_tgt_create(ha, fw_ddb_entry, &idx, 0); + if (ret) { + ret = -EIO; + break; + } + } + + dma_free_coherent(&ha->pdev->dev, sizeof(*fw_ddb_entry), fw_ddb_entry, + fw_ddb_entry_dma); + + return ret; +} + +static void qla4xxx_sysfs_ddb_remove(struct scsi_qla_host *ha) +{ + iscsi_destroy_all_flashnode(ha->host); +} + /** * qla4xxx_build_ddb_list - Build ddb list and setup sessions * @ha: pointer to adapter structure @@ -5341,8 +7016,11 @@ static int qla4xxx_probe_adapter(struct pci_dev *pdev, status = qla4xxx_initialize_adapter(ha, INIT_ADAPTER); /* Dont retry adapter initialization if IRQ allocation failed */ - if (!test_bit(AF_IRQ_ATTACHED, &ha->flags)) + if (is_qla80XX(ha) && !test_bit(AF_IRQ_ATTACHED, &ha->flags)) { + ql4_printk(KERN_WARNING, ha, "%s: Skipping retry of adapter initialization\n", + __func__); goto skip_retry_init; + } while ((!test_bit(AF_ONLINE, &ha->flags)) && init_retry_count++ < MAX_INIT_RETRIES) { @@ -5445,6 +7123,10 @@ skip_retry_init: ql4_printk(KERN_ERR, ha, "%s: No iSCSI boot target configured\n", __func__); + if (qla4xxx_sysfs_ddb_export(ha)) + ql4_printk(KERN_ERR, ha, + "%s: Error exporting ddb to sysfs\n", __func__); + /* Perform the build ddb list and login to each */ qla4xxx_build_ddb_list(ha, INIT_ADAPTER); iscsi_host_for_each_session(ha->host, qla4xxx_login_flash_ddb); @@ -5570,6 +7252,7 @@ static void qla4xxx_remove_adapter(struct pci_dev *pdev) qla4xxx_destroy_fw_ddb_session(ha); qla4_8xxx_free_sysfs_attr(ha); + qla4xxx_sysfs_ddb_remove(ha); scsi_remove_host(ha->host); qla4xxx_free_adapter(ha); @@ -5669,7 +7352,6 @@ struct srb *qla4xxx_del_from_active_array(struct scsi_qla_host *ha, /* update counters */ if (srb->flags & SRB_DMA_VALID) { - ha->req_q_count += srb->iocb_cnt; ha->iocb_cnt -= srb->iocb_cnt; if (srb->cmd) srb->cmd->host_scribble = @@ -6081,6 +7763,7 @@ static int qla4xxx_host_reset(struct Scsi_Host *shost, int reset_type) { struct scsi_qla_host *ha = to_qla_host(shost); int rval = QLA_SUCCESS; + uint32_t idc_ctrl; if (ql4xdontresethba) { DEBUG2(ql4_printk(KERN_INFO, ha, "%s: Don't Reset HBA\n", @@ -6111,6 +7794,14 @@ static int qla4xxx_host_reset(struct Scsi_Host *shost, int reset_type) } recover_adapter: + /* For ISP83XX set graceful reset bit in IDC_DRV_CTRL if + * reset is issued by application */ + if (is_qla8032(ha) && test_bit(DPC_RESET_HA, &ha->dpc_flags)) { + idc_ctrl = qla4_83xx_rd_reg(ha, QLA83XX_IDC_DRV_CTRL); + qla4_83xx_wr_reg(ha, QLA83XX_IDC_DRV_CTRL, + (idc_ctrl | GRACEFUL_RESET_BIT1)); + } + rval = qla4xxx_recover_adapter(ha); if (rval != QLA_SUCCESS) { DEBUG2(ql4_printk(KERN_INFO, ha, "%s: recover adapter fail\n", diff --git a/drivers/scsi/qla4xxx/ql4_version.h b/drivers/scsi/qla4xxx/ql4_version.h index 6775a45af31..83e0fec35d5 100644 --- a/drivers/scsi/qla4xxx/ql4_version.h +++ b/drivers/scsi/qla4xxx/ql4_version.h @@ -5,4 +5,4 @@ * See LICENSE.qla4xxx for copyright and licensing details. */ -#define QLA4XXX_DRIVER_VERSION "5.03.00-k4" +#define QLA4XXX_DRIVER_VERSION "5.03.00-k8" diff --git a/drivers/scsi/scsi_transport_iscsi.c b/drivers/scsi/scsi_transport_iscsi.c index 0a74b975efd..ce06e8772f3 100644 --- a/drivers/scsi/scsi_transport_iscsi.c +++ b/drivers/scsi/scsi_transport_iscsi.c @@ -25,6 +25,7 @@ #include <linux/slab.h> #include <linux/bsg-lib.h> #include <linux/idr.h> +#include <linux/list.h> #include <net/tcp.h> #include <scsi/scsi.h> #include <scsi/scsi_host.h> @@ -460,6 +461,689 @@ void iscsi_destroy_iface(struct iscsi_iface *iface) EXPORT_SYMBOL_GPL(iscsi_destroy_iface); /* + * Interface to display flash node params to sysfs + */ + +#define ISCSI_FLASHNODE_ATTR(_prefix, _name, _mode, _show, _store) \ +struct device_attribute dev_attr_##_prefix##_##_name = \ + __ATTR(_name, _mode, _show, _store) + +/* flash node session attrs show */ +#define iscsi_flashnode_sess_attr_show(type, name, param) \ +static ssize_t \ +show_##type##_##name(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct iscsi_bus_flash_session *fnode_sess = \ + iscsi_dev_to_flash_session(dev);\ + struct iscsi_transport *t = fnode_sess->transport; \ + return t->get_flashnode_param(fnode_sess, param, buf); \ +} \ + + +#define iscsi_flashnode_sess_attr(type, name, param) \ + iscsi_flashnode_sess_attr_show(type, name, param) \ +static ISCSI_FLASHNODE_ATTR(type, name, S_IRUGO, \ + show_##type##_##name, NULL); + +/* Flash node session attributes */ + +iscsi_flashnode_sess_attr(fnode, auto_snd_tgt_disable, + ISCSI_FLASHNODE_AUTO_SND_TGT_DISABLE); +iscsi_flashnode_sess_attr(fnode, discovery_session, + ISCSI_FLASHNODE_DISCOVERY_SESS); +iscsi_flashnode_sess_attr(fnode, portal_type, ISCSI_FLASHNODE_PORTAL_TYPE); +iscsi_flashnode_sess_attr(fnode, entry_enable, ISCSI_FLASHNODE_ENTRY_EN); +iscsi_flashnode_sess_attr(fnode, immediate_data, ISCSI_FLASHNODE_IMM_DATA_EN); +iscsi_flashnode_sess_attr(fnode, initial_r2t, ISCSI_FLASHNODE_INITIAL_R2T_EN); +iscsi_flashnode_sess_attr(fnode, data_seq_in_order, + ISCSI_FLASHNODE_DATASEQ_INORDER); +iscsi_flashnode_sess_attr(fnode, data_pdu_in_order, + ISCSI_FLASHNODE_PDU_INORDER); +iscsi_flashnode_sess_attr(fnode, chap_auth, ISCSI_FLASHNODE_CHAP_AUTH_EN); +iscsi_flashnode_sess_attr(fnode, discovery_logout, + ISCSI_FLASHNODE_DISCOVERY_LOGOUT_EN); +iscsi_flashnode_sess_attr(fnode, bidi_chap, ISCSI_FLASHNODE_BIDI_CHAP_EN); +iscsi_flashnode_sess_attr(fnode, discovery_auth_optional, + ISCSI_FLASHNODE_DISCOVERY_AUTH_OPTIONAL); +iscsi_flashnode_sess_attr(fnode, erl, ISCSI_FLASHNODE_ERL); +iscsi_flashnode_sess_attr(fnode, first_burst_len, ISCSI_FLASHNODE_FIRST_BURST); +iscsi_flashnode_sess_attr(fnode, def_time2wait, ISCSI_FLASHNODE_DEF_TIME2WAIT); +iscsi_flashnode_sess_attr(fnode, def_time2retain, + ISCSI_FLASHNODE_DEF_TIME2RETAIN); +iscsi_flashnode_sess_attr(fnode, max_outstanding_r2t, ISCSI_FLASHNODE_MAX_R2T); +iscsi_flashnode_sess_attr(fnode, isid, ISCSI_FLASHNODE_ISID); +iscsi_flashnode_sess_attr(fnode, tsid, ISCSI_FLASHNODE_TSID); +iscsi_flashnode_sess_attr(fnode, max_burst_len, ISCSI_FLASHNODE_MAX_BURST); +iscsi_flashnode_sess_attr(fnode, def_taskmgmt_tmo, + ISCSI_FLASHNODE_DEF_TASKMGMT_TMO); +iscsi_flashnode_sess_attr(fnode, targetalias, ISCSI_FLASHNODE_ALIAS); +iscsi_flashnode_sess_attr(fnode, targetname, ISCSI_FLASHNODE_NAME); +iscsi_flashnode_sess_attr(fnode, tpgt, ISCSI_FLASHNODE_TPGT); +iscsi_flashnode_sess_attr(fnode, discovery_parent_idx, + ISCSI_FLASHNODE_DISCOVERY_PARENT_IDX); +iscsi_flashnode_sess_attr(fnode, discovery_parent_type, + ISCSI_FLASHNODE_DISCOVERY_PARENT_TYPE); +iscsi_flashnode_sess_attr(fnode, chap_in_idx, ISCSI_FLASHNODE_CHAP_IN_IDX); +iscsi_flashnode_sess_attr(fnode, chap_out_idx, ISCSI_FLASHNODE_CHAP_OUT_IDX); +iscsi_flashnode_sess_attr(fnode, username, ISCSI_FLASHNODE_USERNAME); +iscsi_flashnode_sess_attr(fnode, username_in, ISCSI_FLASHNODE_USERNAME_IN); +iscsi_flashnode_sess_attr(fnode, password, ISCSI_FLASHNODE_PASSWORD); +iscsi_flashnode_sess_attr(fnode, password_in, ISCSI_FLASHNODE_PASSWORD_IN); +iscsi_flashnode_sess_attr(fnode, is_boot_target, ISCSI_FLASHNODE_IS_BOOT_TGT); + +static struct attribute *iscsi_flashnode_sess_attrs[] = { + &dev_attr_fnode_auto_snd_tgt_disable.attr, + &dev_attr_fnode_discovery_session.attr, + &dev_attr_fnode_portal_type.attr, + &dev_attr_fnode_entry_enable.attr, + &dev_attr_fnode_immediate_data.attr, + &dev_attr_fnode_initial_r2t.attr, + &dev_attr_fnode_data_seq_in_order.attr, + &dev_attr_fnode_data_pdu_in_order.attr, + &dev_attr_fnode_chap_auth.attr, + &dev_attr_fnode_discovery_logout.attr, + &dev_attr_fnode_bidi_chap.attr, + &dev_attr_fnode_discovery_auth_optional.attr, + &dev_attr_fnode_erl.attr, + &dev_attr_fnode_first_burst_len.attr, + &dev_attr_fnode_def_time2wait.attr, + &dev_attr_fnode_def_time2retain.attr, + &dev_attr_fnode_max_outstanding_r2t.attr, + &dev_attr_fnode_isid.attr, + &dev_attr_fnode_tsid.attr, + &dev_attr_fnode_max_burst_len.attr, + &dev_attr_fnode_def_taskmgmt_tmo.attr, + &dev_attr_fnode_targetalias.attr, + &dev_attr_fnode_targetname.attr, + &dev_attr_fnode_tpgt.attr, + &dev_attr_fnode_discovery_parent_idx.attr, + &dev_attr_fnode_discovery_parent_type.attr, + &dev_attr_fnode_chap_in_idx.attr, + &dev_attr_fnode_chap_out_idx.attr, + &dev_attr_fnode_username.attr, + &dev_attr_fnode_username_in.attr, + &dev_attr_fnode_password.attr, + &dev_attr_fnode_password_in.attr, + &dev_attr_fnode_is_boot_target.attr, + NULL, +}; + +static umode_t iscsi_flashnode_sess_attr_is_visible(struct kobject *kobj, + struct attribute *attr, + int i) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct iscsi_bus_flash_session *fnode_sess = + iscsi_dev_to_flash_session(dev); + struct iscsi_transport *t = fnode_sess->transport; + int param; + + if (attr == &dev_attr_fnode_auto_snd_tgt_disable.attr) { + param = ISCSI_FLASHNODE_AUTO_SND_TGT_DISABLE; + } else if (attr == &dev_attr_fnode_discovery_session.attr) { + param = ISCSI_FLASHNODE_DISCOVERY_SESS; + } else if (attr == &dev_attr_fnode_portal_type.attr) { + param = ISCSI_FLASHNODE_PORTAL_TYPE; + } else if (attr == &dev_attr_fnode_entry_enable.attr) { + param = ISCSI_FLASHNODE_ENTRY_EN; + } else if (attr == &dev_attr_fnode_immediate_data.attr) { + param = ISCSI_FLASHNODE_IMM_DATA_EN; + } else if (attr == &dev_attr_fnode_initial_r2t.attr) { + param = ISCSI_FLASHNODE_INITIAL_R2T_EN; + } else if (attr == &dev_attr_fnode_data_seq_in_order.attr) { + param = ISCSI_FLASHNODE_DATASEQ_INORDER; + } else if (attr == &dev_attr_fnode_data_pdu_in_order.attr) { + param = ISCSI_FLASHNODE_PDU_INORDER; + } else if (attr == &dev_attr_fnode_chap_auth.attr) { + param = ISCSI_FLASHNODE_CHAP_AUTH_EN; + } else if (attr == &dev_attr_fnode_discovery_logout.attr) { + param = ISCSI_FLASHNODE_DISCOVERY_LOGOUT_EN; + } else if (attr == &dev_attr_fnode_bidi_chap.attr) { + param = ISCSI_FLASHNODE_BIDI_CHAP_EN; + } else if (attr == &dev_attr_fnode_discovery_auth_optional.attr) { + param = ISCSI_FLASHNODE_DISCOVERY_AUTH_OPTIONAL; + } else if (attr == &dev_attr_fnode_erl.attr) { + param = ISCSI_FLASHNODE_ERL; + } else if (attr == &dev_attr_fnode_first_burst_len.attr) { + param = ISCSI_FLASHNODE_FIRST_BURST; + } else if (attr == &dev_attr_fnode_def_time2wait.attr) { + param = ISCSI_FLASHNODE_DEF_TIME2WAIT; + } else if (attr == &dev_attr_fnode_def_time2retain.attr) { + param = ISCSI_FLASHNODE_DEF_TIME2RETAIN; + } else if (attr == &dev_attr_fnode_max_outstanding_r2t.attr) { + param = ISCSI_FLASHNODE_MAX_R2T; + } else if (attr == &dev_attr_fnode_isid.attr) { + param = ISCSI_FLASHNODE_ISID; + } else if (attr == &dev_attr_fnode_tsid.attr) { + param = ISCSI_FLASHNODE_TSID; + } else if (attr == &dev_attr_fnode_max_burst_len.attr) { + param = ISCSI_FLASHNODE_MAX_BURST; + } else if (attr == &dev_attr_fnode_def_taskmgmt_tmo.attr) { + param = ISCSI_FLASHNODE_DEF_TASKMGMT_TMO; + } else if (attr == &dev_attr_fnode_targetalias.attr) { + param = ISCSI_FLASHNODE_ALIAS; + } else if (attr == &dev_attr_fnode_targetname.attr) { + param = ISCSI_FLASHNODE_NAME; + } else if (attr == &dev_attr_fnode_tpgt.attr) { + param = ISCSI_FLASHNODE_TPGT; + } else if (attr == &dev_attr_fnode_discovery_parent_idx.attr) { + param = ISCSI_FLASHNODE_DISCOVERY_PARENT_IDX; + } else if (attr == &dev_attr_fnode_discovery_parent_type.attr) { + param = ISCSI_FLASHNODE_DISCOVERY_PARENT_TYPE; + } else if (attr == &dev_attr_fnode_chap_in_idx.attr) { + param = ISCSI_FLASHNODE_CHAP_IN_IDX; + } else if (attr == &dev_attr_fnode_chap_out_idx.attr) { + param = ISCSI_FLASHNODE_CHAP_OUT_IDX; + } else if (attr == &dev_attr_fnode_username.attr) { + param = ISCSI_FLASHNODE_USERNAME; + } else if (attr == &dev_attr_fnode_username_in.attr) { + param = ISCSI_FLASHNODE_USERNAME_IN; + } else if (attr == &dev_attr_fnode_password.attr) { + param = ISCSI_FLASHNODE_PASSWORD; + } else if (attr == &dev_attr_fnode_password_in.attr) { + param = ISCSI_FLASHNODE_PASSWORD_IN; + } else if (attr == &dev_attr_fnode_is_boot_target.attr) { + param = ISCSI_FLASHNODE_IS_BOOT_TGT; + } else { + WARN_ONCE(1, "Invalid flashnode session attr"); + return 0; + } + + return t->attr_is_visible(ISCSI_FLASHNODE_PARAM, param); +} + +static struct attribute_group iscsi_flashnode_sess_attr_group = { + .attrs = iscsi_flashnode_sess_attrs, + .is_visible = iscsi_flashnode_sess_attr_is_visible, +}; + +static const struct attribute_group *iscsi_flashnode_sess_attr_groups[] = { + &iscsi_flashnode_sess_attr_group, + NULL, +}; + +static void iscsi_flashnode_sess_release(struct device *dev) +{ + struct iscsi_bus_flash_session *fnode_sess = + iscsi_dev_to_flash_session(dev); + + kfree(fnode_sess->targetname); + kfree(fnode_sess->targetalias); + kfree(fnode_sess->portal_type); + kfree(fnode_sess); +} + +struct device_type iscsi_flashnode_sess_dev_type = { + .name = "iscsi_flashnode_sess_dev_type", + .groups = iscsi_flashnode_sess_attr_groups, + .release = iscsi_flashnode_sess_release, +}; + +/* flash node connection attrs show */ +#define iscsi_flashnode_conn_attr_show(type, name, param) \ +static ssize_t \ +show_##type##_##name(struct device *dev, struct device_attribute *attr, \ + char *buf) \ +{ \ + struct iscsi_bus_flash_conn *fnode_conn = iscsi_dev_to_flash_conn(dev);\ + struct iscsi_bus_flash_session *fnode_sess = \ + iscsi_flash_conn_to_flash_session(fnode_conn);\ + struct iscsi_transport *t = fnode_conn->transport; \ + return t->get_flashnode_param(fnode_sess, param, buf); \ +} \ + + +#define iscsi_flashnode_conn_attr(type, name, param) \ + iscsi_flashnode_conn_attr_show(type, name, param) \ +static ISCSI_FLASHNODE_ATTR(type, name, S_IRUGO, \ + show_##type##_##name, NULL); + +/* Flash node connection attributes */ + +iscsi_flashnode_conn_attr(fnode, is_fw_assigned_ipv6, + ISCSI_FLASHNODE_IS_FW_ASSIGNED_IPV6); +iscsi_flashnode_conn_attr(fnode, header_digest, ISCSI_FLASHNODE_HDR_DGST_EN); +iscsi_flashnode_conn_attr(fnode, data_digest, ISCSI_FLASHNODE_DATA_DGST_EN); +iscsi_flashnode_conn_attr(fnode, snack_req, ISCSI_FLASHNODE_SNACK_REQ_EN); +iscsi_flashnode_conn_attr(fnode, tcp_timestamp_stat, + ISCSI_FLASHNODE_TCP_TIMESTAMP_STAT); +iscsi_flashnode_conn_attr(fnode, tcp_nagle_disable, + ISCSI_FLASHNODE_TCP_NAGLE_DISABLE); +iscsi_flashnode_conn_attr(fnode, tcp_wsf_disable, + ISCSI_FLASHNODE_TCP_WSF_DISABLE); +iscsi_flashnode_conn_attr(fnode, tcp_timer_scale, + ISCSI_FLASHNODE_TCP_TIMER_SCALE); +iscsi_flashnode_conn_attr(fnode, tcp_timestamp_enable, + ISCSI_FLASHNODE_TCP_TIMESTAMP_EN); +iscsi_flashnode_conn_attr(fnode, fragment_disable, + ISCSI_FLASHNODE_IP_FRAG_DISABLE); +iscsi_flashnode_conn_attr(fnode, keepalive_tmo, ISCSI_FLASHNODE_KEEPALIVE_TMO); +iscsi_flashnode_conn_attr(fnode, port, ISCSI_FLASHNODE_PORT); +iscsi_flashnode_conn_attr(fnode, ipaddress, ISCSI_FLASHNODE_IPADDR); +iscsi_flashnode_conn_attr(fnode, max_recv_dlength, + ISCSI_FLASHNODE_MAX_RECV_DLENGTH); +iscsi_flashnode_conn_attr(fnode, max_xmit_dlength, + ISCSI_FLASHNODE_MAX_XMIT_DLENGTH); +iscsi_flashnode_conn_attr(fnode, local_port, ISCSI_FLASHNODE_LOCAL_PORT); +iscsi_flashnode_conn_attr(fnode, ipv4_tos, ISCSI_FLASHNODE_IPV4_TOS); +iscsi_flashnode_conn_attr(fnode, ipv6_traffic_class, ISCSI_FLASHNODE_IPV6_TC); +iscsi_flashnode_conn_attr(fnode, ipv6_flow_label, + ISCSI_FLASHNODE_IPV6_FLOW_LABEL); +iscsi_flashnode_conn_attr(fnode, redirect_ipaddr, + ISCSI_FLASHNODE_REDIRECT_IPADDR); +iscsi_flashnode_conn_attr(fnode, max_segment_size, + ISCSI_FLASHNODE_MAX_SEGMENT_SIZE); +iscsi_flashnode_conn_attr(fnode, link_local_ipv6, + ISCSI_FLASHNODE_LINK_LOCAL_IPV6); +iscsi_flashnode_conn_attr(fnode, tcp_xmit_wsf, ISCSI_FLASHNODE_TCP_XMIT_WSF); +iscsi_flashnode_conn_attr(fnode, tcp_recv_wsf, ISCSI_FLASHNODE_TCP_RECV_WSF); +iscsi_flashnode_conn_attr(fnode, statsn, ISCSI_FLASHNODE_STATSN); +iscsi_flashnode_conn_attr(fnode, exp_statsn, ISCSI_FLASHNODE_EXP_STATSN); + +static struct attribute *iscsi_flashnode_conn_attrs[] = { + &dev_attr_fnode_is_fw_assigned_ipv6.attr, + &dev_attr_fnode_header_digest.attr, + &dev_attr_fnode_data_digest.attr, + &dev_attr_fnode_snack_req.attr, + &dev_attr_fnode_tcp_timestamp_stat.attr, + &dev_attr_fnode_tcp_nagle_disable.attr, + &dev_attr_fnode_tcp_wsf_disable.attr, + &dev_attr_fnode_tcp_timer_scale.attr, + &dev_attr_fnode_tcp_timestamp_enable.attr, + &dev_attr_fnode_fragment_disable.attr, + &dev_attr_fnode_max_recv_dlength.attr, + &dev_attr_fnode_max_xmit_dlength.attr, + &dev_attr_fnode_keepalive_tmo.attr, + &dev_attr_fnode_port.attr, + &dev_attr_fnode_ipaddress.attr, + &dev_attr_fnode_redirect_ipaddr.attr, + &dev_attr_fnode_max_segment_size.attr, + &dev_attr_fnode_local_port.attr, + &dev_attr_fnode_ipv4_tos.attr, + &dev_attr_fnode_ipv6_traffic_class.attr, + &dev_attr_fnode_ipv6_flow_label.attr, + &dev_attr_fnode_link_local_ipv6.attr, + &dev_attr_fnode_tcp_xmit_wsf.attr, + &dev_attr_fnode_tcp_recv_wsf.attr, + &dev_attr_fnode_statsn.attr, + &dev_attr_fnode_exp_statsn.attr, + NULL, +}; + +static umode_t iscsi_flashnode_conn_attr_is_visible(struct kobject *kobj, + struct attribute *attr, + int i) +{ + struct device *dev = container_of(kobj, struct device, kobj); + struct iscsi_bus_flash_conn *fnode_conn = iscsi_dev_to_flash_conn(dev); + struct iscsi_transport *t = fnode_conn->transport; + int param; + + if (attr == &dev_attr_fnode_is_fw_assigned_ipv6.attr) { + param = ISCSI_FLASHNODE_IS_FW_ASSIGNED_IPV6; + } else if (attr == &dev_attr_fnode_header_digest.attr) { + param = ISCSI_FLASHNODE_HDR_DGST_EN; + } else if (attr == &dev_attr_fnode_data_digest.attr) { + param = ISCSI_FLASHNODE_DATA_DGST_EN; + } else if (attr == &dev_attr_fnode_snack_req.attr) { + param = ISCSI_FLASHNODE_SNACK_REQ_EN; + } else if (attr == &dev_attr_fnode_tcp_timestamp_stat.attr) { + param = ISCSI_FLASHNODE_TCP_TIMESTAMP_STAT; + } else if (attr == &dev_attr_fnode_tcp_nagle_disable.attr) { + param = ISCSI_FLASHNODE_TCP_NAGLE_DISABLE; + } else if (attr == &dev_attr_fnode_tcp_wsf_disable.attr) { + param = ISCSI_FLASHNODE_TCP_WSF_DISABLE; + } else if (attr == &dev_attr_fnode_tcp_timer_scale.attr) { + param = ISCSI_FLASHNODE_TCP_TIMER_SCALE; + } else if (attr == &dev_attr_fnode_tcp_timestamp_enable.attr) { + param = ISCSI_FLASHNODE_TCP_TIMESTAMP_EN; + } else if (attr == &dev_attr_fnode_fragment_disable.attr) { + param = ISCSI_FLASHNODE_IP_FRAG_DISABLE; + } else if (attr == &dev_attr_fnode_max_recv_dlength.attr) { + param = ISCSI_FLASHNODE_MAX_RECV_DLENGTH; + } else if (attr == &dev_attr_fnode_max_xmit_dlength.attr) { + param = ISCSI_FLASHNODE_MAX_XMIT_DLENGTH; + } else if (attr == &dev_attr_fnode_keepalive_tmo.attr) { + param = ISCSI_FLASHNODE_KEEPALIVE_TMO; + } else if (attr == &dev_attr_fnode_port.attr) { + param = ISCSI_FLASHNODE_PORT; + } else if (attr == &dev_attr_fnode_ipaddress.attr) { + param = ISCSI_FLASHNODE_IPADDR; + } else if (attr == &dev_attr_fnode_redirect_ipaddr.attr) { + param = ISCSI_FLASHNODE_REDIRECT_IPADDR; + } else if (attr == &dev_attr_fnode_max_segment_size.attr) { + param = ISCSI_FLASHNODE_MAX_SEGMENT_SIZE; + } else if (attr == &dev_attr_fnode_local_port.attr) { + param = ISCSI_FLASHNODE_LOCAL_PORT; + } else if (attr == &dev_attr_fnode_ipv4_tos.attr) { + param = ISCSI_FLASHNODE_IPV4_TOS; + } else if (attr == &dev_attr_fnode_ipv6_traffic_class.attr) { + param = ISCSI_FLASHNODE_IPV6_TC; + } else if (attr == &dev_attr_fnode_ipv6_flow_label.attr) { + param = ISCSI_FLASHNODE_IPV6_FLOW_LABEL; + } else if (attr == &dev_attr_fnode_link_local_ipv6.attr) { + param = ISCSI_FLASHNODE_LINK_LOCAL_IPV6; + } else if (attr == &dev_attr_fnode_tcp_xmit_wsf.attr) { + param = ISCSI_FLASHNODE_TCP_XMIT_WSF; + } else if (attr == &dev_attr_fnode_tcp_recv_wsf.attr) { + param = ISCSI_FLASHNODE_TCP_RECV_WSF; + } else if (attr == &dev_attr_fnode_statsn.attr) { + param = ISCSI_FLASHNODE_STATSN; + } else if (attr == &dev_attr_fnode_exp_statsn.attr) { + param = ISCSI_FLASHNODE_EXP_STATSN; + } else { + WARN_ONCE(1, "Invalid flashnode connection attr"); + return 0; + } + + return t->attr_is_visible(ISCSI_FLASHNODE_PARAM, param); +} + +static struct attribute_group iscsi_flashnode_conn_attr_group = { + .attrs = iscsi_flashnode_conn_attrs, + .is_visible = iscsi_flashnode_conn_attr_is_visible, +}; + +static const struct attribute_group *iscsi_flashnode_conn_attr_groups[] = { + &iscsi_flashnode_conn_attr_group, + NULL, +}; + +static void iscsi_flashnode_conn_release(struct device *dev) +{ + struct iscsi_bus_flash_conn *fnode_conn = iscsi_dev_to_flash_conn(dev); + + kfree(fnode_conn->ipaddress); + kfree(fnode_conn->redirect_ipaddr); + kfree(fnode_conn->link_local_ipv6_addr); + kfree(fnode_conn); +} + +struct device_type iscsi_flashnode_conn_dev_type = { + .name = "iscsi_flashnode_conn_dev_type", + .groups = iscsi_flashnode_conn_attr_groups, + .release = iscsi_flashnode_conn_release, +}; + +struct bus_type iscsi_flashnode_bus; + +int iscsi_flashnode_bus_match(struct device *dev, + struct device_driver *drv) +{ + if (dev->bus == &iscsi_flashnode_bus) + return 1; + return 0; +} +EXPORT_SYMBOL_GPL(iscsi_flashnode_bus_match); + +struct bus_type iscsi_flashnode_bus = { + .name = "iscsi_flashnode", + .match = &iscsi_flashnode_bus_match, +}; + +/** + * iscsi_create_flashnode_sess - Add flashnode session entry in sysfs + * @shost: pointer to host data + * @index: index of flashnode to add in sysfs + * @transport: pointer to transport data + * @dd_size: total size to allocate + * + * Adds a sysfs entry for the flashnode session attributes + * + * Returns: + * pointer to allocated flashnode sess on sucess + * %NULL on failure + */ +struct iscsi_bus_flash_session * +iscsi_create_flashnode_sess(struct Scsi_Host *shost, int index, + struct iscsi_transport *transport, + int dd_size) +{ + struct iscsi_bus_flash_session *fnode_sess; + int err; + + fnode_sess = kzalloc(sizeof(*fnode_sess) + dd_size, GFP_KERNEL); + if (!fnode_sess) + return NULL; + + fnode_sess->transport = transport; + fnode_sess->target_id = index; + fnode_sess->dev.type = &iscsi_flashnode_sess_dev_type; + fnode_sess->dev.bus = &iscsi_flashnode_bus; + fnode_sess->dev.parent = &shost->shost_gendev; + dev_set_name(&fnode_sess->dev, "flashnode_sess-%u:%u", + shost->host_no, index); + + err = device_register(&fnode_sess->dev); + if (err) + goto free_fnode_sess; + + if (dd_size) + fnode_sess->dd_data = &fnode_sess[1]; + + return fnode_sess; + +free_fnode_sess: + kfree(fnode_sess); + return NULL; +} +EXPORT_SYMBOL_GPL(iscsi_create_flashnode_sess); + +/** + * iscsi_create_flashnode_conn - Add flashnode conn entry in sysfs + * @shost: pointer to host data + * @fnode_sess: pointer to the parent flashnode session entry + * @transport: pointer to transport data + * @dd_size: total size to allocate + * + * Adds a sysfs entry for the flashnode connection attributes + * + * Returns: + * pointer to allocated flashnode conn on success + * %NULL on failure + */ +struct iscsi_bus_flash_conn * +iscsi_create_flashnode_conn(struct Scsi_Host *shost, + struct iscsi_bus_flash_session *fnode_sess, + struct iscsi_transport *transport, + int dd_size) +{ + struct iscsi_bus_flash_conn *fnode_conn; + int err; + + fnode_conn = kzalloc(sizeof(*fnode_conn) + dd_size, GFP_KERNEL); + if (!fnode_conn) + return NULL; + + fnode_conn->transport = transport; + fnode_conn->dev.type = &iscsi_flashnode_conn_dev_type; + fnode_conn->dev.bus = &iscsi_flashnode_bus; + fnode_conn->dev.parent = &fnode_sess->dev; + dev_set_name(&fnode_conn->dev, "flashnode_conn-%u:%u:0", + shost->host_no, fnode_sess->target_id); + + err = device_register(&fnode_conn->dev); + if (err) + goto free_fnode_conn; + + if (dd_size) + fnode_conn->dd_data = &fnode_conn[1]; + + return fnode_conn; + +free_fnode_conn: + kfree(fnode_conn); + return NULL; +} +EXPORT_SYMBOL_GPL(iscsi_create_flashnode_conn); + +/** + * iscsi_is_flashnode_conn_dev - verify passed device is to be flashnode conn + * @dev: device to verify + * @data: pointer to data containing value to use for verification + * + * Verifies if the passed device is flashnode conn device + * + * Returns: + * 1 on success + * 0 on failure + */ +int iscsi_is_flashnode_conn_dev(struct device *dev, void *data) +{ + return dev->bus == &iscsi_flashnode_bus; +} +EXPORT_SYMBOL_GPL(iscsi_is_flashnode_conn_dev); + +static int iscsi_destroy_flashnode_conn(struct iscsi_bus_flash_conn *fnode_conn) +{ + device_unregister(&fnode_conn->dev); + return 0; +} + +static int flashnode_match_index(struct device *dev, void *data) +{ + struct iscsi_bus_flash_session *fnode_sess = NULL; + int ret = 0; + + if (!iscsi_flashnode_bus_match(dev, NULL)) + goto exit_match_index; + + fnode_sess = iscsi_dev_to_flash_session(dev); + ret = (fnode_sess->target_id == *((int *)data)) ? 1 : 0; + +exit_match_index: + return ret; +} + +/** + * iscsi_get_flashnode_by_index -finds flashnode session entry by index + * @shost: pointer to host data + * @data: pointer to data containing value to use for comparison + * @fn: function pointer that does actual comparison + * + * Finds the flashnode session object for the passed index + * + * Returns: + * pointer to found flashnode session object on success + * %NULL on failure + */ +static struct iscsi_bus_flash_session * +iscsi_get_flashnode_by_index(struct Scsi_Host *shost, void *data, + int (*fn)(struct device *dev, void *data)) +{ + struct iscsi_bus_flash_session *fnode_sess = NULL; + struct device *dev; + + dev = device_find_child(&shost->shost_gendev, data, fn); + if (dev) + fnode_sess = iscsi_dev_to_flash_session(dev); + + return fnode_sess; +} + +/** + * iscsi_find_flashnode_sess - finds flashnode session entry + * @shost: pointer to host data + * @data: pointer to data containing value to use for comparison + * @fn: function pointer that does actual comparison + * + * Finds the flashnode session object comparing the data passed using logic + * defined in passed function pointer + * + * Returns: + * pointer to found flashnode session device object on success + * %NULL on failure + */ +struct device * +iscsi_find_flashnode_sess(struct Scsi_Host *shost, void *data, + int (*fn)(struct device *dev, void *data)) +{ + struct device *dev; + + dev = device_find_child(&shost->shost_gendev, data, fn); + return dev; +} +EXPORT_SYMBOL_GPL(iscsi_find_flashnode_sess); + +/** + * iscsi_find_flashnode_conn - finds flashnode connection entry + * @fnode_sess: pointer to parent flashnode session entry + * @data: pointer to data containing value to use for comparison + * @fn: function pointer that does actual comparison + * + * Finds the flashnode connection object comparing the data passed using logic + * defined in passed function pointer + * + * Returns: + * pointer to found flashnode connection device object on success + * %NULL on failure + */ +struct device * +iscsi_find_flashnode_conn(struct iscsi_bus_flash_session *fnode_sess, + void *data, + int (*fn)(struct device *dev, void *data)) +{ + struct device *dev; + + dev = device_find_child(&fnode_sess->dev, data, fn); + return dev; +} +EXPORT_SYMBOL_GPL(iscsi_find_flashnode_conn); + +static int iscsi_iter_destroy_flashnode_conn_fn(struct device *dev, void *data) +{ + if (!iscsi_is_flashnode_conn_dev(dev, NULL)) + return 0; + + return iscsi_destroy_flashnode_conn(iscsi_dev_to_flash_conn(dev)); +} + +/** + * iscsi_destroy_flashnode_sess - destory flashnode session entry + * @fnode_sess: pointer to flashnode session entry to be destroyed + * + * Deletes the flashnode session entry and all children flashnode connection + * entries from sysfs + */ +void iscsi_destroy_flashnode_sess(struct iscsi_bus_flash_session *fnode_sess) +{ + int err; + + err = device_for_each_child(&fnode_sess->dev, NULL, + iscsi_iter_destroy_flashnode_conn_fn); + if (err) + pr_err("Could not delete all connections for %s. Error %d.\n", + fnode_sess->dev.kobj.name, err); + + device_unregister(&fnode_sess->dev); +} +EXPORT_SYMBOL_GPL(iscsi_destroy_flashnode_sess); + +static int iscsi_iter_destroy_flashnode_fn(struct device *dev, void *data) +{ + if (!iscsi_flashnode_bus_match(dev, NULL)) + return 0; + + iscsi_destroy_flashnode_sess(iscsi_dev_to_flash_session(dev)); + return 0; +} + +/** + * iscsi_destroy_all_flashnode - destory all flashnode session entries + * @shost: pointer to host data + * + * Destroys all the flashnode session entries and all corresponding children + * flashnode connection entries from sysfs + */ +void iscsi_destroy_all_flashnode(struct Scsi_Host *shost) +{ + device_for_each_child(&shost->shost_gendev, NULL, + iscsi_iter_destroy_flashnode_fn); +} +EXPORT_SYMBOL_GPL(iscsi_destroy_all_flashnode); + +/* * BSG support */ /** @@ -2092,6 +2776,294 @@ static int iscsi_delete_chap(struct iscsi_transport *transport, return err; } +static const struct { + enum iscsi_discovery_parent_type value; + char *name; +} iscsi_discovery_parent_names[] = { + {ISCSI_DISC_PARENT_UNKNOWN, "Unknown" }, + {ISCSI_DISC_PARENT_SENDTGT, "Sendtarget" }, + {ISCSI_DISC_PARENT_ISNS, "isns" }, +}; + +char *iscsi_get_discovery_parent_name(int parent_type) +{ + int i; + char *state = "Unknown!"; + + for (i = 0; i < ARRAY_SIZE(iscsi_discovery_parent_names); i++) { + if (iscsi_discovery_parent_names[i].value & parent_type) { + state = iscsi_discovery_parent_names[i].name; + break; + } + } + return state; +} +EXPORT_SYMBOL_GPL(iscsi_get_discovery_parent_name); + +static int iscsi_set_flashnode_param(struct iscsi_transport *transport, + struct iscsi_uevent *ev, uint32_t len) +{ + char *data = (char *)ev + sizeof(*ev); + struct Scsi_Host *shost; + struct iscsi_bus_flash_session *fnode_sess; + struct iscsi_bus_flash_conn *fnode_conn; + struct device *dev; + uint32_t *idx; + int err = 0; + + if (!transport->set_flashnode_param) { + err = -ENOSYS; + goto exit_set_fnode; + } + + shost = scsi_host_lookup(ev->u.set_flashnode.host_no); + if (!shost) { + pr_err("%s could not find host no %u\n", + __func__, ev->u.set_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + idx = &ev->u.set_flashnode.flashnode_idx; + fnode_sess = iscsi_get_flashnode_by_index(shost, idx, + flashnode_match_index); + if (!fnode_sess) { + pr_err("%s could not find flashnode %u for host no %u\n", + __func__, *idx, ev->u.set_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + dev = iscsi_find_flashnode_conn(fnode_sess, NULL, + iscsi_is_flashnode_conn_dev); + if (!dev) { + err = -ENODEV; + goto put_host; + } + + fnode_conn = iscsi_dev_to_flash_conn(dev); + err = transport->set_flashnode_param(fnode_sess, fnode_conn, data, len); + +put_host: + scsi_host_put(shost); + +exit_set_fnode: + return err; +} + +static int iscsi_new_flashnode(struct iscsi_transport *transport, + struct iscsi_uevent *ev, uint32_t len) +{ + char *data = (char *)ev + sizeof(*ev); + struct Scsi_Host *shost; + int index; + int err = 0; + + if (!transport->new_flashnode) { + err = -ENOSYS; + goto exit_new_fnode; + } + + shost = scsi_host_lookup(ev->u.new_flashnode.host_no); + if (!shost) { + pr_err("%s could not find host no %u\n", + __func__, ev->u.new_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + index = transport->new_flashnode(shost, data, len); + + if (index >= 0) + ev->r.new_flashnode_ret.flashnode_idx = index; + else + err = -EIO; + +put_host: + scsi_host_put(shost); + +exit_new_fnode: + return err; +} + +static int iscsi_del_flashnode(struct iscsi_transport *transport, + struct iscsi_uevent *ev) +{ + struct Scsi_Host *shost; + struct iscsi_bus_flash_session *fnode_sess; + uint32_t *idx; + int err = 0; + + if (!transport->del_flashnode) { + err = -ENOSYS; + goto exit_del_fnode; + } + + shost = scsi_host_lookup(ev->u.del_flashnode.host_no); + if (!shost) { + pr_err("%s could not find host no %u\n", + __func__, ev->u.del_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + idx = &ev->u.del_flashnode.flashnode_idx; + fnode_sess = iscsi_get_flashnode_by_index(shost, idx, + flashnode_match_index); + if (!fnode_sess) { + pr_err("%s could not find flashnode %u for host no %u\n", + __func__, *idx, ev->u.del_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + err = transport->del_flashnode(fnode_sess); + +put_host: + scsi_host_put(shost); + +exit_del_fnode: + return err; +} + +static int iscsi_login_flashnode(struct iscsi_transport *transport, + struct iscsi_uevent *ev) +{ + struct Scsi_Host *shost; + struct iscsi_bus_flash_session *fnode_sess; + struct iscsi_bus_flash_conn *fnode_conn; + struct device *dev; + uint32_t *idx; + int err = 0; + + if (!transport->login_flashnode) { + err = -ENOSYS; + goto exit_login_fnode; + } + + shost = scsi_host_lookup(ev->u.login_flashnode.host_no); + if (!shost) { + pr_err("%s could not find host no %u\n", + __func__, ev->u.login_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + idx = &ev->u.login_flashnode.flashnode_idx; + fnode_sess = iscsi_get_flashnode_by_index(shost, idx, + flashnode_match_index); + if (!fnode_sess) { + pr_err("%s could not find flashnode %u for host no %u\n", + __func__, *idx, ev->u.login_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + dev = iscsi_find_flashnode_conn(fnode_sess, NULL, + iscsi_is_flashnode_conn_dev); + if (!dev) { + err = -ENODEV; + goto put_host; + } + + fnode_conn = iscsi_dev_to_flash_conn(dev); + err = transport->login_flashnode(fnode_sess, fnode_conn); + +put_host: + scsi_host_put(shost); + +exit_login_fnode: + return err; +} + +static int iscsi_logout_flashnode(struct iscsi_transport *transport, + struct iscsi_uevent *ev) +{ + struct Scsi_Host *shost; + struct iscsi_bus_flash_session *fnode_sess; + struct iscsi_bus_flash_conn *fnode_conn; + struct device *dev; + uint32_t *idx; + int err = 0; + + if (!transport->logout_flashnode) { + err = -ENOSYS; + goto exit_logout_fnode; + } + + shost = scsi_host_lookup(ev->u.logout_flashnode.host_no); + if (!shost) { + pr_err("%s could not find host no %u\n", + __func__, ev->u.logout_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + idx = &ev->u.logout_flashnode.flashnode_idx; + fnode_sess = iscsi_get_flashnode_by_index(shost, idx, + flashnode_match_index); + if (!fnode_sess) { + pr_err("%s could not find flashnode %u for host no %u\n", + __func__, *idx, ev->u.logout_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + dev = iscsi_find_flashnode_conn(fnode_sess, NULL, + iscsi_is_flashnode_conn_dev); + if (!dev) { + err = -ENODEV; + goto put_host; + } + + fnode_conn = iscsi_dev_to_flash_conn(dev); + + err = transport->logout_flashnode(fnode_sess, fnode_conn); + +put_host: + scsi_host_put(shost); + +exit_logout_fnode: + return err; +} + +static int iscsi_logout_flashnode_sid(struct iscsi_transport *transport, + struct iscsi_uevent *ev) +{ + struct Scsi_Host *shost; + struct iscsi_cls_session *session; + int err = 0; + + if (!transport->logout_flashnode_sid) { + err = -ENOSYS; + goto exit_logout_sid; + } + + shost = scsi_host_lookup(ev->u.logout_flashnode_sid.host_no); + if (!shost) { + pr_err("%s could not find host no %u\n", + __func__, ev->u.logout_flashnode.host_no); + err = -ENODEV; + goto put_host; + } + + session = iscsi_session_lookup(ev->u.logout_flashnode_sid.sid); + if (!session) { + pr_err("%s could not find session id %u\n", + __func__, ev->u.logout_flashnode_sid.sid); + err = -EINVAL; + goto put_host; + } + + err = transport->logout_flashnode_sid(session); + +put_host: + scsi_host_put(shost); + +exit_logout_sid: + return err; +} + static int iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, uint32_t *group) { @@ -2246,6 +3218,27 @@ iscsi_if_recv_msg(struct sk_buff *skb, struct nlmsghdr *nlh, uint32_t *group) case ISCSI_UEVENT_DELETE_CHAP: err = iscsi_delete_chap(transport, ev); break; + case ISCSI_UEVENT_SET_FLASHNODE_PARAMS: + err = iscsi_set_flashnode_param(transport, ev, + nlmsg_attrlen(nlh, + sizeof(*ev))); + break; + case ISCSI_UEVENT_NEW_FLASHNODE: + err = iscsi_new_flashnode(transport, ev, + nlmsg_attrlen(nlh, sizeof(*ev))); + break; + case ISCSI_UEVENT_DEL_FLASHNODE: + err = iscsi_del_flashnode(transport, ev); + break; + case ISCSI_UEVENT_LOGIN_FLASHNODE: + err = iscsi_login_flashnode(transport, ev); + break; + case ISCSI_UEVENT_LOGOUT_FLASHNODE: + err = iscsi_logout_flashnode(transport, ev); + break; + case ISCSI_UEVENT_LOGOUT_FLASHNODE_SID: + err = iscsi_logout_flashnode_sid(transport, ev); + break; default: err = -ENOSYS; break; @@ -2981,10 +3974,14 @@ static __init int iscsi_transport_init(void) if (err) goto unregister_conn_class; + err = bus_register(&iscsi_flashnode_bus); + if (err) + goto unregister_session_class; + nls = netlink_kernel_create(&init_net, NETLINK_ISCSI, &cfg); if (!nls) { err = -ENOBUFS; - goto unregister_session_class; + goto unregister_flashnode_bus; } iscsi_eh_timer_workq = create_singlethread_workqueue("iscsi_eh"); @@ -2995,6 +3992,8 @@ static __init int iscsi_transport_init(void) release_nls: netlink_kernel_release(nls); +unregister_flashnode_bus: + bus_unregister(&iscsi_flashnode_bus); unregister_session_class: transport_class_unregister(&iscsi_session_class); unregister_conn_class: @@ -3014,6 +4013,7 @@ static void __exit iscsi_transport_exit(void) { destroy_workqueue(iscsi_eh_timer_workq); netlink_kernel_release(nls); + bus_unregister(&iscsi_flashnode_bus); transport_class_unregister(&iscsi_connection_class); transport_class_unregister(&iscsi_session_class); transport_class_unregister(&iscsi_host_class); diff --git a/drivers/target/iscsi/Makefile b/drivers/target/iscsi/Makefile index 5b9a2cf7f0a..13a92403fe3 100644 --- a/drivers/target/iscsi/Makefile +++ b/drivers/target/iscsi/Makefile @@ -15,6 +15,7 @@ iscsi_target_mod-y += iscsi_target_parameters.o \ iscsi_target_util.o \ iscsi_target.o \ iscsi_target_configfs.o \ - iscsi_target_stat.o + iscsi_target_stat.o \ + iscsi_target_transport.o obj-$(CONFIG_ISCSI_TARGET) += iscsi_target_mod.o diff --git a/drivers/target/iscsi/iscsi_target.c b/drivers/target/iscsi/iscsi_target.c index 7ea246a0773..ffbc6a94be5 100644 --- a/drivers/target/iscsi/iscsi_target.c +++ b/drivers/target/iscsi/iscsi_target.c @@ -49,6 +49,8 @@ #include "iscsi_target_device.h" #include "iscsi_target_stat.h" +#include <target/iscsi/iscsi_transport.h> + static LIST_HEAD(g_tiqn_list); static LIST_HEAD(g_np_list); static DEFINE_SPINLOCK(tiqn_lock); @@ -68,8 +70,7 @@ struct kmem_cache *lio_ooo_cache; struct kmem_cache *lio_r2t_cache; static int iscsit_handle_immediate_data(struct iscsi_cmd *, - unsigned char *buf, u32); -static int iscsit_logout_post_handler(struct iscsi_cmd *, struct iscsi_conn *); + struct iscsi_scsi_req *, u32); struct iscsi_tiqn *iscsit_get_tiqn_for_login(unsigned char *buf) { @@ -401,8 +402,7 @@ struct iscsi_np *iscsit_add_np( spin_unlock_bh(&np_lock); pr_debug("CORE[0] - Added Network Portal: %s:%hu on %s\n", - np->np_ip, np->np_port, (np->np_network_transport == ISCSI_TCP) ? - "TCP" : "SCTP"); + np->np_ip, np->np_port, np->np_transport->name); return np; } @@ -441,11 +441,10 @@ int iscsit_reset_np_thread( return 0; } -static int iscsit_del_np_comm(struct iscsi_np *np) +static void iscsit_free_np(struct iscsi_np *np) { if (np->np_socket) sock_release(np->np_socket); - return 0; } int iscsit_del_np(struct iscsi_np *np) @@ -467,20 +466,47 @@ int iscsit_del_np(struct iscsi_np *np) send_sig(SIGINT, np->np_thread, 1); kthread_stop(np->np_thread); } - iscsit_del_np_comm(np); + + np->np_transport->iscsit_free_np(np); spin_lock_bh(&np_lock); list_del(&np->np_list); spin_unlock_bh(&np_lock); pr_debug("CORE[0] - Removed Network Portal: %s:%hu on %s\n", - np->np_ip, np->np_port, (np->np_network_transport == ISCSI_TCP) ? - "TCP" : "SCTP"); + np->np_ip, np->np_port, np->np_transport->name); + iscsit_put_transport(np->np_transport); kfree(np); return 0; } +static int iscsit_immediate_queue(struct iscsi_conn *, struct iscsi_cmd *, int); +static int iscsit_response_queue(struct iscsi_conn *, struct iscsi_cmd *, int); + +static int iscsit_queue_rsp(struct iscsi_conn *conn, struct iscsi_cmd *cmd) +{ + iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state); + return 0; +} + +static struct iscsit_transport iscsi_target_transport = { + .name = "iSCSI/TCP", + .transport_type = ISCSI_TCP, + .owner = NULL, + .iscsit_setup_np = iscsit_setup_np, + .iscsit_accept_np = iscsit_accept_np, + .iscsit_free_np = iscsit_free_np, + .iscsit_alloc_cmd = iscsit_alloc_cmd, + .iscsit_get_login_rx = iscsit_get_login_rx, + .iscsit_put_login_tx = iscsit_put_login_tx, + .iscsit_get_dataout = iscsit_build_r2ts_for_cmd, + .iscsit_immediate_queue = iscsit_immediate_queue, + .iscsit_response_queue = iscsit_response_queue, + .iscsit_queue_data_in = iscsit_queue_rsp, + .iscsit_queue_status = iscsit_queue_rsp, +}; + static int __init iscsi_target_init_module(void) { int ret = 0; @@ -557,6 +583,8 @@ static int __init iscsi_target_init_module(void) goto ooo_out; } + iscsit_register_transport(&iscsi_target_transport); + if (iscsit_load_discovery_tpg() < 0) goto r2t_out; @@ -587,6 +615,7 @@ static void __exit iscsi_target_cleanup_module(void) iscsi_deallocate_thread_sets(); iscsi_thread_set_free(); iscsit_release_discovery_tpg(); + iscsit_unregister_transport(&iscsi_target_transport); kmem_cache_destroy(lio_cmd_cache); kmem_cache_destroy(lio_qr_cache); kmem_cache_destroy(lio_dr_cache); @@ -682,11 +711,20 @@ int iscsit_add_reject_from_cmd( iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); ret = wait_for_completion_interruptible(&cmd->reject_comp); + /* + * Perform the kref_put now if se_cmd has already been setup by + * scsit_setup_scsi_cmd() + */ + if (cmd->se_cmd.se_tfo != NULL) { + pr_debug("iscsi reject: calling target_put_sess_cmd >>>>>>\n"); + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); + } if (ret != 0) return -1; return (!fail_conn) ? 0 : -1; } +EXPORT_SYMBOL(iscsit_add_reject_from_cmd); /* * Map some portion of the allocated scatterlist to an iovec, suitable for @@ -745,6 +783,9 @@ static void iscsit_ack_from_expstatsn(struct iscsi_conn *conn, u32 exp_statsn) conn->exp_statsn = exp_statsn; + if (conn->sess->sess_ops->RDMAExtensions) + return; + spin_lock_bh(&conn->cmd_lock); list_for_each_entry(cmd, &conn->conn_cmd_list, i_conn_node) { spin_lock(&cmd->istate_lock); @@ -777,12 +818,10 @@ static int iscsit_allocate_iovecs(struct iscsi_cmd *cmd) return 0; } -static int iscsit_handle_scsi_cmd( - struct iscsi_conn *conn, - unsigned char *buf) +int iscsit_setup_scsi_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) { - int data_direction, payload_length, cmdsn_ret = 0, immed_ret; - struct iscsi_cmd *cmd = NULL; + int data_direction, payload_length; struct iscsi_scsi_req *hdr; int iscsi_task_attr; int sam_task_attr; @@ -805,8 +844,8 @@ static int iscsit_handle_scsi_cmd( !(hdr->flags & ISCSI_FLAG_CMD_FINAL)) { pr_err("ISCSI_FLAG_CMD_WRITE & ISCSI_FLAG_CMD_FINAL" " not set. Bad iSCSI Initiator.\n"); - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_INVALID, + 1, 1, buf, cmd); } if (((hdr->flags & ISCSI_FLAG_CMD_READ) || @@ -826,8 +865,8 @@ static int iscsit_handle_scsi_cmd( pr_err("ISCSI_FLAG_CMD_READ or ISCSI_FLAG_CMD_WRITE" " set when Expected Data Transfer Length is 0 for" " CDB: 0x%02x. Bad iSCSI Initiator.\n", hdr->cdb[0]); - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_INVALID, + 1, 1, buf, cmd); } done: @@ -836,29 +875,29 @@ done: pr_err("ISCSI_FLAG_CMD_READ and/or ISCSI_FLAG_CMD_WRITE" " MUST be set if Expected Data Transfer Length is not 0." " Bad iSCSI Initiator\n"); - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_INVALID, + 1, 1, buf, cmd); } if ((hdr->flags & ISCSI_FLAG_CMD_READ) && (hdr->flags & ISCSI_FLAG_CMD_WRITE)) { pr_err("Bidirectional operations not supported!\n"); - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_INVALID, + 1, 1, buf, cmd); } if (hdr->opcode & ISCSI_OP_IMMEDIATE) { pr_err("Illegally set Immediate Bit in iSCSI Initiator" " Scsi Command PDU.\n"); - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_INVALID, + 1, 1, buf, cmd); } if (payload_length && !conn->sess->sess_ops->ImmediateData) { pr_err("ImmediateData=No but DataSegmentLength=%u," " protocol error.\n", payload_length); - return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_PROTOCOL_ERROR, + 1, 1, buf, cmd); } if ((be32_to_cpu(hdr->data_length )== payload_length) && @@ -866,43 +905,38 @@ done: pr_err("Expected Data Transfer Length and Length of" " Immediate Data are the same, but ISCSI_FLAG_CMD_FINAL" " bit is not set protocol error\n"); - return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_PROTOCOL_ERROR, + 1, 1, buf, cmd); } if (payload_length > be32_to_cpu(hdr->data_length)) { pr_err("DataSegmentLength: %u is greater than" " EDTL: %u, protocol error.\n", payload_length, hdr->data_length); - return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_PROTOCOL_ERROR, + 1, 1, buf, cmd); } if (payload_length > conn->conn_ops->MaxXmitDataSegmentLength) { pr_err("DataSegmentLength: %u is greater than" " MaxXmitDataSegmentLength: %u, protocol error.\n", payload_length, conn->conn_ops->MaxXmitDataSegmentLength); - return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_PROTOCOL_ERROR, + 1, 1, buf, cmd); } if (payload_length > conn->sess->sess_ops->FirstBurstLength) { pr_err("DataSegmentLength: %u is greater than" " FirstBurstLength: %u, protocol error.\n", payload_length, conn->sess->sess_ops->FirstBurstLength); - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_INVALID, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_BOOKMARK_INVALID, + 1, 1, buf, cmd); } data_direction = (hdr->flags & ISCSI_FLAG_CMD_WRITE) ? DMA_TO_DEVICE : (hdr->flags & ISCSI_FLAG_CMD_READ) ? DMA_FROM_DEVICE : DMA_NONE; - cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); - if (!cmd) - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, 1, - buf, conn); - cmd->data_direction = data_direction; iscsi_task_attr = hdr->flags & ISCSI_FLAG_CMD_ATTR_MASK; /* @@ -945,7 +979,8 @@ done: cmd->exp_stat_sn = be32_to_cpu(hdr->exp_statsn); cmd->first_burst_len = payload_length; - if (cmd->data_direction == DMA_FROM_DEVICE) { + if (!conn->sess->sess_ops->RDMAExtensions && + cmd->data_direction == DMA_FROM_DEVICE) { struct iscsi_datain_req *dr; dr = iscsit_allocate_datain_req(); @@ -967,7 +1002,10 @@ done: pr_debug("Got SCSI Command, ITT: 0x%08x, CmdSN: 0x%08x," " ExpXferLen: %u, Length: %u, CID: %hu\n", hdr->itt, - hdr->cmdsn, hdr->data_length, payload_length, conn->cid); + hdr->cmdsn, be32_to_cpu(hdr->data_length), payload_length, + conn->cid); + + target_get_sess_cmd(conn->sess->se_sess, &cmd->se_cmd, true); cmd->sense_reason = transport_lookup_cmd_lun(&cmd->se_cmd, scsilun_to_int(&hdr->lun)); @@ -1001,12 +1039,24 @@ attach_cmd: */ core_alua_check_nonop_delay(&cmd->se_cmd); - if (iscsit_allocate_iovecs(cmd) < 0) { - return iscsit_add_reject_from_cmd( - ISCSI_REASON_BOOKMARK_NO_RESOURCES, - 1, 0, buf, cmd); - } + return 0; +} +EXPORT_SYMBOL(iscsit_setup_scsi_cmd); + +void iscsit_set_unsoliticed_dataout(struct iscsi_cmd *cmd) +{ + iscsit_set_dataout_sequence_values(cmd); + + spin_lock_bh(&cmd->dataout_timeout_lock); + iscsit_start_dataout_timer(cmd, cmd->conn); + spin_unlock_bh(&cmd->dataout_timeout_lock); +} +EXPORT_SYMBOL(iscsit_set_unsoliticed_dataout); +int iscsit_process_scsi_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_scsi_req *hdr) +{ + int cmdsn_ret = 0; /* * Check the CmdSN against ExpCmdSN/MaxCmdSN here if * the Immediate Bit is not set, and no Immediate @@ -1019,12 +1069,17 @@ attach_cmd: */ if (!cmd->immediate_data) { cmdsn_ret = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); - if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) + if (cmdsn_ret == CMDSN_LOWER_THAN_EXP) { + if (!cmd->sense_reason) + return 0; + + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); return 0; - else if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) + } else if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) { return iscsit_add_reject_from_cmd( ISCSI_REASON_PROTOCOL_ERROR, - 1, 0, buf, cmd); + 1, 0, (unsigned char *)hdr, cmd); + } } iscsit_ack_from_expstatsn(conn, be32_to_cpu(hdr->exp_statsn)); @@ -1033,25 +1088,23 @@ attach_cmd: * If no Immediate Data is attached, it's OK to return now. */ if (!cmd->immediate_data) { - if (!cmd->sense_reason && cmd->unsolicited_data) { - iscsit_set_dataout_sequence_values(cmd); - - spin_lock_bh(&cmd->dataout_timeout_lock); - iscsit_start_dataout_timer(cmd, cmd->conn); - spin_unlock_bh(&cmd->dataout_timeout_lock); - } + if (!cmd->sense_reason && cmd->unsolicited_data) + iscsit_set_unsoliticed_dataout(cmd); + if (!cmd->sense_reason) + return 0; + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); return 0; } /* - * Early CHECK_CONDITIONs never make it to the transport processing - * thread. They are processed in CmdSN order by - * iscsit_check_received_cmdsn() below. + * Early CHECK_CONDITIONs with ImmediateData never make it to command + * execution. These exceptions are processed in CmdSN order using + * iscsit_check_received_cmdsn() in iscsit_get_immediate_data() below. */ if (cmd->sense_reason) { - immed_ret = IMMEDIATE_DATA_NORMAL_OPERATION; - goto after_immediate_data; + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); + return 1; } /* * Call directly into transport_generic_new_cmd() to perform @@ -1059,11 +1112,27 @@ attach_cmd: */ cmd->sense_reason = transport_generic_new_cmd(&cmd->se_cmd); if (cmd->sense_reason) { - immed_ret = IMMEDIATE_DATA_NORMAL_OPERATION; - goto after_immediate_data; + target_put_sess_cmd(conn->sess->se_sess, &cmd->se_cmd); + return 1; } - immed_ret = iscsit_handle_immediate_data(cmd, buf, payload_length); + return 0; +} +EXPORT_SYMBOL(iscsit_process_scsi_cmd); + +static int +iscsit_get_immediate_data(struct iscsi_cmd *cmd, struct iscsi_scsi_req *hdr, + bool dump_payload) +{ + int cmdsn_ret = 0, immed_ret = IMMEDIATE_DATA_NORMAL_OPERATION; + /* + * Special case for Unsupported SAM WRITE Opcodes and ImmediateData=Yes. + */ + if (dump_payload == true) + goto after_immediate_data; + + immed_ret = iscsit_handle_immediate_data(cmd, hdr, + cmd->first_burst_len); after_immediate_data: if (immed_ret == IMMEDIATE_DATA_NORMAL_OPERATION) { /* @@ -1071,26 +1140,19 @@ after_immediate_data: * DataCRC, check against ExpCmdSN/MaxCmdSN if * Immediate Bit is not set. */ - cmdsn_ret = iscsit_sequence_cmd(conn, cmd, hdr->cmdsn); - /* - * Special case for Unsupported SAM WRITE Opcodes - * and ImmediateData=Yes. - */ + cmdsn_ret = iscsit_sequence_cmd(cmd->conn, cmd, hdr->cmdsn); + if (cmd->sense_reason) { - if (iscsit_dump_data_payload(conn, payload_length, 1) < 0) + if (iscsit_dump_data_payload(cmd->conn, + cmd->first_burst_len, 1) < 0) return -1; - } else if (cmd->unsolicited_data) { - iscsit_set_dataout_sequence_values(cmd); - - spin_lock_bh(&cmd->dataout_timeout_lock); - iscsit_start_dataout_timer(cmd, cmd->conn); - spin_unlock_bh(&cmd->dataout_timeout_lock); - } + } else if (cmd->unsolicited_data) + iscsit_set_unsoliticed_dataout(cmd); if (cmdsn_ret == CMDSN_ERROR_CANNOT_RECOVER) return iscsit_add_reject_from_cmd( ISCSI_REASON_PROTOCOL_ERROR, - 1, 0, buf, cmd); + 1, 0, (unsigned char *)hdr, cmd); } else if (immed_ret == IMMEDIATE_DATA_ERL1_CRC_FAILURE) { /* @@ -1105,13 +1167,47 @@ after_immediate_data: * CmdSN and issue a retry to plug the sequence. */ cmd->i_state = ISTATE_REMOVE; - iscsit_add_cmd_to_immediate_queue(cmd, conn, cmd->i_state); + iscsit_add_cmd_to_immediate_queue(cmd, cmd->conn, cmd->i_state); } else /* immed_ret == IMMEDIATE_DATA_CANNOT_RECOVER */ return -1; return 0; } +static int +iscsit_handle_scsi_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) +{ + struct iscsi_scsi_req *hdr = (struct iscsi_scsi_req *)buf; + int rc, immed_data; + bool dump_payload = false; + + rc = iscsit_setup_scsi_cmd(conn, cmd, buf); + if (rc < 0) + return rc; + /* + * Allocation iovecs needed for struct socket operations for + * traditional iSCSI block I/O. + */ + if (iscsit_allocate_iovecs(cmd) < 0) { + return iscsit_add_reject_from_cmd( + ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, 0, buf, cmd); + } + immed_data = cmd->immediate_data; + + rc = iscsit_process_scsi_cmd(conn, cmd, hdr); + if (rc < 0) + return rc; + else if (rc > 0) + dump_payload = true; + + if (!immed_data) + return 0; + + return iscsit_get_immediate_data(cmd, hdr, dump_payload); +} + static u32 iscsit_do_crypto_hash_sg( struct hash_desc *hash, struct iscsi_cmd *cmd, @@ -1174,20 +1270,16 @@ static void iscsit_do_crypto_hash_buf( crypto_hash_final(hash, data_crc); } -static int iscsit_handle_data_out(struct iscsi_conn *conn, unsigned char *buf) +int +iscsit_check_dataout_hdr(struct iscsi_conn *conn, unsigned char *buf, + struct iscsi_cmd **out_cmd) { - int iov_ret, ooo_cmdsn = 0, ret; - u8 data_crc_failed = 0; - u32 checksum, iov_count = 0, padding = 0, rx_got = 0; - u32 rx_size = 0, payload_length; + struct iscsi_data *hdr = (struct iscsi_data *)buf; struct iscsi_cmd *cmd = NULL; struct se_cmd *se_cmd; - struct iscsi_data *hdr; - struct kvec *iov; unsigned long flags; - - hdr = (struct iscsi_data *) buf; - payload_length = ntoh24(hdr->dlength); + u32 payload_length = ntoh24(hdr->dlength); + int rc; if (!payload_length) { pr_err("DataOUT payload is ZERO, protocol error.\n"); @@ -1220,7 +1312,7 @@ static int iscsit_handle_data_out(struct iscsi_conn *conn, unsigned char *buf) pr_debug("Got DataOut ITT: 0x%08x, TTT: 0x%08x," " DataSN: 0x%08x, Offset: %u, Length: %u, CID: %hu\n", - hdr->itt, hdr->ttt, hdr->datasn, hdr->offset, + hdr->itt, hdr->ttt, hdr->datasn, ntohl(hdr->offset), payload_length, conn->cid); if (cmd->cmd_flags & ICF_GOT_LAST_DATAOUT) { @@ -1312,12 +1404,26 @@ static int iscsit_handle_data_out(struct iscsi_conn *conn, unsigned char *buf) * Preform DataSN, DataSequenceInOrder, DataPDUInOrder, and * within-command recovery checks before receiving the payload. */ - ret = iscsit_check_pre_dataout(cmd, buf); - if (ret == DATAOUT_WITHIN_COMMAND_RECOVERY) + rc = iscsit_check_pre_dataout(cmd, buf); + if (rc == DATAOUT_WITHIN_COMMAND_RECOVERY) return 0; - else if (ret == DATAOUT_CANNOT_RECOVER) + else if (rc == DATAOUT_CANNOT_RECOVER) return -1; + *out_cmd = cmd; + return 0; +} +EXPORT_SYMBOL(iscsit_check_dataout_hdr); + +static int +iscsit_get_dataout(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + struct iscsi_data *hdr) +{ + struct kvec *iov; + u32 checksum, iov_count = 0, padding = 0, rx_got = 0, rx_size = 0; + u32 payload_length = ntoh24(hdr->dlength); + int iov_ret, data_crc_failed = 0; + rx_size += payload_length; iov = &cmd->iov_data[0]; @@ -1370,17 +1476,27 @@ static int iscsit_handle_data_out(struct iscsi_conn *conn, unsigned char *buf) payload_length); } } + + return data_crc_failed; +} + +int +iscsit_check_dataout_payload(struct iscsi_cmd *cmd, struct iscsi_data *hdr, + bool data_crc_failed) +{ + struct iscsi_conn *conn = cmd->conn; + int rc, ooo_cmdsn; /* * Increment post receive data and CRC values or perform * within-command recovery. */ - ret = iscsit_check_post_dataout(cmd, buf, data_crc_failed); - if ((ret == DATAOUT_NORMAL) || (ret == DATAOUT_WITHIN_COMMAND_RECOVERY)) + rc = iscsit_check_post_dataout(cmd, (unsigned char *)hdr, data_crc_failed); + if ((rc == DATAOUT_NORMAL) || (rc == DATAOUT_WITHIN_COMMAND_RECOVERY)) return 0; - else if (ret == DATAOUT_SEND_R2T) { + else if (rc == DATAOUT_SEND_R2T) { iscsit_set_dataout_sequence_values(cmd); - iscsit_build_r2ts_for_cmd(cmd, conn, false); - } else if (ret == DATAOUT_SEND_TO_TRANSPORT) { + conn->conn_transport->iscsit_get_dataout(conn, cmd, false); + } else if (rc == DATAOUT_SEND_TO_TRANSPORT) { /* * Handle extra special case for out of order * Unsolicited Data Out. @@ -1401,15 +1517,37 @@ static int iscsit_handle_data_out(struct iscsi_conn *conn, unsigned char *buf) return 0; } +EXPORT_SYMBOL(iscsit_check_dataout_payload); -static int iscsit_handle_nop_out( - struct iscsi_conn *conn, - unsigned char *buf) +static int iscsit_handle_data_out(struct iscsi_conn *conn, unsigned char *buf) +{ + struct iscsi_cmd *cmd; + struct iscsi_data *hdr = (struct iscsi_data *)buf; + int rc; + bool data_crc_failed = false; + + rc = iscsit_check_dataout_hdr(conn, buf, &cmd); + if (rc < 0) + return rc; + else if (!cmd) + return 0; + + rc = iscsit_get_dataout(conn, cmd, hdr); + if (rc < 0) + return rc; + else if (rc > 0) + data_crc_failed = true; + + return iscsit_check_dataout_payload(cmd, hdr, data_crc_failed); +} + +int iscsit_handle_nop_out(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) { unsigned char *ping_data = NULL; int cmdsn_ret, niov = 0, ret = 0, rx_got, rx_size; u32 checksum, data_crc, padding = 0, payload_length; - struct iscsi_cmd *cmd = NULL; + struct iscsi_cmd *cmd_p = NULL; struct kvec *iov = NULL; struct iscsi_nopout *hdr; @@ -1432,7 +1570,7 @@ static int iscsit_handle_nop_out( buf, conn); } - pr_debug("Got NOPOUT Ping %s ITT: 0x%08x, TTT: 0x%09x," + pr_debug("Got NOPOUT Ping %s ITT: 0x%08x, TTT: 0x%08x," " CmdSN: 0x%08x, ExpStatSN: 0x%08x, Length: %u\n", hdr->itt == RESERVED_ITT ? "Response" : "Request", hdr->itt, hdr->ttt, hdr->cmdsn, hdr->exp_statsn, @@ -1445,7 +1583,6 @@ static int iscsit_handle_nop_out( * can contain ping data. */ if (hdr->ttt == cpu_to_be32(0xFFFFFFFF)) { - cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); if (!cmd) return iscsit_add_reject( ISCSI_REASON_BOOKMARK_NO_RESOURCES, @@ -1580,14 +1717,14 @@ static int iscsit_handle_nop_out( /* * This was a response to a unsolicited NOPIN ping. */ - cmd = iscsit_find_cmd_from_ttt(conn, be32_to_cpu(hdr->ttt)); - if (!cmd) + cmd_p = iscsit_find_cmd_from_ttt(conn, be32_to_cpu(hdr->ttt)); + if (!cmd_p) return -1; iscsit_stop_nopin_response_timer(conn); - cmd->i_state = ISTATE_REMOVE; - iscsit_add_cmd_to_immediate_queue(cmd, conn, cmd->i_state); + cmd_p->i_state = ISTATE_REMOVE; + iscsit_add_cmd_to_immediate_queue(cmd_p, conn, cmd_p->i_state); iscsit_start_nopin_timer(conn); } else { /* @@ -1611,12 +1748,12 @@ ping_out: kfree(ping_data); return ret; } +EXPORT_SYMBOL(iscsit_handle_nop_out); -static int iscsit_handle_task_mgt_cmd( - struct iscsi_conn *conn, - unsigned char *buf) +int +iscsit_handle_task_mgt_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) { - struct iscsi_cmd *cmd; struct se_tmr_req *se_tmr; struct iscsi_tmr_req *tmr_req; struct iscsi_tm *hdr; @@ -1645,18 +1782,13 @@ static int iscsit_handle_task_mgt_cmd( pr_err("Task Management Request TASK_REASSIGN not" " issued as immediate command, bad iSCSI Initiator" "implementation\n"); - return iscsit_add_reject(ISCSI_REASON_PROTOCOL_ERROR, 1, - buf, conn); + return iscsit_add_reject_from_cmd(ISCSI_REASON_PROTOCOL_ERROR, + 1, 1, buf, cmd); } if ((function != ISCSI_TM_FUNC_ABORT_TASK) && be32_to_cpu(hdr->refcmdsn) != ISCSI_RESERVED_TAG) hdr->refcmdsn = cpu_to_be32(ISCSI_RESERVED_TAG); - cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); - if (!cmd) - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, - 1, buf, conn); - cmd->data_direction = DMA_NONE; cmd->tmr_req = kzalloc(sizeof(struct iscsi_tmr_req), GFP_KERNEL); @@ -1827,6 +1959,7 @@ attach: iscsit_add_cmd_to_response_queue(cmd, conn, cmd->i_state); return 0; } +EXPORT_SYMBOL(iscsit_handle_task_mgt_cmd); /* #warning FIXME: Support Text Command parameters besides SendTargets */ static int iscsit_handle_text_cmd( @@ -2089,13 +2222,12 @@ int iscsit_logout_removeconnforrecovery(struct iscsi_cmd *cmd, struct iscsi_conn return 0; } -static int iscsit_handle_logout_cmd( - struct iscsi_conn *conn, - unsigned char *buf) +int +iscsit_handle_logout_cmd(struct iscsi_conn *conn, struct iscsi_cmd *cmd, + unsigned char *buf) { int cmdsn_ret, logout_remove = 0; u8 reason_code = 0; - struct iscsi_cmd *cmd; struct iscsi_logout *hdr; struct iscsi_tiqn *tiqn = iscsit_snmp_get_tiqn(conn); @@ -2119,14 +2251,10 @@ static int iscsit_handle_logout_cmd( if (conn->conn_state != TARG_CONN_STATE_LOGGED_IN) { pr_err("Received logout request on connection that" " is not in logged in state, ignoring request.\n"); + iscsit_release_cmd(cmd); return 0; } - cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); - if (!cmd) - return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, 1, - buf, conn); - cmd->iscsi_opcode = ISCSI_OP_LOGOUT; cmd->i_state = ISTATE_SEND_LOGOUTRSP; cmd->immediate_cmd = ((hdr->opcode & ISCSI_OP_IMMEDIATE) ? 1 : 0); @@ -2176,6 +2304,7 @@ static int iscsit_handle_logout_cmd( return logout_remove; } +EXPORT_SYMBOL(iscsit_handle_logout_cmd); static int iscsit_handle_snack( struct iscsi_conn *conn, @@ -2243,7 +2372,7 @@ static void iscsit_rx_thread_wait_for_tcp(struct iscsi_conn *conn) static int iscsit_handle_immediate_data( struct iscsi_cmd *cmd, - unsigned char *buf, + struct iscsi_scsi_req *hdr, u32 length) { int iov_ret, rx_got = 0, rx_size = 0; @@ -2299,12 +2428,12 @@ static int iscsit_handle_immediate_data( " in ERL=0.\n"); iscsit_add_reject_from_cmd( ISCSI_REASON_DATA_DIGEST_ERROR, - 1, 0, buf, cmd); + 1, 0, (unsigned char *)hdr, cmd); return IMMEDIATE_DATA_CANNOT_RECOVER; } else { iscsit_add_reject_from_cmd( ISCSI_REASON_DATA_DIGEST_ERROR, - 0, 0, buf, cmd); + 0, 0, (unsigned char *)hdr, cmd); return IMMEDIATE_DATA_ERL1_CRC_FAILURE; } } else { @@ -2424,18 +2553,60 @@ static void iscsit_tx_thread_wait_for_tcp(struct iscsi_conn *conn) } } -static int iscsit_send_data_in( - struct iscsi_cmd *cmd, - struct iscsi_conn *conn) +static void +iscsit_build_datain_pdu(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_datain *datain, struct iscsi_data_rsp *hdr, + bool set_statsn) { - int iov_ret = 0, set_statsn = 0; - u32 iov_count = 0, tx_size = 0; + hdr->opcode = ISCSI_OP_SCSI_DATA_IN; + hdr->flags = datain->flags; + if (hdr->flags & ISCSI_FLAG_DATA_STATUS) { + if (cmd->se_cmd.se_cmd_flags & SCF_OVERFLOW_BIT) { + hdr->flags |= ISCSI_FLAG_DATA_OVERFLOW; + hdr->residual_count = cpu_to_be32(cmd->se_cmd.residual_count); + } else if (cmd->se_cmd.se_cmd_flags & SCF_UNDERFLOW_BIT) { + hdr->flags |= ISCSI_FLAG_DATA_UNDERFLOW; + hdr->residual_count = cpu_to_be32(cmd->se_cmd.residual_count); + } + } + hton24(hdr->dlength, datain->length); + if (hdr->flags & ISCSI_FLAG_DATA_ACK) + int_to_scsilun(cmd->se_cmd.orig_fe_lun, + (struct scsi_lun *)&hdr->lun); + else + put_unaligned_le64(0xFFFFFFFFFFFFFFFFULL, &hdr->lun); + + hdr->itt = cmd->init_task_tag; + + if (hdr->flags & ISCSI_FLAG_DATA_ACK) + hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); + else + hdr->ttt = cpu_to_be32(0xFFFFFFFF); + if (set_statsn) + hdr->statsn = cpu_to_be32(cmd->stat_sn); + else + hdr->statsn = cpu_to_be32(0xFFFFFFFF); + + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + hdr->datasn = cpu_to_be32(datain->data_sn); + hdr->offset = cpu_to_be32(datain->offset); + + pr_debug("Built DataIN ITT: 0x%08x, StatSN: 0x%08x," + " DataSN: 0x%08x, Offset: %u, Length: %u, CID: %hu\n", + cmd->init_task_tag, ntohl(hdr->statsn), ntohl(hdr->datasn), + ntohl(hdr->offset), datain->length, conn->cid); +} + +static int iscsit_send_datain(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_data_rsp *hdr = (struct iscsi_data_rsp *)&cmd->pdu[0]; struct iscsi_datain datain; struct iscsi_datain_req *dr; - struct iscsi_data_rsp *hdr; struct kvec *iov; - int eodr = 0; - int ret; + u32 iov_count = 0, tx_size = 0; + int eodr = 0, ret, iov_ret; + bool set_statsn = false; memset(&datain, 0, sizeof(struct iscsi_datain)); dr = iscsit_get_datain_values(cmd, &datain); @@ -2444,7 +2615,6 @@ static int iscsit_send_data_in( cmd->init_task_tag); return -1; } - /* * Be paranoid and double check the logic for now. */ @@ -2452,7 +2622,7 @@ static int iscsit_send_data_in( pr_err("Command ITT: 0x%08x, datain.offset: %u and" " datain.length: %u exceeds cmd->data_length: %u\n", cmd->init_task_tag, datain.offset, datain.length, - cmd->se_cmd.data_length); + cmd->se_cmd.data_length); return -1; } @@ -2476,47 +2646,13 @@ static int iscsit_send_data_in( (dr->dr_complete == DATAIN_COMPLETE_CONNECTION_RECOVERY)) { iscsit_increment_maxcmdsn(cmd, conn->sess); cmd->stat_sn = conn->stat_sn++; - set_statsn = 1; + set_statsn = true; } else if (dr->dr_complete == - DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY) - set_statsn = 1; + DATAIN_COMPLETE_WITHIN_COMMAND_RECOVERY) + set_statsn = true; } - hdr = (struct iscsi_data_rsp *) cmd->pdu; - memset(hdr, 0, ISCSI_HDR_LEN); - hdr->opcode = ISCSI_OP_SCSI_DATA_IN; - hdr->flags = datain.flags; - if (hdr->flags & ISCSI_FLAG_DATA_STATUS) { - if (cmd->se_cmd.se_cmd_flags & SCF_OVERFLOW_BIT) { - hdr->flags |= ISCSI_FLAG_DATA_OVERFLOW; - hdr->residual_count = cpu_to_be32(cmd->se_cmd.residual_count); - } else if (cmd->se_cmd.se_cmd_flags & SCF_UNDERFLOW_BIT) { - hdr->flags |= ISCSI_FLAG_DATA_UNDERFLOW; - hdr->residual_count = cpu_to_be32(cmd->se_cmd.residual_count); - } - } - hton24(hdr->dlength, datain.length); - if (hdr->flags & ISCSI_FLAG_DATA_ACK) - int_to_scsilun(cmd->se_cmd.orig_fe_lun, - (struct scsi_lun *)&hdr->lun); - else - put_unaligned_le64(0xFFFFFFFFFFFFFFFFULL, &hdr->lun); - - hdr->itt = cmd->init_task_tag; - - if (hdr->flags & ISCSI_FLAG_DATA_ACK) - hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); - else - hdr->ttt = cpu_to_be32(0xFFFFFFFF); - if (set_statsn) - hdr->statsn = cpu_to_be32(cmd->stat_sn); - else - hdr->statsn = cpu_to_be32(0xFFFFFFFF); - - hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); - hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); - hdr->datasn = cpu_to_be32(datain.data_sn); - hdr->offset = cpu_to_be32(datain.offset); + iscsit_build_datain_pdu(cmd, conn, &datain, hdr, set_statsn); iov = &cmd->iov_data[0]; iov[iov_count].iov_base = cmd->pdu; @@ -2527,7 +2663,7 @@ static int iscsit_send_data_in( u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, - (unsigned char *)hdr, ISCSI_HDR_LEN, + (unsigned char *)cmd->pdu, ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); iov[0].iov_len += ISCSI_CRC_LEN; @@ -2537,7 +2673,8 @@ static int iscsit_send_data_in( " for DataIN PDU 0x%08x\n", *header_digest); } - iov_ret = iscsit_map_iovec(cmd, &cmd->iov_data[1], datain.offset, datain.length); + iov_ret = iscsit_map_iovec(cmd, &cmd->iov_data[1], + datain.offset, datain.length); if (iov_ret < 0) return -1; @@ -2568,11 +2705,6 @@ static int iscsit_send_data_in( cmd->iov_data_count = iov_count; cmd->tx_size = tx_size; - pr_debug("Built DataIN ITT: 0x%08x, StatSN: 0x%08x," - " DataSN: 0x%08x, Offset: %u, Length: %u, CID: %hu\n", - cmd->init_task_tag, ntohl(hdr->statsn), ntohl(hdr->datasn), - ntohl(hdr->offset), datain.length, conn->cid); - /* sendpage is preferred but can't insert markers */ if (!conn->conn_ops->IFMarker) ret = iscsit_fe_sendpage_sg(cmd, conn); @@ -2595,16 +2727,13 @@ static int iscsit_send_data_in( return eodr; } -static int iscsit_send_logout_response( - struct iscsi_cmd *cmd, - struct iscsi_conn *conn) +int +iscsit_build_logout_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_logout_rsp *hdr) { - int niov = 0, tx_size; struct iscsi_conn *logout_conn = NULL; struct iscsi_conn_recovery *cr = NULL; struct iscsi_session *sess = conn->sess; - struct kvec *iov; - struct iscsi_logout_rsp *hdr; /* * The actual shutting down of Sessions and/or Connections * for CLOSESESSION and CLOSECONNECTION Logout Requests @@ -2673,9 +2802,6 @@ static int iscsit_send_logout_response( return -1; } - tx_size = ISCSI_HDR_LEN; - hdr = (struct iscsi_logout_rsp *)cmd->pdu; - memset(hdr, 0, ISCSI_HDR_LEN); hdr->opcode = ISCSI_OP_LOGOUT_RSP; hdr->flags |= ISCSI_FLAG_CMD_FINAL; hdr->response = cmd->logout_response; @@ -2687,6 +2813,27 @@ static int iscsit_send_logout_response( hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + pr_debug("Built Logout Response ITT: 0x%08x StatSN:" + " 0x%08x Response: 0x%02x CID: %hu on CID: %hu\n", + cmd->init_task_tag, cmd->stat_sn, hdr->response, + cmd->logout_cid, conn->cid); + + return 0; +} +EXPORT_SYMBOL(iscsit_build_logout_rsp); + +static int +iscsit_send_logout(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct kvec *iov; + int niov = 0, tx_size, rc; + + rc = iscsit_build_logout_rsp(cmd, conn, + (struct iscsi_logout_rsp *)&cmd->pdu[0]); + if (rc < 0) + return rc; + + tx_size = ISCSI_HDR_LEN; iov = &cmd->iov_misc[0]; iov[niov].iov_base = cmd->pdu; iov[niov++].iov_len = ISCSI_HDR_LEN; @@ -2695,7 +2842,7 @@ static int iscsit_send_logout_response( u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, - (unsigned char *)hdr, ISCSI_HDR_LEN, + (unsigned char *)&cmd->pdu[0], ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); iov[0].iov_len += ISCSI_CRC_LEN; @@ -2706,14 +2853,37 @@ static int iscsit_send_logout_response( cmd->iov_misc_count = niov; cmd->tx_size = tx_size; - pr_debug("Sending Logout Response ITT: 0x%08x StatSN:" - " 0x%08x Response: 0x%02x CID: %hu on CID: %hu\n", - cmd->init_task_tag, cmd->stat_sn, hdr->response, - cmd->logout_cid, conn->cid); - return 0; } +void +iscsit_build_nopin_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_nopin *hdr, bool nopout_response) +{ + hdr->opcode = ISCSI_OP_NOOP_IN; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hton24(hdr->dlength, cmd->buf_ptr_size); + if (nopout_response) + put_unaligned_le64(0xFFFFFFFFFFFFFFFFULL, &hdr->lun); + hdr->itt = cmd->init_task_tag; + hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); + cmd->stat_sn = (nopout_response) ? conn->stat_sn++ : + conn->stat_sn; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + + if (nopout_response) + iscsit_increment_maxcmdsn(cmd, conn->sess); + + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + + pr_debug("Built NOPIN %s Response ITT: 0x%08x, TTT: 0x%08x," + " StatSN: 0x%08x, Length %u\n", (nopout_response) ? + "Solicitied" : "Unsolicitied", cmd->init_task_tag, + cmd->targ_xfer_tag, cmd->stat_sn, cmd->buf_ptr_size); +} +EXPORT_SYMBOL(iscsit_build_nopin_rsp); + /* * Unsolicited NOPIN, either requesting a response or not. */ @@ -2722,20 +2892,10 @@ static int iscsit_send_unsolicited_nopin( struct iscsi_conn *conn, int want_response) { - int tx_size = ISCSI_HDR_LEN; - struct iscsi_nopin *hdr; - int ret; + struct iscsi_nopin *hdr = (struct iscsi_nopin *)&cmd->pdu[0]; + int tx_size = ISCSI_HDR_LEN, ret; - hdr = (struct iscsi_nopin *) cmd->pdu; - memset(hdr, 0, ISCSI_HDR_LEN); - hdr->opcode = ISCSI_OP_NOOP_IN; - hdr->flags |= ISCSI_FLAG_CMD_FINAL; - hdr->itt = cmd->init_task_tag; - hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); - cmd->stat_sn = conn->stat_sn; - hdr->statsn = cpu_to_be32(cmd->stat_sn); - hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); - hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + iscsit_build_nopin_rsp(cmd, conn, hdr, false); if (conn->conn_ops->HeaderDigest) { u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; @@ -2771,31 +2931,17 @@ static int iscsit_send_unsolicited_nopin( return 0; } -static int iscsit_send_nopin_response( - struct iscsi_cmd *cmd, - struct iscsi_conn *conn) +static int +iscsit_send_nopin(struct iscsi_cmd *cmd, struct iscsi_conn *conn) { - int niov = 0, tx_size; - u32 padding = 0; + struct iscsi_nopin *hdr = (struct iscsi_nopin *)&cmd->pdu[0]; struct kvec *iov; - struct iscsi_nopin *hdr; - - tx_size = ISCSI_HDR_LEN; - hdr = (struct iscsi_nopin *) cmd->pdu; - memset(hdr, 0, ISCSI_HDR_LEN); - hdr->opcode = ISCSI_OP_NOOP_IN; - hdr->flags |= ISCSI_FLAG_CMD_FINAL; - hton24(hdr->dlength, cmd->buf_ptr_size); - put_unaligned_le64(0xFFFFFFFFFFFFFFFFULL, &hdr->lun); - hdr->itt = cmd->init_task_tag; - hdr->ttt = cpu_to_be32(cmd->targ_xfer_tag); - cmd->stat_sn = conn->stat_sn++; - hdr->statsn = cpu_to_be32(cmd->stat_sn); + u32 padding = 0; + int niov = 0, tx_size; - iscsit_increment_maxcmdsn(cmd, conn->sess); - hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); - hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + iscsit_build_nopin_rsp(cmd, conn, hdr, true); + tx_size = ISCSI_HDR_LEN; iov = &cmd->iov_misc[0]; iov[niov].iov_base = cmd->pdu; iov[niov++].iov_len = ISCSI_HDR_LEN; @@ -2851,10 +2997,6 @@ static int iscsit_send_nopin_response( cmd->iov_misc_count = niov; cmd->tx_size = tx_size; - pr_debug("Sending NOPIN Response ITT: 0x%08x, TTT:" - " 0x%08x, StatSN: 0x%08x, Length %u\n", cmd->init_task_tag, - cmd->targ_xfer_tag, cmd->stat_sn, cmd->buf_ptr_size); - return 0; } @@ -2939,8 +3081,8 @@ static int iscsit_send_r2t( * connection recovery. */ int iscsit_build_r2ts_for_cmd( - struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_cmd *cmd, bool recovery) { int first_r2t = 1; @@ -3015,24 +3157,16 @@ int iscsit_build_r2ts_for_cmd( return 0; } -static int iscsit_send_status( - struct iscsi_cmd *cmd, - struct iscsi_conn *conn) +void iscsit_build_rsp_pdu(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + bool inc_stat_sn, struct iscsi_scsi_rsp *hdr) { - u8 iov_count = 0, recovery; - u32 padding = 0, tx_size = 0; - struct iscsi_scsi_rsp *hdr; - struct kvec *iov; - - recovery = (cmd->i_state != ISTATE_SEND_STATUS); - if (!recovery) + if (inc_stat_sn) cmd->stat_sn = conn->stat_sn++; spin_lock_bh(&conn->sess->session_stats_lock); conn->sess->rsp_pdus++; spin_unlock_bh(&conn->sess->session_stats_lock); - hdr = (struct iscsi_scsi_rsp *) cmd->pdu; memset(hdr, 0, ISCSI_HDR_LEN); hdr->opcode = ISCSI_OP_SCSI_CMD_RSP; hdr->flags |= ISCSI_FLAG_CMD_FINAL; @@ -3052,6 +3186,23 @@ static int iscsit_send_status( hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + pr_debug("Built SCSI Response, ITT: 0x%08x, StatSN: 0x%08x," + " Response: 0x%02x, SAM Status: 0x%02x, CID: %hu\n", + cmd->init_task_tag, cmd->stat_sn, cmd->se_cmd.scsi_status, + cmd->se_cmd.scsi_status, conn->cid); +} +EXPORT_SYMBOL(iscsit_build_rsp_pdu); + +static int iscsit_send_response(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_scsi_rsp *hdr = (struct iscsi_scsi_rsp *)&cmd->pdu[0]; + struct kvec *iov; + u32 padding = 0, tx_size = 0; + int iov_count = 0; + bool inc_stat_sn = (cmd->i_state == ISTATE_SEND_STATUS); + + iscsit_build_rsp_pdu(cmd, conn, inc_stat_sn, hdr); + iov = &cmd->iov_misc[0]; iov[iov_count].iov_base = cmd->pdu; iov[iov_count++].iov_len = ISCSI_HDR_LEN; @@ -3106,7 +3257,7 @@ static int iscsit_send_status( u32 *header_digest = (u32 *)&cmd->pdu[ISCSI_HDR_LEN]; iscsit_do_crypto_hash_buf(&conn->conn_tx_hash, - (unsigned char *)hdr, ISCSI_HDR_LEN, + (unsigned char *)cmd->pdu, ISCSI_HDR_LEN, 0, NULL, (u8 *)header_digest); iov[0].iov_len += ISCSI_CRC_LEN; @@ -3118,11 +3269,6 @@ static int iscsit_send_status( cmd->iov_misc_count = iov_count; cmd->tx_size = tx_size; - pr_debug("Built %sSCSI Response, ITT: 0x%08x, StatSN: 0x%08x," - " Response: 0x%02x, SAM Status: 0x%02x, CID: %hu\n", - (!recovery) ? "" : "Recovery ", cmd->init_task_tag, - cmd->stat_sn, 0x00, cmd->se_cmd.scsi_status, conn->cid); - return 0; } @@ -3145,16 +3291,12 @@ static u8 iscsit_convert_tcm_tmr_rsp(struct se_tmr_req *se_tmr) } } -static int iscsit_send_task_mgt_rsp( - struct iscsi_cmd *cmd, - struct iscsi_conn *conn) +void +iscsit_build_task_mgt_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_tm_rsp *hdr) { struct se_tmr_req *se_tmr = cmd->se_cmd.se_tmr_req; - struct iscsi_tm_rsp *hdr; - u32 tx_size = 0; - hdr = (struct iscsi_tm_rsp *) cmd->pdu; - memset(hdr, 0, ISCSI_HDR_LEN); hdr->opcode = ISCSI_OP_SCSI_TMFUNC_RSP; hdr->flags = ISCSI_FLAG_CMD_FINAL; hdr->response = iscsit_convert_tcm_tmr_rsp(se_tmr); @@ -3166,6 +3308,20 @@ static int iscsit_send_task_mgt_rsp( hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + pr_debug("Built Task Management Response ITT: 0x%08x," + " StatSN: 0x%08x, Response: 0x%02x, CID: %hu\n", + cmd->init_task_tag, cmd->stat_sn, hdr->response, conn->cid); +} +EXPORT_SYMBOL(iscsit_build_task_mgt_rsp); + +static int +iscsit_send_task_mgt_rsp(struct iscsi_cmd *cmd, struct iscsi_conn *conn) +{ + struct iscsi_tm_rsp *hdr = (struct iscsi_tm_rsp *)&cmd->pdu[0]; + u32 tx_size = 0; + + iscsit_build_task_mgt_rsp(cmd, conn, hdr); + cmd->iov_misc[0].iov_base = cmd->pdu; cmd->iov_misc[0].iov_len = ISCSI_HDR_LEN; tx_size += ISCSI_HDR_LEN; @@ -3186,10 +3342,6 @@ static int iscsit_send_task_mgt_rsp( cmd->iov_misc_count = 1; cmd->tx_size = tx_size; - pr_debug("Built Task Management Response ITT: 0x%08x," - " StatSN: 0x%08x, Response: 0x%02x, CID: %hu\n", - cmd->init_task_tag, cmd->stat_sn, hdr->response, conn->cid); - return 0; } @@ -3385,6 +3537,22 @@ static int iscsit_send_text_rsp( return 0; } +void +iscsit_build_reject(struct iscsi_cmd *cmd, struct iscsi_conn *conn, + struct iscsi_reject *hdr) +{ + hdr->opcode = ISCSI_OP_REJECT; + hdr->flags |= ISCSI_FLAG_CMD_FINAL; + hton24(hdr->dlength, ISCSI_HDR_LEN); + hdr->ffffffff = cpu_to_be32(0xffffffff); + cmd->stat_sn = conn->stat_sn++; + hdr->statsn = cpu_to_be32(cmd->stat_sn); + hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); + hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + +} +EXPORT_SYMBOL(iscsit_build_reject); + static int iscsit_send_reject( struct iscsi_cmd *cmd, struct iscsi_conn *conn) @@ -3393,18 +3561,9 @@ static int iscsit_send_reject( struct iscsi_reject *hdr; struct kvec *iov; - hdr = (struct iscsi_reject *) cmd->pdu; - hdr->opcode = ISCSI_OP_REJECT; - hdr->flags |= ISCSI_FLAG_CMD_FINAL; - hton24(hdr->dlength, ISCSI_HDR_LEN); - hdr->ffffffff = cpu_to_be32(0xffffffff); - cmd->stat_sn = conn->stat_sn++; - hdr->statsn = cpu_to_be32(cmd->stat_sn); - hdr->exp_cmdsn = cpu_to_be32(conn->sess->exp_cmd_sn); - hdr->max_cmdsn = cpu_to_be32(conn->sess->max_cmd_sn); + iscsit_build_reject(cmd, conn, (struct iscsi_reject *)&cmd->pdu[0]); iov = &cmd->iov_misc[0]; - iov[iov_count].iov_base = cmd->pdu; iov[iov_count++].iov_len = ISCSI_HDR_LEN; iov[iov_count].iov_base = cmd->buf_ptr; @@ -3501,55 +3660,41 @@ static inline void iscsit_thread_check_cpumask( set_cpus_allowed_ptr(p, conn->conn_cpumask); } -static int handle_immediate_queue(struct iscsi_conn *conn) +static int +iscsit_immediate_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) { - struct iscsi_queue_req *qr; - struct iscsi_cmd *cmd; - u8 state; int ret; - while ((qr = iscsit_get_cmd_from_immediate_queue(conn))) { - atomic_set(&conn->check_immediate_queue, 0); - cmd = qr->cmd; - state = qr->state; - kmem_cache_free(lio_qr_cache, qr); - - switch (state) { - case ISTATE_SEND_R2T: - ret = iscsit_send_r2t(cmd, conn); - if (ret < 0) - goto err; - break; - case ISTATE_REMOVE: - if (cmd->data_direction == DMA_TO_DEVICE) - iscsit_stop_dataout_timer(cmd); - - spin_lock_bh(&conn->cmd_lock); - list_del(&cmd->i_conn_node); - spin_unlock_bh(&conn->cmd_lock); + switch (state) { + case ISTATE_SEND_R2T: + ret = iscsit_send_r2t(cmd, conn); + if (ret < 0) + goto err; + break; + case ISTATE_REMOVE: + spin_lock_bh(&conn->cmd_lock); + list_del(&cmd->i_conn_node); + spin_unlock_bh(&conn->cmd_lock); - iscsit_free_cmd(cmd); - continue; - case ISTATE_SEND_NOPIN_WANT_RESPONSE: - iscsit_mod_nopin_response_timer(conn); - ret = iscsit_send_unsolicited_nopin(cmd, - conn, 1); - if (ret < 0) - goto err; - break; - case ISTATE_SEND_NOPIN_NO_RESPONSE: - ret = iscsit_send_unsolicited_nopin(cmd, - conn, 0); - if (ret < 0) - goto err; - break; - default: - pr_err("Unknown Opcode: 0x%02x ITT:" - " 0x%08x, i_state: %d on CID: %hu\n", - cmd->iscsi_opcode, cmd->init_task_tag, state, - conn->cid); + iscsit_free_cmd(cmd); + break; + case ISTATE_SEND_NOPIN_WANT_RESPONSE: + iscsit_mod_nopin_response_timer(conn); + ret = iscsit_send_unsolicited_nopin(cmd, conn, 1); + if (ret < 0) goto err; - } + break; + case ISTATE_SEND_NOPIN_NO_RESPONSE: + ret = iscsit_send_unsolicited_nopin(cmd, conn, 0); + if (ret < 0) + goto err; + break; + default: + pr_err("Unknown Opcode: 0x%02x ITT:" + " 0x%08x, i_state: %d on CID: %hu\n", + cmd->iscsi_opcode, cmd->init_task_tag, state, + conn->cid); + goto err; } return 0; @@ -3558,128 +3703,143 @@ err: return -1; } -static int handle_response_queue(struct iscsi_conn *conn) +static int +iscsit_handle_immediate_queue(struct iscsi_conn *conn) { + struct iscsit_transport *t = conn->conn_transport; struct iscsi_queue_req *qr; struct iscsi_cmd *cmd; u8 state; int ret; - while ((qr = iscsit_get_cmd_from_response_queue(conn))) { + while ((qr = iscsit_get_cmd_from_immediate_queue(conn))) { + atomic_set(&conn->check_immediate_queue, 0); cmd = qr->cmd; state = qr->state; kmem_cache_free(lio_qr_cache, qr); -check_rsp_state: - switch (state) { - case ISTATE_SEND_DATAIN: - ret = iscsit_send_data_in(cmd, conn); - if (ret < 0) - goto err; - else if (!ret) - /* more drs */ - goto check_rsp_state; - else if (ret == 1) { - /* all done */ - spin_lock_bh(&cmd->istate_lock); - cmd->i_state = ISTATE_SENT_STATUS; - spin_unlock_bh(&cmd->istate_lock); - - if (atomic_read(&conn->check_immediate_queue)) - return 1; + ret = t->iscsit_immediate_queue(conn, cmd, state); + if (ret < 0) + return ret; + } - continue; - } else if (ret == 2) { - /* Still must send status, - SCF_TRANSPORT_TASK_SENSE was set */ - spin_lock_bh(&cmd->istate_lock); - cmd->i_state = ISTATE_SEND_STATUS; - spin_unlock_bh(&cmd->istate_lock); - state = ISTATE_SEND_STATUS; - goto check_rsp_state; - } + return 0; +} - break; - case ISTATE_SEND_STATUS: - case ISTATE_SEND_STATUS_RECOVERY: - ret = iscsit_send_status(cmd, conn); - break; - case ISTATE_SEND_LOGOUTRSP: - ret = iscsit_send_logout_response(cmd, conn); - break; - case ISTATE_SEND_ASYNCMSG: - ret = iscsit_send_conn_drop_async_message( - cmd, conn); - break; - case ISTATE_SEND_NOPIN: - ret = iscsit_send_nopin_response(cmd, conn); - break; - case ISTATE_SEND_REJECT: - ret = iscsit_send_reject(cmd, conn); - break; - case ISTATE_SEND_TASKMGTRSP: - ret = iscsit_send_task_mgt_rsp(cmd, conn); - if (ret != 0) - break; - ret = iscsit_tmr_post_handler(cmd, conn); - if (ret != 0) - iscsit_fall_back_to_erl0(conn->sess); - break; - case ISTATE_SEND_TEXTRSP: - ret = iscsit_send_text_rsp(cmd, conn); - break; - default: - pr_err("Unknown Opcode: 0x%02x ITT:" - " 0x%08x, i_state: %d on CID: %hu\n", - cmd->iscsi_opcode, cmd->init_task_tag, - state, conn->cid); - goto err; - } +static int +iscsit_response_queue(struct iscsi_conn *conn, struct iscsi_cmd *cmd, int state) +{ + int ret; + +check_rsp_state: + switch (state) { + case ISTATE_SEND_DATAIN: + ret = iscsit_send_datain(cmd, conn); if (ret < 0) goto err; + else if (!ret) + /* more drs */ + goto check_rsp_state; + else if (ret == 1) { + /* all done */ + spin_lock_bh(&cmd->istate_lock); + cmd->i_state = ISTATE_SENT_STATUS; + spin_unlock_bh(&cmd->istate_lock); - if (iscsit_send_tx_data(cmd, conn, 1) < 0) { - iscsit_tx_thread_wait_for_tcp(conn); - iscsit_unmap_iovec(cmd); - goto err; - } - iscsit_unmap_iovec(cmd); + if (atomic_read(&conn->check_immediate_queue)) + return 1; - switch (state) { - case ISTATE_SEND_LOGOUTRSP: - if (!iscsit_logout_post_handler(cmd, conn)) - goto restart; - /* fall through */ - case ISTATE_SEND_STATUS: - case ISTATE_SEND_ASYNCMSG: - case ISTATE_SEND_NOPIN: - case ISTATE_SEND_STATUS_RECOVERY: - case ISTATE_SEND_TEXTRSP: - case ISTATE_SEND_TASKMGTRSP: + return 0; + } else if (ret == 2) { + /* Still must send status, + SCF_TRANSPORT_TASK_SENSE was set */ spin_lock_bh(&cmd->istate_lock); - cmd->i_state = ISTATE_SENT_STATUS; + cmd->i_state = ISTATE_SEND_STATUS; spin_unlock_bh(&cmd->istate_lock); + state = ISTATE_SEND_STATUS; + goto check_rsp_state; + } + + break; + case ISTATE_SEND_STATUS: + case ISTATE_SEND_STATUS_RECOVERY: + ret = iscsit_send_response(cmd, conn); + break; + case ISTATE_SEND_LOGOUTRSP: + ret = iscsit_send_logout(cmd, conn); + break; + case ISTATE_SEND_ASYNCMSG: + ret = iscsit_send_conn_drop_async_message( + cmd, conn); + break; + case ISTATE_SEND_NOPIN: + ret = iscsit_send_nopin(cmd, conn); + break; + case ISTATE_SEND_REJECT: + ret = iscsit_send_reject(cmd, conn); + break; + case ISTATE_SEND_TASKMGTRSP: + ret = iscsit_send_task_mgt_rsp(cmd, conn); + if (ret != 0) break; - case ISTATE_SEND_REJECT: - if (cmd->cmd_flags & ICF_REJECT_FAIL_CONN) { - cmd->cmd_flags &= ~ICF_REJECT_FAIL_CONN; - complete(&cmd->reject_comp); - goto err; - } + ret = iscsit_tmr_post_handler(cmd, conn); + if (ret != 0) + iscsit_fall_back_to_erl0(conn->sess); + break; + case ISTATE_SEND_TEXTRSP: + ret = iscsit_send_text_rsp(cmd, conn); + break; + default: + pr_err("Unknown Opcode: 0x%02x ITT:" + " 0x%08x, i_state: %d on CID: %hu\n", + cmd->iscsi_opcode, cmd->init_task_tag, + state, conn->cid); + goto err; + } + if (ret < 0) + goto err; + + if (iscsit_send_tx_data(cmd, conn, 1) < 0) { + iscsit_tx_thread_wait_for_tcp(conn); + iscsit_unmap_iovec(cmd); + goto err; + } + iscsit_unmap_iovec(cmd); + + switch (state) { + case ISTATE_SEND_LOGOUTRSP: + if (!iscsit_logout_post_handler(cmd, conn)) + goto restart; + /* fall through */ + case ISTATE_SEND_STATUS: + case ISTATE_SEND_ASYNCMSG: + case ISTATE_SEND_NOPIN: + case ISTATE_SEND_STATUS_RECOVERY: + case ISTATE_SEND_TEXTRSP: + case ISTATE_SEND_TASKMGTRSP: + spin_lock_bh(&cmd->istate_lock); + cmd->i_state = ISTATE_SENT_STATUS; + spin_unlock_bh(&cmd->istate_lock); + break; + case ISTATE_SEND_REJECT: + if (cmd->cmd_flags & ICF_REJECT_FAIL_CONN) { + cmd->cmd_flags &= ~ICF_REJECT_FAIL_CONN; complete(&cmd->reject_comp); - break; - default: - pr_err("Unknown Opcode: 0x%02x ITT:" - " 0x%08x, i_state: %d on CID: %hu\n", - cmd->iscsi_opcode, cmd->init_task_tag, - cmd->i_state, conn->cid); goto err; } - - if (atomic_read(&conn->check_immediate_queue)) - return 1; + complete(&cmd->reject_comp); + break; + default: + pr_err("Unknown Opcode: 0x%02x ITT:" + " 0x%08x, i_state: %d on CID: %hu\n", + cmd->iscsi_opcode, cmd->init_task_tag, + cmd->i_state, conn->cid); + goto err; } + if (atomic_read(&conn->check_immediate_queue)) + return 1; + return 0; err: @@ -3688,6 +3848,27 @@ restart: return -EAGAIN; } +static int iscsit_handle_response_queue(struct iscsi_conn *conn) +{ + struct iscsit_transport *t = conn->conn_transport; + struct iscsi_queue_req *qr; + struct iscsi_cmd *cmd; + u8 state; + int ret; + + while ((qr = iscsit_get_cmd_from_response_queue(conn))) { + cmd = qr->cmd; + state = qr->state; + kmem_cache_free(lio_qr_cache, qr); + + ret = t->iscsit_response_queue(conn, cmd, state); + if (ret == 1 || ret < 0) + return ret; + } + + return 0; +} + int iscsi_target_tx_thread(void *arg) { int ret = 0; @@ -3722,11 +3903,11 @@ restart: goto transport_err; get_immediate: - ret = handle_immediate_queue(conn); + ret = iscsit_handle_immediate_queue(conn); if (ret < 0) goto transport_err; - ret = handle_response_queue(conn); + ret = iscsit_handle_response_queue(conn); if (ret == 1) goto get_immediate; else if (ret == -EAGAIN) @@ -3742,6 +3923,83 @@ out: return 0; } +static int iscsi_target_rx_opcode(struct iscsi_conn *conn, unsigned char *buf) +{ + struct iscsi_hdr *hdr = (struct iscsi_hdr *)buf; + struct iscsi_cmd *cmd; + int ret = 0; + + switch (hdr->opcode & ISCSI_OPCODE_MASK) { + case ISCSI_OP_SCSI_CMD: + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, buf, conn); + + ret = iscsit_handle_scsi_cmd(conn, cmd, buf); + break; + case ISCSI_OP_SCSI_DATA_OUT: + ret = iscsit_handle_data_out(conn, buf); + break; + case ISCSI_OP_NOOP_OUT: + cmd = NULL; + if (hdr->ttt == cpu_to_be32(0xFFFFFFFF)) { + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, buf, conn); + } + ret = iscsit_handle_nop_out(conn, cmd, buf); + break; + case ISCSI_OP_SCSI_TMFUNC: + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, buf, conn); + + ret = iscsit_handle_task_mgt_cmd(conn, cmd, buf); + break; + case ISCSI_OP_TEXT: + ret = iscsit_handle_text_cmd(conn, buf); + break; + case ISCSI_OP_LOGOUT: + cmd = iscsit_allocate_cmd(conn, GFP_KERNEL); + if (!cmd) + return iscsit_add_reject(ISCSI_REASON_BOOKMARK_NO_RESOURCES, + 1, buf, conn); + + ret = iscsit_handle_logout_cmd(conn, cmd, buf); + if (ret > 0) + wait_for_completion_timeout(&conn->conn_logout_comp, + SECONDS_FOR_LOGOUT_COMP * HZ); + break; + case ISCSI_OP_SNACK: + ret = iscsit_handle_snack(conn, buf); + break; + default: + pr_err("Got unknown iSCSI OpCode: 0x%02x\n", hdr->opcode); + if (!conn->sess->sess_ops->ErrorRecoveryLevel) { + pr_err("Cannot recover from unknown" + " opcode while ERL=0, closing iSCSI connection.\n"); + return -1; + } + if (!conn->conn_ops->OFMarker) { + pr_err("Unable to recover from unknown" + " opcode while OFMarker=No, closing iSCSI" + " connection.\n"); + return -1; + } + if (iscsit_recover_from_unknown_opcode(conn) < 0) { + pr_err("Unable to recover from unknown" + " opcode, closing iSCSI connection.\n"); + return -1; + } + break; + } + + return ret; +} + int iscsi_target_rx_thread(void *arg) { int ret; @@ -3761,6 +4019,18 @@ restart: if (!conn) goto out; + if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) { + struct completion comp; + int rc; + + init_completion(&comp); + rc = wait_for_completion_interruptible(&comp); + if (rc < 0) + goto transport_err; + + goto out; + } + while (!kthread_should_stop()) { /* * Ensure that both TX and RX per connection kthreads @@ -3832,62 +4102,9 @@ restart: goto transport_err; } - switch (opcode) { - case ISCSI_OP_SCSI_CMD: - if (iscsit_handle_scsi_cmd(conn, buffer) < 0) - goto transport_err; - break; - case ISCSI_OP_SCSI_DATA_OUT: - if (iscsit_handle_data_out(conn, buffer) < 0) - goto transport_err; - break; - case ISCSI_OP_NOOP_OUT: - if (iscsit_handle_nop_out(conn, buffer) < 0) - goto transport_err; - break; - case ISCSI_OP_SCSI_TMFUNC: - if (iscsit_handle_task_mgt_cmd(conn, buffer) < 0) - goto transport_err; - break; - case ISCSI_OP_TEXT: - if (iscsit_handle_text_cmd(conn, buffer) < 0) - goto transport_err; - break; - case ISCSI_OP_LOGOUT: - ret = iscsit_handle_logout_cmd(conn, buffer); - if (ret > 0) { - wait_for_completion_timeout(&conn->conn_logout_comp, - SECONDS_FOR_LOGOUT_COMP * HZ); - goto transport_err; - } else if (ret < 0) - goto transport_err; - break; - case ISCSI_OP_SNACK: - if (iscsit_handle_snack(conn, buffer) < 0) - goto transport_err; - break; - default: - pr_err("Got unknown iSCSI OpCode: 0x%02x\n", - opcode); - if (!conn->sess->sess_ops->ErrorRecoveryLevel) { - pr_err("Cannot recover from unknown" - " opcode while ERL=0, closing iSCSI connection" - ".\n"); - goto transport_err; - } - if (!conn->conn_ops->OFMarker) { - pr_err("Unable to recover from unknown" - " opcode while OFMarker=No, closing iSCSI" - " connection.\n"); - goto transport_err; - } - if (iscsit_recover_from_unknown_opcode(conn) < 0) { - pr_err("Unable to recover from unknown" - " opcode, closing iSCSI connection.\n"); - goto transport_err; - } - break; - } + ret = iscsi_target_rx_opcode(conn, buffer); + if (ret < 0) + goto transport_err; } transport_err: @@ -4053,6 +4270,12 @@ int iscsit_close_connection( if (conn->sock) sock_release(conn->sock); + + if (conn->conn_transport->iscsit_free_conn) + conn->conn_transport->iscsit_free_conn(conn); + + iscsit_put_transport(conn->conn_transport); + conn->thread_set = NULL; pr_debug("Moving to TARG_CONN_STATE_FREE.\n"); @@ -4284,7 +4507,7 @@ static void iscsit_logout_post_handler_diffcid( /* * Return of 0 causes the TX thread to restart. */ -static int iscsit_logout_post_handler( +int iscsit_logout_post_handler( struct iscsi_cmd *cmd, struct iscsi_conn *conn) { @@ -4342,6 +4565,7 @@ static int iscsit_logout_post_handler( } return ret; } +EXPORT_SYMBOL(iscsit_logout_post_handler); void iscsit_fail_session(struct iscsi_session *sess) { diff --git a/drivers/target/iscsi/iscsi_target.h b/drivers/target/iscsi/iscsi_target.h index b1a1e635070..a0050b2f294 100644 --- a/drivers/target/iscsi/iscsi_target.h +++ b/drivers/target/iscsi/iscsi_target.h @@ -16,11 +16,12 @@ extern int iscsit_reset_np_thread(struct iscsi_np *, struct iscsi_tpg_np *, struct iscsi_portal_group *); extern int iscsit_del_np(struct iscsi_np *); extern int iscsit_add_reject_from_cmd(u8, int, int, unsigned char *, struct iscsi_cmd *); +extern void iscsit_set_unsoliticed_dataout(struct iscsi_cmd *); extern int iscsit_logout_closesession(struct iscsi_cmd *, struct iscsi_conn *); extern int iscsit_logout_closeconnection(struct iscsi_cmd *, struct iscsi_conn *); extern int iscsit_logout_removeconnforrecovery(struct iscsi_cmd *, struct iscsi_conn *); extern int iscsit_send_async_msg(struct iscsi_conn *, u16, u8, u8); -extern int iscsit_build_r2ts_for_cmd(struct iscsi_cmd *, struct iscsi_conn *, bool recovery); +extern int iscsit_build_r2ts_for_cmd(struct iscsi_conn *, struct iscsi_cmd *, bool recovery); extern void iscsit_thread_get_cpumask(struct iscsi_conn *); extern int iscsi_target_tx_thread(void *); extern int iscsi_target_rx_thread(void *); diff --git a/drivers/target/iscsi/iscsi_target_auth.c b/drivers/target/iscsi/iscsi_target_auth.c index a0fc7b9eea6..cee17543278 100644 --- a/drivers/target/iscsi/iscsi_target_auth.c +++ b/drivers/target/iscsi/iscsi_target_auth.c @@ -49,32 +49,6 @@ static void chap_binaryhex_to_asciihex(char *dst, char *src, int src_len) } } -static void chap_set_random(char *data, int length) -{ - long r; - unsigned n; - - while (length > 0) { - get_random_bytes(&r, sizeof(long)); - r = r ^ (r >> 8); - r = r ^ (r >> 4); - n = r & 0x7; - - get_random_bytes(&r, sizeof(long)); - r = r ^ (r >> 8); - r = r ^ (r >> 5); - n = (n << 3) | (r & 0x7); - - get_random_bytes(&r, sizeof(long)); - r = r ^ (r >> 8); - r = r ^ (r >> 5); - n = (n << 2) | (r & 0x3); - - *data++ = n; - length--; - } -} - static void chap_gen_challenge( struct iscsi_conn *conn, int caller, @@ -86,7 +60,7 @@ static void chap_gen_challenge( memset(challenge_asciihex, 0, CHAP_CHALLENGE_LENGTH * 2 + 1); - chap_set_random(chap->challenge, CHAP_CHALLENGE_LENGTH); + get_random_bytes(chap->challenge, CHAP_CHALLENGE_LENGTH); chap_binaryhex_to_asciihex(challenge_asciihex, chap->challenge, CHAP_CHALLENGE_LENGTH); /* diff --git a/drivers/target/iscsi/iscsi_target_configfs.c b/drivers/target/iscsi/iscsi_target_configfs.c index 78d75c8567d..13e9e715ad2 100644 --- a/drivers/target/iscsi/iscsi_target_configfs.c +++ b/drivers/target/iscsi/iscsi_target_configfs.c @@ -27,6 +27,7 @@ #include <target/target_core_fabric_configfs.h> #include <target/target_core_configfs.h> #include <target/configfs_macros.h> +#include <target/iscsi/iscsi_transport.h> #include "iscsi_target_core.h" #include "iscsi_target_parameters.h" @@ -124,8 +125,87 @@ out: TF_NP_BASE_ATTR(lio_target, sctp, S_IRUGO | S_IWUSR); +static ssize_t lio_target_np_show_iser( + struct se_tpg_np *se_tpg_np, + char *page) +{ + struct iscsi_tpg_np *tpg_np = container_of(se_tpg_np, + struct iscsi_tpg_np, se_tpg_np); + struct iscsi_tpg_np *tpg_np_iser; + ssize_t rb; + + tpg_np_iser = iscsit_tpg_locate_child_np(tpg_np, ISCSI_INFINIBAND); + if (tpg_np_iser) + rb = sprintf(page, "1\n"); + else + rb = sprintf(page, "0\n"); + + return rb; +} + +static ssize_t lio_target_np_store_iser( + struct se_tpg_np *se_tpg_np, + const char *page, + size_t count) +{ + struct iscsi_np *np; + struct iscsi_portal_group *tpg; + struct iscsi_tpg_np *tpg_np = container_of(se_tpg_np, + struct iscsi_tpg_np, se_tpg_np); + struct iscsi_tpg_np *tpg_np_iser = NULL; + char *endptr; + u32 op; + int rc; + + op = simple_strtoul(page, &endptr, 0); + if ((op != 1) && (op != 0)) { + pr_err("Illegal value for tpg_enable: %u\n", op); + return -EINVAL; + } + np = tpg_np->tpg_np; + if (!np) { + pr_err("Unable to locate struct iscsi_np from" + " struct iscsi_tpg_np\n"); + return -EINVAL; + } + + tpg = tpg_np->tpg; + if (iscsit_get_tpg(tpg) < 0) + return -EINVAL; + + if (op) { + int rc = request_module("ib_isert"); + if (rc != 0) + pr_warn("Unable to request_module for ib_isert\n"); + + tpg_np_iser = iscsit_tpg_add_network_portal(tpg, &np->np_sockaddr, + np->np_ip, tpg_np, ISCSI_INFINIBAND); + if (!tpg_np_iser || IS_ERR(tpg_np_iser)) + goto out; + } else { + tpg_np_iser = iscsit_tpg_locate_child_np(tpg_np, ISCSI_INFINIBAND); + if (!tpg_np_iser) + goto out; + + rc = iscsit_tpg_del_network_portal(tpg, tpg_np_iser); + if (rc < 0) + goto out; + } + + printk("lio_target_np_store_iser() done, op: %d\n", op); + + iscsit_put_tpg(tpg); + return count; +out: + iscsit_put_tpg(tpg); + return -EINVAL; +} + +TF_NP_BASE_ATTR(lio_target, iser, S_IRUGO | S_IWUSR); + static struct configfs_attribute *lio_target_portal_attrs[] = { &lio_target_np_sctp.attr, + &lio_target_np_iser.attr, NULL, }; @@ -1536,16 +1616,18 @@ static int lio_queue_data_in(struct se_cmd *se_cmd) struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd); cmd->i_state = ISTATE_SEND_DATAIN; - iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state); + cmd->conn->conn_transport->iscsit_queue_data_in(cmd->conn, cmd); + return 0; } static int lio_write_pending(struct se_cmd *se_cmd) { struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd); + struct iscsi_conn *conn = cmd->conn; if (!cmd->immediate_data && !cmd->unsolicited_data) - return iscsit_build_r2ts_for_cmd(cmd, cmd->conn, false); + return conn->conn_transport->iscsit_get_dataout(conn, cmd, false); return 0; } @@ -1567,7 +1649,8 @@ static int lio_queue_status(struct se_cmd *se_cmd) struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd); cmd->i_state = ISTATE_SEND_STATUS; - iscsit_add_cmd_to_response_queue(cmd, cmd->conn, cmd->i_state); + cmd->conn->conn_transport->iscsit_queue_status(cmd->conn, cmd); + return 0; } @@ -1696,11 +1779,17 @@ static void lio_set_default_node_attributes(struct se_node_acl *se_acl) iscsit_set_default_node_attribues(acl); } +static int lio_check_stop_free(struct se_cmd *se_cmd) +{ + return target_put_sess_cmd(se_cmd->se_sess, se_cmd); +} + static void lio_release_cmd(struct se_cmd *se_cmd) { struct iscsi_cmd *cmd = container_of(se_cmd, struct iscsi_cmd, se_cmd); - iscsit_release_cmd(cmd); + pr_debug("Entering lio_release_cmd for se_cmd: %p\n", se_cmd); + cmd->release_cmd(cmd); } /* End functions for target_core_fabric_ops */ @@ -1740,6 +1829,7 @@ int iscsi_target_register_configfs(void) fabric->tf_ops.tpg_alloc_fabric_acl = &lio_tpg_alloc_fabric_acl; fabric->tf_ops.tpg_release_fabric_acl = &lio_tpg_release_fabric_acl; fabric->tf_ops.tpg_get_inst_index = &lio_tpg_get_inst_index; + fabric->tf_ops.check_stop_free = &lio_check_stop_free, fabric->tf_ops.release_cmd = &lio_release_cmd; fabric->tf_ops.shutdown_session = &lio_tpg_shutdown_session; fabric->tf_ops.close_session = &lio_tpg_close_session; diff --git a/drivers/target/iscsi/iscsi_target_core.h b/drivers/target/iscsi/iscsi_target_core.h index 7a333d28d9a..60ec4b92be0 100644 --- a/drivers/target/iscsi/iscsi_target_core.h +++ b/drivers/target/iscsi/iscsi_target_core.h @@ -60,7 +60,7 @@ #define ISCSI_IOV_DATA_BUFFER 5 -enum tpg_np_network_transport_table { +enum iscsit_transport_type { ISCSI_TCP = 0, ISCSI_SCTP_TCP = 1, ISCSI_SCTP_UDP = 2, @@ -244,6 +244,11 @@ struct iscsi_conn_ops { u8 IFMarker; /* [0,1] == [No,Yes] */ u32 OFMarkInt; /* [1..65535] */ u32 IFMarkInt; /* [1..65535] */ + /* + * iSER specific connection parameters + */ + u32 InitiatorRecvDataSegmentLength; /* [512..2**24-1] */ + u32 TargetRecvDataSegmentLength; /* [512..2**24-1] */ }; struct iscsi_sess_ops { @@ -265,6 +270,10 @@ struct iscsi_sess_ops { u8 DataSequenceInOrder; /* [0,1] == [No,Yes] */ u8 ErrorRecoveryLevel; /* [0..2] */ u8 SessionType; /* [0,1] == [Normal,Discovery]*/ + /* + * iSER specific session parameters + */ + u8 RDMAExtensions; /* [0,1] == [No,Yes] */ }; struct iscsi_queue_req { @@ -284,6 +293,7 @@ struct iscsi_data_count { }; struct iscsi_param_list { + bool iser; struct list_head param_list; struct list_head extra_response_list; }; @@ -475,6 +485,7 @@ struct iscsi_cmd { u32 first_data_sg_off; u32 kmapped_nents; sense_reason_t sense_reason; + void (*release_cmd)(struct iscsi_cmd *); } ____cacheline_aligned; struct iscsi_tmr_req { @@ -503,6 +514,7 @@ struct iscsi_conn { u16 login_port; u16 local_port; int net_size; + int login_family; u32 auth_id; u32 conn_flags; /* Used for iscsi_tx_login_rsp() */ @@ -562,9 +574,12 @@ struct iscsi_conn { struct list_head immed_queue_list; struct list_head response_queue_list; struct iscsi_conn_ops *conn_ops; + struct iscsi_login *conn_login; + struct iscsit_transport *conn_transport; struct iscsi_param_list *param_list; /* Used for per connection auth state machine */ void *auth_protocol; + void *context; struct iscsi_login_thread_s *login_thread; struct iscsi_portal_group *tpg; /* Pointer to parent session */ @@ -663,6 +678,8 @@ struct iscsi_login { u8 first_request; u8 version_min; u8 version_max; + u8 login_complete; + u8 login_failed; char isid[6]; u32 cmd_sn; itt_t init_task_tag; @@ -670,10 +687,11 @@ struct iscsi_login { u32 rsp_length; u16 cid; u16 tsih; - char *req; - char *rsp; + char req[ISCSI_HDR_LEN]; + char rsp[ISCSI_HDR_LEN]; char *req_buf; char *rsp_buf; + struct iscsi_conn *conn; } ____cacheline_aligned; struct iscsi_node_attrib { @@ -754,6 +772,8 @@ struct iscsi_np { struct task_struct *np_thread; struct timer_list np_login_timer; struct iscsi_portal_group *np_login_tpg; + void *np_context; + struct iscsit_transport *np_transport; struct list_head np_list; } ____cacheline_aligned; diff --git a/drivers/target/iscsi/iscsi_target_device.c b/drivers/target/iscsi/iscsi_target_device.c index bcc409853a6..1b74033510a 100644 --- a/drivers/target/iscsi/iscsi_target_device.c +++ b/drivers/target/iscsi/iscsi_target_device.c @@ -60,8 +60,13 @@ void iscsit_increment_maxcmdsn(struct iscsi_cmd *cmd, struct iscsi_session *sess cmd->maxcmdsn_inc = 1; - mutex_lock(&sess->cmdsn_mutex); + if (!mutex_trylock(&sess->cmdsn_mutex)) { + sess->max_cmd_sn += 1; + pr_debug("Updated MaxCmdSN to 0x%08x\n", sess->max_cmd_sn); + return; + } sess->max_cmd_sn += 1; pr_debug("Updated MaxCmdSN to 0x%08x\n", sess->max_cmd_sn); mutex_unlock(&sess->cmdsn_mutex); } +EXPORT_SYMBOL(iscsit_increment_maxcmdsn); diff --git a/drivers/target/iscsi/iscsi_target_erl1.c b/drivers/target/iscsi/iscsi_target_erl1.c index 0b52a237130..7816af6cdd1 100644 --- a/drivers/target/iscsi/iscsi_target_erl1.c +++ b/drivers/target/iscsi/iscsi_target_erl1.c @@ -22,6 +22,7 @@ #include <scsi/iscsi_proto.h> #include <target/target_core_base.h> #include <target/target_core_fabric.h> +#include <target/iscsi/iscsi_transport.h> #include "iscsi_target_core.h" #include "iscsi_target_seq_pdu_list.h" @@ -53,6 +54,9 @@ int iscsit_dump_data_payload( u32 length, padding, offset = 0, size; struct kvec iov; + if (conn->sess->sess_ops->RDMAExtensions) + return 0; + length = (buf_len > OFFLOAD_BUF_SIZE) ? OFFLOAD_BUF_SIZE : buf_len; buf = kzalloc(length, GFP_ATOMIC); @@ -919,6 +923,7 @@ int iscsit_execute_ooo_cmdsns(struct iscsi_session *sess) int iscsit_execute_cmd(struct iscsi_cmd *cmd, int ooo) { struct se_cmd *se_cmd = &cmd->se_cmd; + struct iscsi_conn *conn = cmd->conn; int lr = 0; spin_lock_bh(&cmd->istate_lock); @@ -981,7 +986,7 @@ int iscsit_execute_cmd(struct iscsi_cmd *cmd, int ooo) return 0; iscsit_set_dataout_sequence_values(cmd); - iscsit_build_r2ts_for_cmd(cmd, cmd->conn, false); + conn->conn_transport->iscsit_get_dataout(conn, cmd, false); } return 0; } @@ -999,10 +1004,7 @@ int iscsit_execute_cmd(struct iscsi_cmd *cmd, int ooo) if (transport_check_aborted_status(se_cmd, 1) != 0) return 0; - iscsit_set_dataout_sequence_values(cmd); - spin_lock_bh(&cmd->dataout_timeout_lock); - iscsit_start_dataout_timer(cmd, cmd->conn); - spin_unlock_bh(&cmd->dataout_timeout_lock); + iscsit_set_unsoliticed_dataout(cmd); } return transport_handle_cdb_direct(&cmd->se_cmd); @@ -1290,3 +1292,4 @@ void iscsit_stop_dataout_timer(struct iscsi_cmd *cmd) cmd->init_task_tag); spin_unlock_bh(&cmd->dataout_timeout_lock); } +EXPORT_SYMBOL(iscsit_stop_dataout_timer); diff --git a/drivers/target/iscsi/iscsi_target_login.c b/drivers/target/iscsi/iscsi_target_login.c index 2535d4d46c0..bb5d5c5bce6 100644 --- a/drivers/target/iscsi/iscsi_target_login.c +++ b/drivers/target/iscsi/iscsi_target_login.c @@ -39,8 +39,39 @@ #include "iscsi_target.h" #include "iscsi_target_parameters.h" -static int iscsi_login_init_conn(struct iscsi_conn *conn) +#include <target/iscsi/iscsi_transport.h> + +static struct iscsi_login *iscsi_login_init_conn(struct iscsi_conn *conn) { + struct iscsi_login *login; + + login = kzalloc(sizeof(struct iscsi_login), GFP_KERNEL); + if (!login) { + pr_err("Unable to allocate memory for struct iscsi_login.\n"); + return NULL; + } + login->conn = conn; + login->first_request = 1; + + login->req_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL); + if (!login->req_buf) { + pr_err("Unable to allocate memory for response buffer.\n"); + goto out_login; + } + + login->rsp_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL); + if (!login->rsp_buf) { + pr_err("Unable to allocate memory for request buffer.\n"); + goto out_req_buf; + } + + conn->conn_ops = kzalloc(sizeof(struct iscsi_conn_ops), GFP_KERNEL); + if (!conn->conn_ops) { + pr_err("Unable to allocate memory for" + " struct iscsi_conn_ops.\n"); + goto out_rsp_buf; + } + init_waitqueue_head(&conn->queues_wq); INIT_LIST_HEAD(&conn->conn_list); INIT_LIST_HEAD(&conn->conn_cmd_list); @@ -62,10 +93,21 @@ static int iscsi_login_init_conn(struct iscsi_conn *conn) if (!zalloc_cpumask_var(&conn->conn_cpumask, GFP_KERNEL)) { pr_err("Unable to allocate conn->conn_cpumask\n"); - return -ENOMEM; + goto out_conn_ops; } + conn->conn_login = login; - return 0; + return login; + +out_conn_ops: + kfree(conn->conn_ops); +out_rsp_buf: + kfree(login->rsp_buf); +out_req_buf: + kfree(login->req_buf); +out_login: + kfree(login); + return NULL; } /* @@ -298,6 +340,7 @@ static int iscsi_login_zero_tsih_s2( struct iscsi_node_attrib *na; struct iscsi_session *sess = conn->sess; unsigned char buf[32]; + bool iser = false; sess->tpg = conn->tpg; @@ -319,7 +362,10 @@ static int iscsi_login_zero_tsih_s2( return -1; } - iscsi_set_keys_to_negotiate(0, conn->param_list); + if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) + iser = true; + + iscsi_set_keys_to_negotiate(conn->param_list, iser); if (sess->sess_ops->SessionType) return iscsi_set_keys_irrelevant_for_discovery( @@ -357,6 +403,56 @@ static int iscsi_login_zero_tsih_s2( if (iscsi_login_disable_FIM_keys(conn->param_list, conn) < 0) return -1; + /* + * Set RDMAExtensions=Yes by default for iSER enabled network portals + */ + if (iser) { + struct iscsi_param *param; + unsigned long mrdsl, off; + int rc; + + sprintf(buf, "RDMAExtensions=Yes"); + if (iscsi_change_param_value(buf, conn->param_list, 0) < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return -1; + } + /* + * Make MaxRecvDataSegmentLength PAGE_SIZE aligned for + * Immediate Data + Unsolicitied Data-OUT if necessary.. + */ + param = iscsi_find_param_from_key("MaxRecvDataSegmentLength", + conn->param_list); + if (!param) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return -1; + } + rc = strict_strtoul(param->value, 0, &mrdsl); + if (rc < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return -1; + } + off = mrdsl % PAGE_SIZE; + if (!off) + return 0; + + if (mrdsl < PAGE_SIZE) + mrdsl = PAGE_SIZE; + else + mrdsl -= off; + + pr_warn("Aligning ISER MaxRecvDataSegmentLength: %lu down" + " to PAGE_SIZE\n", mrdsl); + + sprintf(buf, "MaxRecvDataSegmentLength=%lu\n", mrdsl); + if (iscsi_change_param_value(buf, conn->param_list, 0) < 0) { + iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, + ISCSI_LOGIN_STATUS_NO_RESOURCES); + return -1; + } + } return 0; } @@ -436,6 +532,7 @@ static int iscsi_login_non_zero_tsih_s2( struct se_portal_group *se_tpg = &tpg->tpg_se_tpg; struct se_session *se_sess, *se_sess_tmp; struct iscsi_login_req *pdu = (struct iscsi_login_req *)buf; + bool iser = false; spin_lock_bh(&se_tpg->session_lock); list_for_each_entry_safe(se_sess, se_sess_tmp, &se_tpg->tpg_sess_list, @@ -485,7 +582,10 @@ static int iscsi_login_non_zero_tsih_s2( return -1; } - iscsi_set_keys_to_negotiate(0, conn->param_list); + if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) + iser = true; + + iscsi_set_keys_to_negotiate(conn->param_list, iser); /* * Need to send TargetPortalGroupTag back in first login response * on any iSCSI connection where the Initiator provides TargetName. @@ -574,6 +674,11 @@ int iscsi_login_post_auth_non_zero_tsih( static void iscsi_post_login_start_timers(struct iscsi_conn *conn) { struct iscsi_session *sess = conn->sess; + /* + * FIXME: Unsolicitied NopIN support for ISER + */ + if (conn->conn_transport->transport_type == ISCSI_INFINIBAND) + return; if (!sess->sess_ops->SessionType) iscsit_start_nopin_timer(conn); @@ -632,6 +737,7 @@ static int iscsi_post_login_handler( spin_unlock_bh(&sess->conn_lock); iscsi_post_login_start_timers(conn); + iscsi_activate_thread_set(conn, ts); /* * Determine CPU mask to ensure connection's RX and TX kthreads @@ -761,11 +867,11 @@ static void iscsi_stop_login_thread_timer(struct iscsi_np *np) spin_unlock_bh(&np->np_thread_lock); } -int iscsi_target_setup_login_socket( +int iscsit_setup_np( struct iscsi_np *np, struct __kernel_sockaddr_storage *sockaddr) { - struct socket *sock; + struct socket *sock = NULL; int backlog = 5, ret, opt = 0, len; switch (np->np_network_transport) { @@ -781,15 +887,15 @@ int iscsi_target_setup_login_socket( np->np_ip_proto = IPPROTO_SCTP; np->np_sock_type = SOCK_SEQPACKET; break; - case ISCSI_IWARP_TCP: - case ISCSI_IWARP_SCTP: - case ISCSI_INFINIBAND: default: pr_err("Unsupported network_transport: %d\n", np->np_network_transport); return -EINVAL; } + np->np_ip_proto = IPPROTO_TCP; + np->np_sock_type = SOCK_STREAM; + ret = sock_create(sockaddr->ss_family, np->np_sock_type, np->np_ip_proto, &sock); if (ret < 0) { @@ -853,7 +959,6 @@ int iscsi_target_setup_login_socket( } return 0; - fail: np->np_socket = NULL; if (sock) @@ -861,21 +966,169 @@ fail: return ret; } +int iscsi_target_setup_login_socket( + struct iscsi_np *np, + struct __kernel_sockaddr_storage *sockaddr) +{ + struct iscsit_transport *t; + int rc; + + t = iscsit_get_transport(np->np_network_transport); + if (!t) + return -EINVAL; + + rc = t->iscsit_setup_np(np, sockaddr); + if (rc < 0) { + iscsit_put_transport(t); + return rc; + } + + np->np_transport = t; + printk("Set np->np_transport to %p -> %s\n", np->np_transport, + np->np_transport->name); + return 0; +} + +int iscsit_accept_np(struct iscsi_np *np, struct iscsi_conn *conn) +{ + struct socket *new_sock, *sock = np->np_socket; + struct sockaddr_in sock_in; + struct sockaddr_in6 sock_in6; + int rc, err; + + rc = kernel_accept(sock, &new_sock, 0); + if (rc < 0) + return rc; + + conn->sock = new_sock; + conn->login_family = np->np_sockaddr.ss_family; + printk("iSCSI/TCP: Setup conn->sock from new_sock: %p\n", new_sock); + + if (np->np_sockaddr.ss_family == AF_INET6) { + memset(&sock_in6, 0, sizeof(struct sockaddr_in6)); + + rc = conn->sock->ops->getname(conn->sock, + (struct sockaddr *)&sock_in6, &err, 1); + if (!rc) { + snprintf(conn->login_ip, sizeof(conn->login_ip), "%pI6c", + &sock_in6.sin6_addr.in6_u); + conn->login_port = ntohs(sock_in6.sin6_port); + } + + rc = conn->sock->ops->getname(conn->sock, + (struct sockaddr *)&sock_in6, &err, 0); + if (!rc) { + snprintf(conn->local_ip, sizeof(conn->local_ip), "%pI6c", + &sock_in6.sin6_addr.in6_u); + conn->local_port = ntohs(sock_in6.sin6_port); + } + } else { + memset(&sock_in, 0, sizeof(struct sockaddr_in)); + + rc = conn->sock->ops->getname(conn->sock, + (struct sockaddr *)&sock_in, &err, 1); + if (!rc) { + sprintf(conn->login_ip, "%pI4", + &sock_in.sin_addr.s_addr); + conn->login_port = ntohs(sock_in.sin_port); + } + + rc = conn->sock->ops->getname(conn->sock, + (struct sockaddr *)&sock_in, &err, 0); + if (!rc) { + sprintf(conn->local_ip, "%pI4", + &sock_in.sin_addr.s_addr); + conn->local_port = ntohs(sock_in.sin_port); + } + } + + return 0; +} + +int iscsit_get_login_rx(struct iscsi_conn *conn, struct iscsi_login *login) +{ + struct iscsi_login_req *login_req; + u32 padding = 0, payload_length; + + if (iscsi_login_rx_data(conn, login->req, ISCSI_HDR_LEN) < 0) + return -1; + + login_req = (struct iscsi_login_req *)login->req; + payload_length = ntoh24(login_req->dlength); + padding = ((-payload_length) & 3); + + pr_debug("Got Login Command, Flags 0x%02x, ITT: 0x%08x," + " CmdSN: 0x%08x, ExpStatSN: 0x%08x, CID: %hu, Length: %u\n", + login_req->flags, login_req->itt, login_req->cmdsn, + login_req->exp_statsn, login_req->cid, payload_length); + /* + * Setup the initial iscsi_login values from the leading + * login request PDU. + */ + if (login->first_request) { + login_req = (struct iscsi_login_req *)login->req; + login->leading_connection = (!login_req->tsih) ? 1 : 0; + login->current_stage = ISCSI_LOGIN_CURRENT_STAGE(login_req->flags); + login->version_min = login_req->min_version; + login->version_max = login_req->max_version; + memcpy(login->isid, login_req->isid, 6); + login->cmd_sn = be32_to_cpu(login_req->cmdsn); + login->init_task_tag = login_req->itt; + login->initial_exp_statsn = be32_to_cpu(login_req->exp_statsn); + login->cid = be16_to_cpu(login_req->cid); + login->tsih = be16_to_cpu(login_req->tsih); + } + + if (iscsi_target_check_login_request(conn, login) < 0) + return -1; + + memset(login->req_buf, 0, MAX_KEY_VALUE_PAIRS); + if (iscsi_login_rx_data(conn, login->req_buf, + payload_length + padding) < 0) + return -1; + + return 0; +} + +int iscsit_put_login_tx(struct iscsi_conn *conn, struct iscsi_login *login, + u32 length) +{ + if (iscsi_login_tx_data(conn, login->rsp, login->rsp_buf, length) < 0) + return -1; + + return 0; +} + +static int +iscsit_conn_set_transport(struct iscsi_conn *conn, struct iscsit_transport *t) +{ + int rc; + + if (!t->owner) { + conn->conn_transport = t; + return 0; + } + + rc = try_module_get(t->owner); + if (!rc) { + pr_err("try_module_get() failed for %s\n", t->name); + return -EINVAL; + } + + conn->conn_transport = t; + return 0; +} + static int __iscsi_target_login_thread(struct iscsi_np *np) { - u8 buffer[ISCSI_HDR_LEN], iscsi_opcode, zero_tsih = 0; - int err, ret = 0, stop; + u8 *buffer, zero_tsih = 0; + int ret = 0, rc, stop; struct iscsi_conn *conn = NULL; struct iscsi_login *login; struct iscsi_portal_group *tpg = NULL; - struct socket *new_sock, *sock; - struct kvec iov; struct iscsi_login_req *pdu; - struct sockaddr_in sock_in; - struct sockaddr_in6 sock_in6; flush_signals(current); - sock = np->np_socket; spin_lock_bh(&np->np_thread_lock); if (np->np_thread_state == ISCSI_NP_THREAD_RESET) { @@ -886,75 +1139,76 @@ static int __iscsi_target_login_thread(struct iscsi_np *np) } spin_unlock_bh(&np->np_thread_lock); - if (kernel_accept(sock, &new_sock, 0) < 0) { - spin_lock_bh(&np->np_thread_lock); - if (np->np_thread_state == ISCSI_NP_THREAD_RESET) { - spin_unlock_bh(&np->np_thread_lock); - complete(&np->np_restart_comp); - /* Get another socket */ - return 1; - } - spin_unlock_bh(&np->np_thread_lock); - goto out; - } - iscsi_start_login_thread_timer(np); - conn = kzalloc(sizeof(struct iscsi_conn), GFP_KERNEL); if (!conn) { pr_err("Could not allocate memory for" " new connection\n"); - sock_release(new_sock); /* Get another socket */ return 1; } - pr_debug("Moving to TARG_CONN_STATE_FREE.\n"); conn->conn_state = TARG_CONN_STATE_FREE; - conn->sock = new_sock; - pr_debug("Moving to TARG_CONN_STATE_XPT_UP.\n"); - conn->conn_state = TARG_CONN_STATE_XPT_UP; + if (iscsit_conn_set_transport(conn, np->np_transport) < 0) { + kfree(conn); + return 1; + } - /* - * Allocate conn->conn_ops early as a failure calling - * iscsit_tx_login_rsp() below will call tx_data(). - */ - conn->conn_ops = kzalloc(sizeof(struct iscsi_conn_ops), GFP_KERNEL); - if (!conn->conn_ops) { - pr_err("Unable to allocate memory for" - " struct iscsi_conn_ops.\n"); - goto new_sess_out; + rc = np->np_transport->iscsit_accept_np(np, conn); + if (rc == -ENOSYS) { + complete(&np->np_restart_comp); + iscsit_put_transport(conn->conn_transport); + kfree(conn); + conn = NULL; + goto exit; + } else if (rc < 0) { + spin_lock_bh(&np->np_thread_lock); + if (np->np_thread_state == ISCSI_NP_THREAD_RESET) { + spin_unlock_bh(&np->np_thread_lock); + complete(&np->np_restart_comp); + if (ret == -ENODEV) { + iscsit_put_transport(conn->conn_transport); + kfree(conn); + conn = NULL; + goto out; + } + /* Get another socket */ + return 1; + } + spin_unlock_bh(&np->np_thread_lock); + iscsit_put_transport(conn->conn_transport); + kfree(conn); + conn = NULL; + goto out; } /* * Perform the remaining iSCSI connection initialization items.. */ - if (iscsi_login_init_conn(conn) < 0) - goto new_sess_out; - - memset(buffer, 0, ISCSI_HDR_LEN); - memset(&iov, 0, sizeof(struct kvec)); - iov.iov_base = buffer; - iov.iov_len = ISCSI_HDR_LEN; - - if (rx_data(conn, &iov, 1, ISCSI_HDR_LEN) <= 0) { - pr_err("rx_data() returned an error.\n"); + login = iscsi_login_init_conn(conn); + if (!login) { goto new_sess_out; } - iscsi_opcode = (buffer[0] & ISCSI_OPCODE_MASK); - if (!(iscsi_opcode & ISCSI_OP_LOGIN)) { - pr_err("First opcode is not login request," - " failing login request.\n"); - goto new_sess_out; - } + iscsi_start_login_thread_timer(np); - pdu = (struct iscsi_login_req *) buffer; + pr_debug("Moving to TARG_CONN_STATE_XPT_UP.\n"); + conn->conn_state = TARG_CONN_STATE_XPT_UP; + /* + * This will process the first login request + payload.. + */ + rc = np->np_transport->iscsit_get_login_rx(conn, login); + if (rc == 1) + return 1; + else if (rc < 0) + goto new_sess_out; + buffer = &login->req[0]; + pdu = (struct iscsi_login_req *)buffer; /* * Used by iscsit_tx_login_rsp() for Login Resonses PDUs * when Status-Class != 0. */ - conn->login_itt = pdu->itt; + conn->login_itt = pdu->itt; spin_lock_bh(&np->np_thread_lock); if (np->np_thread_state != ISCSI_NP_THREAD_ACTIVE) { @@ -967,61 +1221,11 @@ static int __iscsi_target_login_thread(struct iscsi_np *np) } spin_unlock_bh(&np->np_thread_lock); - if (np->np_sockaddr.ss_family == AF_INET6) { - memset(&sock_in6, 0, sizeof(struct sockaddr_in6)); - - if (conn->sock->ops->getname(conn->sock, - (struct sockaddr *)&sock_in6, &err, 1) < 0) { - pr_err("sock_ops->getname() failed.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_TARGET_ERROR); - goto new_sess_out; - } - snprintf(conn->login_ip, sizeof(conn->login_ip), "%pI6c", - &sock_in6.sin6_addr.in6_u); - conn->login_port = ntohs(sock_in6.sin6_port); - - if (conn->sock->ops->getname(conn->sock, - (struct sockaddr *)&sock_in6, &err, 0) < 0) { - pr_err("sock_ops->getname() failed.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_TARGET_ERROR); - goto new_sess_out; - } - snprintf(conn->local_ip, sizeof(conn->local_ip), "%pI6c", - &sock_in6.sin6_addr.in6_u); - conn->local_port = ntohs(sock_in6.sin6_port); - - } else { - memset(&sock_in, 0, sizeof(struct sockaddr_in)); - - if (conn->sock->ops->getname(conn->sock, - (struct sockaddr *)&sock_in, &err, 1) < 0) { - pr_err("sock_ops->getname() failed.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_TARGET_ERROR); - goto new_sess_out; - } - sprintf(conn->login_ip, "%pI4", &sock_in.sin_addr.s_addr); - conn->login_port = ntohs(sock_in.sin_port); - - if (conn->sock->ops->getname(conn->sock, - (struct sockaddr *)&sock_in, &err, 0) < 0) { - pr_err("sock_ops->getname() failed.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_TARGET_ERROR); - goto new_sess_out; - } - sprintf(conn->local_ip, "%pI4", &sock_in.sin_addr.s_addr); - conn->local_port = ntohs(sock_in.sin_port); - } - conn->network_transport = np->np_network_transport; pr_debug("Received iSCSI login request from %s on %s Network" - " Portal %s:%hu\n", conn->login_ip, - (conn->network_transport == ISCSI_TCP) ? "TCP" : "SCTP", - conn->local_ip, conn->local_port); + " Portal %s:%hu\n", conn->login_ip, np->np_transport->name, + conn->local_ip, conn->local_port); pr_debug("Moving to TARG_CONN_STATE_IN_LOGIN.\n"); conn->conn_state = TARG_CONN_STATE_IN_LOGIN; @@ -1050,13 +1254,17 @@ static int __iscsi_target_login_thread(struct iscsi_np *np) if (iscsi_login_non_zero_tsih_s1(conn, buffer) < 0) goto new_sess_out; } - /* - * This will process the first login request, and call - * iscsi_target_locate_portal(), and return a valid struct iscsi_login. + * SessionType: Discovery + * + * Locates Default Portal + * + * SessionType: Normal + * + * Locates Target Portal from NP -> Target IQN */ - login = iscsi_target_init_negotiation(np, conn, buffer); - if (!login) { + rc = iscsi_target_locate_portal(np, conn, login); + if (rc < 0) { tpg = conn->tpg; goto new_sess_out; } @@ -1068,15 +1276,11 @@ static int __iscsi_target_login_thread(struct iscsi_np *np) } if (zero_tsih) { - if (iscsi_login_zero_tsih_s2(conn) < 0) { - iscsi_target_nego_release(login, conn); + if (iscsi_login_zero_tsih_s2(conn) < 0) goto new_sess_out; - } } else { - if (iscsi_login_non_zero_tsih_s2(conn, buffer) < 0) { - iscsi_target_nego_release(login, conn); + if (iscsi_login_non_zero_tsih_s2(conn, buffer) < 0) goto old_sess_out; - } } if (iscsi_target_start_negotiation(login, conn) < 0) @@ -1153,8 +1357,18 @@ old_sess_out: iscsi_release_param_list(conn->param_list); conn->param_list = NULL; } - if (conn->sock) + iscsi_target_nego_release(conn); + + if (conn->sock) { sock_release(conn->sock); + conn->sock = NULL; + } + + if (conn->conn_transport->iscsit_free_conn) + conn->conn_transport->iscsit_free_conn(conn); + + iscsit_put_transport(conn->conn_transport); + kfree(conn); if (tpg) { @@ -1172,11 +1386,13 @@ out: /* Wait for another socket.. */ if (!stop) return 1; - +exit: iscsi_stop_login_thread_timer(np); spin_lock_bh(&np->np_thread_lock); np->np_thread_state = ISCSI_NP_THREAD_EXIT; + np->np_thread = NULL; spin_unlock_bh(&np->np_thread_lock); + return 0; } diff --git a/drivers/target/iscsi/iscsi_target_login.h b/drivers/target/iscsi/iscsi_target_login.h index 091dcae2532..63efd287845 100644 --- a/drivers/target/iscsi/iscsi_target_login.h +++ b/drivers/target/iscsi/iscsi_target_login.h @@ -4,8 +4,14 @@ extern int iscsi_login_setup_crypto(struct iscsi_conn *); extern int iscsi_check_for_session_reinstatement(struct iscsi_conn *); extern int iscsi_login_post_auth_non_zero_tsih(struct iscsi_conn *, u16, u32); +extern int iscsit_setup_np(struct iscsi_np *, + struct __kernel_sockaddr_storage *); extern int iscsi_target_setup_login_socket(struct iscsi_np *, struct __kernel_sockaddr_storage *); +extern int iscsit_accept_np(struct iscsi_np *, struct iscsi_conn *); +extern int iscsit_get_login_rx(struct iscsi_conn *, struct iscsi_login *); +extern int iscsit_put_login_tx(struct iscsi_conn *, struct iscsi_login *, u32); +extern void iscsit_free_conn(struct iscsi_np *, struct iscsi_conn *); extern int iscsi_target_login_thread(void *); extern int iscsi_login_disable_FIM_keys(struct iscsi_param_list *, struct iscsi_conn *); diff --git a/drivers/target/iscsi/iscsi_target_nego.c b/drivers/target/iscsi/iscsi_target_nego.c index 9d902aefe01..7ad912060e2 100644 --- a/drivers/target/iscsi/iscsi_target_nego.c +++ b/drivers/target/iscsi/iscsi_target_nego.c @@ -22,6 +22,7 @@ #include <scsi/iscsi_proto.h> #include <target/target_core_base.h> #include <target/target_core_fabric.h> +#include <target/iscsi/iscsi_transport.h> #include "iscsi_target_core.h" #include "iscsi_target_parameters.h" @@ -169,7 +170,7 @@ static void iscsi_remove_failed_auth_entry(struct iscsi_conn *conn) kfree(conn->auth_protocol); } -static int iscsi_target_check_login_request( +int iscsi_target_check_login_request( struct iscsi_conn *conn, struct iscsi_login *login) { @@ -200,8 +201,8 @@ static int iscsi_target_check_login_request( return -1; } - req_csg = (login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2; - req_nsg = (login_req->flags & ISCSI_FLAG_LOGIN_NEXT_STAGE_MASK); + req_csg = ISCSI_LOGIN_CURRENT_STAGE(login_req->flags); + req_nsg = ISCSI_LOGIN_NEXT_STAGE(login_req->flags); if (req_csg != login->current_stage) { pr_err("Initiator unexpectedly changed login stage" @@ -352,11 +353,8 @@ static int iscsi_target_do_tx_login_io(struct iscsi_conn *conn, struct iscsi_log padding = ((-login->rsp_length) & 3); - if (iscsi_login_tx_data( - conn, - login->rsp, - login->rsp_buf, - login->rsp_length + padding) < 0) + if (conn->conn_transport->iscsit_put_login_tx(conn, login, + login->rsp_length + padding) < 0) return -1; login->rsp_length = 0; @@ -368,72 +366,12 @@ static int iscsi_target_do_tx_login_io(struct iscsi_conn *conn, struct iscsi_log return 0; } -static int iscsi_target_do_rx_login_io(struct iscsi_conn *conn, struct iscsi_login *login) -{ - u32 padding = 0, payload_length; - struct iscsi_login_req *login_req; - - if (iscsi_login_rx_data(conn, login->req, ISCSI_HDR_LEN) < 0) - return -1; - - login_req = (struct iscsi_login_req *) login->req; - payload_length = ntoh24(login_req->dlength); - - pr_debug("Got Login Command, Flags 0x%02x, ITT: 0x%08x," - " CmdSN: 0x%08x, ExpStatSN: 0x%08x, CID: %hu, Length: %u\n", - login_req->flags, login_req->itt, login_req->cmdsn, - login_req->exp_statsn, login_req->cid, payload_length); - - if (iscsi_target_check_login_request(conn, login) < 0) - return -1; - - padding = ((-payload_length) & 3); - memset(login->req_buf, 0, MAX_KEY_VALUE_PAIRS); - - if (iscsi_login_rx_data( - conn, - login->req_buf, - payload_length + padding) < 0) - return -1; - - return 0; -} - static int iscsi_target_do_login_io(struct iscsi_conn *conn, struct iscsi_login *login) { if (iscsi_target_do_tx_login_io(conn, login) < 0) return -1; - if (iscsi_target_do_rx_login_io(conn, login) < 0) - return -1; - - return 0; -} - -static int iscsi_target_get_initial_payload( - struct iscsi_conn *conn, - struct iscsi_login *login) -{ - u32 padding = 0, payload_length; - struct iscsi_login_req *login_req; - - login_req = (struct iscsi_login_req *) login->req; - payload_length = ntoh24(login_req->dlength); - - pr_debug("Got Login Command, Flags 0x%02x, ITT: 0x%08x," - " CmdSN: 0x%08x, ExpStatSN: 0x%08x, Length: %u\n", - login_req->flags, login_req->itt, login_req->cmdsn, - login_req->exp_statsn, payload_length); - - if (iscsi_target_check_login_request(conn, login) < 0) - return -1; - - padding = ((-payload_length) & 3); - - if (iscsi_login_rx_data( - conn, - login->req_buf, - payload_length + padding) < 0) + if (conn->conn_transport->iscsit_get_login_rx(conn, login) < 0) return -1; return 0; @@ -681,9 +619,9 @@ static int iscsi_target_do_login(struct iscsi_conn *conn, struct iscsi_login *lo return -1; } - switch ((login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2) { + switch (ISCSI_LOGIN_CURRENT_STAGE(login_req->flags)) { case 0: - login_rsp->flags |= (0 & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK); + login_rsp->flags &= ~ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK; if (iscsi_target_handle_csg_zero(conn, login) < 0) return -1; break; @@ -693,6 +631,7 @@ static int iscsi_target_do_login(struct iscsi_conn *conn, struct iscsi_login *lo return -1; if (login_rsp->flags & ISCSI_FLAG_LOGIN_TRANSIT) { login->tsih = conn->sess->tsih; + login->login_complete = 1; if (iscsi_target_do_tx_login_io(conn, login) < 0) return -1; @@ -702,8 +641,7 @@ static int iscsi_target_do_login(struct iscsi_conn *conn, struct iscsi_login *lo default: pr_err("Illegal CSG: %d received from" " Initiator, protocol error.\n", - (login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) - >> 2); + ISCSI_LOGIN_CURRENT_STAGE(login_req->flags)); break; } @@ -737,7 +675,7 @@ static void iscsi_initiatorname_tolower( /* * Processes the first Login Request.. */ -static int iscsi_target_locate_portal( +int iscsi_target_locate_portal( struct iscsi_np *np, struct iscsi_conn *conn, struct iscsi_login *login) @@ -753,22 +691,6 @@ static int iscsi_target_locate_portal( login_req = (struct iscsi_login_req *) login->req; payload_length = ntoh24(login_req->dlength); - login->first_request = 1; - login->leading_connection = (!login_req->tsih) ? 1 : 0; - login->current_stage = - (login_req->flags & ISCSI_FLAG_LOGIN_CURRENT_STAGE_MASK) >> 2; - login->version_min = login_req->min_version; - login->version_max = login_req->max_version; - memcpy(login->isid, login_req->isid, 6); - login->cmd_sn = be32_to_cpu(login_req->cmdsn); - login->init_task_tag = login_req->itt; - login->initial_exp_statsn = be32_to_cpu(login_req->exp_statsn); - login->cid = be16_to_cpu(login_req->cid); - login->tsih = be16_to_cpu(login_req->tsih); - - if (iscsi_target_get_initial_payload(conn, login) < 0) - return -1; - tmpbuf = kzalloc(payload_length + 1, GFP_KERNEL); if (!tmpbuf) { pr_err("Unable to allocate memory for tmpbuf.\n"); @@ -800,6 +722,8 @@ static int iscsi_target_locate_portal( start += strlen(key) + strlen(value) + 2; } + printk("i_buf: %s, s_buf: %s, t_buf: %s\n", i_buf, s_buf, t_buf); + /* * See 5.3. Login Phase. */ @@ -958,100 +882,30 @@ out: return ret; } -struct iscsi_login *iscsi_target_init_negotiation( - struct iscsi_np *np, - struct iscsi_conn *conn, - char *login_pdu) -{ - struct iscsi_login *login; - - login = kzalloc(sizeof(struct iscsi_login), GFP_KERNEL); - if (!login) { - pr_err("Unable to allocate memory for struct iscsi_login.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_NO_RESOURCES); - return NULL; - } - - login->req = kmemdup(login_pdu, ISCSI_HDR_LEN, GFP_KERNEL); - if (!login->req) { - pr_err("Unable to allocate memory for Login Request.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_NO_RESOURCES); - goto out; - } - - login->req_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL); - if (!login->req_buf) { - pr_err("Unable to allocate memory for response buffer.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_NO_RESOURCES); - goto out; - } - /* - * SessionType: Discovery - * - * Locates Default Portal - * - * SessionType: Normal - * - * Locates Target Portal from NP -> Target IQN - */ - if (iscsi_target_locate_portal(np, conn, login) < 0) { - goto out; - } - - return login; -out: - kfree(login->req); - kfree(login->req_buf); - kfree(login); - - return NULL; -} - int iscsi_target_start_negotiation( struct iscsi_login *login, struct iscsi_conn *conn) { - int ret = -1; - - login->rsp = kzalloc(ISCSI_HDR_LEN, GFP_KERNEL); - if (!login->rsp) { - pr_err("Unable to allocate memory for" - " Login Response.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_NO_RESOURCES); - ret = -1; - goto out; - } - - login->rsp_buf = kzalloc(MAX_KEY_VALUE_PAIRS, GFP_KERNEL); - if (!login->rsp_buf) { - pr_err("Unable to allocate memory for" - " request buffer.\n"); - iscsit_tx_login_rsp(conn, ISCSI_STATUS_CLS_TARGET_ERR, - ISCSI_LOGIN_STATUS_NO_RESOURCES); - ret = -1; - goto out; - } + int ret; ret = iscsi_target_do_login(conn, login); -out: if (ret != 0) iscsi_remove_failed_auth_entry(conn); - iscsi_target_nego_release(login, conn); + iscsi_target_nego_release(conn); return ret; } -void iscsi_target_nego_release( - struct iscsi_login *login, - struct iscsi_conn *conn) +void iscsi_target_nego_release(struct iscsi_conn *conn) { - kfree(login->req); - kfree(login->rsp); + struct iscsi_login *login = conn->conn_login; + + if (!login) + return; + kfree(login->req_buf); kfree(login->rsp_buf); kfree(login); + + conn->conn_login = NULL; } diff --git a/drivers/target/iscsi/iscsi_target_nego.h b/drivers/target/iscsi/iscsi_target_nego.h index 92e133a5158..f021cbd330e 100644 --- a/drivers/target/iscsi/iscsi_target_nego.h +++ b/drivers/target/iscsi/iscsi_target_nego.h @@ -7,11 +7,14 @@ extern void convert_null_to_semi(char *, int); extern int extract_param(const char *, const char *, unsigned int, char *, unsigned char *); -extern struct iscsi_login *iscsi_target_init_negotiation( - struct iscsi_np *, struct iscsi_conn *, char *); +extern int iscsi_target_check_login_request(struct iscsi_conn *, + struct iscsi_login *); +extern int iscsi_target_get_initial_payload(struct iscsi_conn *, + struct iscsi_login *); +extern int iscsi_target_locate_portal(struct iscsi_np *, struct iscsi_conn *, + struct iscsi_login *); extern int iscsi_target_start_negotiation( struct iscsi_login *, struct iscsi_conn *); -extern void iscsi_target_nego_release( - struct iscsi_login *, struct iscsi_conn *); +extern void iscsi_target_nego_release(struct iscsi_conn *); #endif /* ISCSI_TARGET_NEGO_H */ diff --git a/drivers/target/iscsi/iscsi_target_parameters.c b/drivers/target/iscsi/iscsi_target_parameters.c index ca2be406f14..f690be9e529 100644 --- a/drivers/target/iscsi/iscsi_target_parameters.c +++ b/drivers/target/iscsi/iscsi_target_parameters.c @@ -59,7 +59,7 @@ int iscsi_login_tx_data( char *text_buf, int text_length) { - int length, tx_sent; + int length, tx_sent, iov_cnt = 1; struct kvec iov[2]; length = (ISCSI_HDR_LEN + text_length); @@ -67,8 +67,12 @@ int iscsi_login_tx_data( memset(&iov[0], 0, 2 * sizeof(struct kvec)); iov[0].iov_len = ISCSI_HDR_LEN; iov[0].iov_base = pdu_buf; - iov[1].iov_len = text_length; - iov[1].iov_base = text_buf; + + if (text_buf && text_length) { + iov[1].iov_len = text_length; + iov[1].iov_base = text_buf; + iov_cnt++; + } /* * Initial Marker-less Interval. @@ -77,7 +81,7 @@ int iscsi_login_tx_data( */ conn->if_marker += length; - tx_sent = tx_data(conn, &iov[0], 2, length); + tx_sent = tx_data(conn, &iov[0], iov_cnt, length); if (tx_sent != length) { pr_err("tx_data returned %d, expecting %d.\n", tx_sent, length); @@ -429,6 +433,28 @@ int iscsi_create_default_params(struct iscsi_param_list **param_list_ptr) TYPERANGE_MARKINT, USE_INITIAL_ONLY); if (!param) goto out; + /* + * Extra parameters for ISER from RFC-5046 + */ + param = iscsi_set_default_param(pl, RDMAEXTENTIONS, INITIAL_RDMAEXTENTIONS, + PHASE_OPERATIONAL, SCOPE_SESSION_WIDE, SENDER_BOTH, + TYPERANGE_BOOL_AND, USE_LEADING_ONLY); + if (!param) + goto out; + + param = iscsi_set_default_param(pl, INITIATORRECVDATASEGMENTLENGTH, + INITIAL_INITIATORRECVDATASEGMENTLENGTH, + PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH, + TYPERANGE_512_TO_16777215, USE_ALL); + if (!param) + goto out; + + param = iscsi_set_default_param(pl, TARGETRECVDATASEGMENTLENGTH, + INITIAL_TARGETRECVDATASEGMENTLENGTH, + PHASE_OPERATIONAL, SCOPE_CONNECTION_ONLY, SENDER_BOTH, + TYPERANGE_512_TO_16777215, USE_ALL); + if (!param) + goto out; *param_list_ptr = pl; return 0; @@ -438,19 +464,23 @@ out: } int iscsi_set_keys_to_negotiate( - int sessiontype, - struct iscsi_param_list *param_list) + struct iscsi_param_list *param_list, + bool iser) { struct iscsi_param *param; + param_list->iser = iser; + list_for_each_entry(param, ¶m_list->param_list, p_list) { param->state = 0; if (!strcmp(param->name, AUTHMETHOD)) { SET_PSTATE_NEGOTIATE(param); } else if (!strcmp(param->name, HEADERDIGEST)) { - SET_PSTATE_NEGOTIATE(param); + if (iser == false) + SET_PSTATE_NEGOTIATE(param); } else if (!strcmp(param->name, DATADIGEST)) { - SET_PSTATE_NEGOTIATE(param); + if (iser == false) + SET_PSTATE_NEGOTIATE(param); } else if (!strcmp(param->name, MAXCONNECTIONS)) { SET_PSTATE_NEGOTIATE(param); } else if (!strcmp(param->name, TARGETNAME)) { @@ -469,7 +499,8 @@ int iscsi_set_keys_to_negotiate( } else if (!strcmp(param->name, IMMEDIATEDATA)) { SET_PSTATE_NEGOTIATE(param); } else if (!strcmp(param->name, MAXRECVDATASEGMENTLENGTH)) { - SET_PSTATE_NEGOTIATE(param); + if (iser == false) + SET_PSTATE_NEGOTIATE(param); } else if (!strcmp(param->name, MAXXMITDATASEGMENTLENGTH)) { continue; } else if (!strcmp(param->name, MAXBURSTLENGTH)) { @@ -498,6 +529,15 @@ int iscsi_set_keys_to_negotiate( SET_PSTATE_NEGOTIATE(param); } else if (!strcmp(param->name, OFMARKINT)) { SET_PSTATE_NEGOTIATE(param); + } else if (!strcmp(param->name, RDMAEXTENTIONS)) { + if (iser == true) + SET_PSTATE_NEGOTIATE(param); + } else if (!strcmp(param->name, INITIATORRECVDATASEGMENTLENGTH)) { + if (iser == true) + SET_PSTATE_NEGOTIATE(param); + } else if (!strcmp(param->name, TARGETRECVDATASEGMENTLENGTH)) { + if (iser == true) + SET_PSTATE_NEGOTIATE(param); } } @@ -540,6 +580,12 @@ int iscsi_set_keys_irrelevant_for_discovery( param->state &= ~PSTATE_NEGOTIATE; else if (!strcmp(param->name, OFMARKINT)) param->state &= ~PSTATE_NEGOTIATE; + else if (!strcmp(param->name, RDMAEXTENTIONS)) + param->state &= ~PSTATE_NEGOTIATE; + else if (!strcmp(param->name, INITIATORRECVDATASEGMENTLENGTH)) + param->state &= ~PSTATE_NEGOTIATE; + else if (!strcmp(param->name, TARGETRECVDATASEGMENTLENGTH)) + param->state &= ~PSTATE_NEGOTIATE; } return 0; @@ -1755,6 +1801,9 @@ void iscsi_set_connection_parameters( * this key is not sent over the wire. */ if (!strcmp(param->name, MAXXMITDATASEGMENTLENGTH)) { + if (param_list->iser == true) + continue; + ops->MaxXmitDataSegmentLength = simple_strtoul(param->value, &tmpptr, 0); pr_debug("MaxXmitDataSegmentLength: %s\n", @@ -1800,6 +1849,22 @@ void iscsi_set_connection_parameters( simple_strtoul(param->value, &tmpptr, 0); pr_debug("IFMarkInt: %s\n", param->value); + } else if (!strcmp(param->name, INITIATORRECVDATASEGMENTLENGTH)) { + ops->InitiatorRecvDataSegmentLength = + simple_strtoul(param->value, &tmpptr, 0); + pr_debug("InitiatorRecvDataSegmentLength: %s\n", + param->value); + ops->MaxRecvDataSegmentLength = + ops->InitiatorRecvDataSegmentLength; + pr_debug("Set MRDSL from InitiatorRecvDataSegmentLength\n"); + } else if (!strcmp(param->name, TARGETRECVDATASEGMENTLENGTH)) { + ops->TargetRecvDataSegmentLength = + simple_strtoul(param->value, &tmpptr, 0); + pr_debug("TargetRecvDataSegmentLength: %s\n", + param->value); + ops->MaxXmitDataSegmentLength = + ops->TargetRecvDataSegmentLength; + pr_debug("Set MXDSL from TargetRecvDataSegmentLength\n"); } } pr_debug("----------------------------------------------------" @@ -1912,6 +1977,10 @@ void iscsi_set_session_parameters( ops->SessionType = !strcmp(param->value, DISCOVERY); pr_debug("SessionType: %s\n", param->value); + } else if (!strcmp(param->name, RDMAEXTENTIONS)) { + ops->RDMAExtensions = !strcmp(param->value, YES); + pr_debug("RDMAExtensions: %s\n", + param->value); } } pr_debug("----------------------------------------------------" diff --git a/drivers/target/iscsi/iscsi_target_parameters.h b/drivers/target/iscsi/iscsi_target_parameters.h index 1e1b7504a76..f31b9c4b83f 100644 --- a/drivers/target/iscsi/iscsi_target_parameters.h +++ b/drivers/target/iscsi/iscsi_target_parameters.h @@ -27,7 +27,7 @@ extern void iscsi_dump_conn_ops(struct iscsi_conn_ops *); extern void iscsi_dump_sess_ops(struct iscsi_sess_ops *); extern void iscsi_print_params(struct iscsi_param_list *); extern int iscsi_create_default_params(struct iscsi_param_list **); -extern int iscsi_set_keys_to_negotiate(int, struct iscsi_param_list *); +extern int iscsi_set_keys_to_negotiate(struct iscsi_param_list *, bool); extern int iscsi_set_keys_irrelevant_for_discovery(struct iscsi_param_list *); extern int iscsi_copy_param_list(struct iscsi_param_list **, struct iscsi_param_list *, int); @@ -89,6 +89,13 @@ extern void iscsi_set_session_parameters(struct iscsi_sess_ops *, #define X_EXTENSIONKEY_CISCO_OLD "X-com.cisco.iscsi.draft" /* + * Parameter names of iSCSI Extentions for RDMA (iSER). See RFC-5046 + */ +#define RDMAEXTENTIONS "RDMAExtensions" +#define INITIATORRECVDATASEGMENTLENGTH "InitiatorRecvDataSegmentLength" +#define TARGETRECVDATASEGMENTLENGTH "TargetRecvDataSegmentLength" + +/* * For AuthMethod. */ #define KRB5 "KRB5" @@ -133,6 +140,13 @@ extern void iscsi_set_session_parameters(struct iscsi_sess_ops *, #define INITIAL_OFMARKINT "2048~65535" /* + * Initial values for iSER parameters following RFC-5046 Section 6 + */ +#define INITIAL_RDMAEXTENTIONS NO +#define INITIAL_INITIATORRECVDATASEGMENTLENGTH "262144" +#define INITIAL_TARGETRECVDATASEGMENTLENGTH "8192" + +/* * For [Header,Data]Digests. */ #define CRC32C "CRC32C" diff --git a/drivers/target/iscsi/iscsi_target_tmr.c b/drivers/target/iscsi/iscsi_target_tmr.c index 9d4417aae92..b997e5da47d 100644 --- a/drivers/target/iscsi/iscsi_target_tmr.c +++ b/drivers/target/iscsi/iscsi_target_tmr.c @@ -23,6 +23,7 @@ #include <scsi/iscsi_proto.h> #include <target/target_core_base.h> #include <target/target_core_fabric.h> +#include <target/iscsi/iscsi_transport.h> #include "iscsi_target_core.h" #include "iscsi_target_seq_pdu_list.h" @@ -301,7 +302,7 @@ static int iscsit_task_reassign_complete_write( /* * iscsit_build_r2ts_for_cmd() can handle the rest from here. */ - return iscsit_build_r2ts_for_cmd(cmd, conn, true); + return conn->conn_transport->iscsit_get_dataout(conn, cmd, true); } static int iscsit_task_reassign_complete_read( @@ -471,6 +472,7 @@ int iscsit_tmr_post_handler(struct iscsi_cmd *cmd, struct iscsi_conn *conn) return 0; } +EXPORT_SYMBOL(iscsit_tmr_post_handler); /* * Nothing to do here, but leave it for good measure. :-) diff --git a/drivers/target/iscsi/iscsi_target_tpg.c b/drivers/target/iscsi/iscsi_target_tpg.c index ee8f8c66248..439260b7d87 100644 --- a/drivers/target/iscsi/iscsi_target_tpg.c +++ b/drivers/target/iscsi/iscsi_target_tpg.c @@ -31,6 +31,8 @@ #include "iscsi_target.h" #include "iscsi_target_parameters.h" +#include <target/iscsi/iscsi_transport.h> + struct iscsi_portal_group *iscsit_alloc_portal_group(struct iscsi_tiqn *tiqn, u16 tpgt) { struct iscsi_portal_group *tpg; @@ -508,7 +510,7 @@ struct iscsi_tpg_np *iscsit_tpg_add_network_portal( pr_debug("CORE[%s] - Added Network Portal: %s:%hu,%hu on %s\n", tpg->tpg_tiqn->tiqn, np->np_ip, np->np_port, tpg->tpgt, - (np->np_network_transport == ISCSI_TCP) ? "TCP" : "SCTP"); + np->np_transport->name); return tpg_np; } @@ -522,7 +524,7 @@ static int iscsit_tpg_release_np( pr_debug("CORE[%s] - Removed Network Portal: %s:%hu,%hu on %s\n", tpg->tpg_tiqn->tiqn, np->np_ip, np->np_port, tpg->tpgt, - (np->np_network_transport == ISCSI_TCP) ? "TCP" : "SCTP"); + np->np_transport->name); tpg_np->tpg_np = NULL; tpg_np->tpg = NULL; diff --git a/drivers/target/iscsi/iscsi_target_transport.c b/drivers/target/iscsi/iscsi_target_transport.c new file mode 100644 index 00000000000..882728fac30 --- /dev/null +++ b/drivers/target/iscsi/iscsi_target_transport.c @@ -0,0 +1,55 @@ +#include <linux/spinlock.h> +#include <linux/list.h> +#include <target/iscsi/iscsi_transport.h> + +static LIST_HEAD(g_transport_list); +static DEFINE_MUTEX(transport_mutex); + +struct iscsit_transport *iscsit_get_transport(int type) +{ + struct iscsit_transport *t; + + mutex_lock(&transport_mutex); + list_for_each_entry(t, &g_transport_list, t_node) { + if (t->transport_type == type) { + if (t->owner && !try_module_get(t->owner)) { + t = NULL; + } + mutex_unlock(&transport_mutex); + return t; + } + } + mutex_unlock(&transport_mutex); + + return NULL; +} + +void iscsit_put_transport(struct iscsit_transport *t) +{ + if (t->owner) + module_put(t->owner); +} + +int iscsit_register_transport(struct iscsit_transport *t) +{ + INIT_LIST_HEAD(&t->t_node); + + mutex_lock(&transport_mutex); + list_add_tail(&t->t_node, &g_transport_list); + mutex_unlock(&transport_mutex); + + pr_debug("Registered iSCSI transport: %s\n", t->name); + + return 0; +} +EXPORT_SYMBOL(iscsit_register_transport); + +void iscsit_unregister_transport(struct iscsit_transport *t) +{ + mutex_lock(&transport_mutex); + list_del(&t->t_node); + mutex_unlock(&transport_mutex); + + pr_debug("Unregistered iSCSI transport: %s\n", t->name); +} +EXPORT_SYMBOL(iscsit_unregister_transport); diff --git a/drivers/target/iscsi/iscsi_target_util.c b/drivers/target/iscsi/iscsi_target_util.c index 7ce350578c8..2cc6c9a3ffb 100644 --- a/drivers/target/iscsi/iscsi_target_util.c +++ b/drivers/target/iscsi/iscsi_target_util.c @@ -24,6 +24,7 @@ #include <target/target_core_base.h> #include <target/target_core_fabric.h> #include <target/target_core_configfs.h> +#include <target/iscsi/iscsi_transport.h> #include "iscsi_target_core.h" #include "iscsi_target_parameters.h" @@ -148,6 +149,18 @@ void iscsit_free_r2ts_from_list(struct iscsi_cmd *cmd) spin_unlock_bh(&cmd->r2t_lock); } +struct iscsi_cmd *iscsit_alloc_cmd(struct iscsi_conn *conn, gfp_t gfp_mask) +{ + struct iscsi_cmd *cmd; + + cmd = kmem_cache_zalloc(lio_cmd_cache, gfp_mask); + if (!cmd) + return NULL; + + cmd->release_cmd = &iscsit_release_cmd; + return cmd; +} + /* * May be called from software interrupt (timer) context for allocating * iSCSI NopINs. @@ -156,13 +169,12 @@ struct iscsi_cmd *iscsit_allocate_cmd(struct iscsi_conn *conn, gfp_t gfp_mask) { struct iscsi_cmd *cmd; - cmd = kmem_cache_zalloc(lio_cmd_cache, gfp_mask); + cmd = conn->conn_transport->iscsit_alloc_cmd(conn, gfp_mask); if (!cmd) { pr_err("Unable to allocate memory for struct iscsi_cmd.\n"); return NULL; } - - cmd->conn = conn; + cmd->conn = conn; INIT_LIST_HEAD(&cmd->i_conn_node); INIT_LIST_HEAD(&cmd->datain_list); INIT_LIST_HEAD(&cmd->cmd_r2t_list); @@ -175,6 +187,7 @@ struct iscsi_cmd *iscsit_allocate_cmd(struct iscsi_conn *conn, gfp_t gfp_mask) return cmd; } +EXPORT_SYMBOL(iscsit_allocate_cmd); struct iscsi_seq *iscsit_get_seq_holder_for_datain( struct iscsi_cmd *cmd, @@ -304,6 +317,7 @@ int iscsit_sequence_cmd( return ret; } +EXPORT_SYMBOL(iscsit_sequence_cmd); int iscsit_check_unsolicited_dataout(struct iscsi_cmd *cmd, unsigned char *buf) { @@ -689,6 +703,11 @@ void iscsit_free_cmd(struct iscsi_cmd *cmd) */ switch (cmd->iscsi_opcode) { case ISCSI_OP_SCSI_CMD: + if (cmd->data_direction == DMA_TO_DEVICE) + iscsit_stop_dataout_timer(cmd); + /* + * Fallthrough + */ case ISCSI_OP_SCSI_TMFUNC: transport_generic_free_cmd(&cmd->se_cmd, 1); break; @@ -704,7 +723,7 @@ void iscsit_free_cmd(struct iscsi_cmd *cmd) } /* Fall-through */ default: - iscsit_release_cmd(cmd); + cmd->release_cmd(cmd); break; } } @@ -1226,34 +1245,19 @@ send_datacrc: */ int iscsit_tx_login_rsp(struct iscsi_conn *conn, u8 status_class, u8 status_detail) { - u8 iscsi_hdr[ISCSI_HDR_LEN]; - int err; - struct kvec iov; struct iscsi_login_rsp *hdr; + struct iscsi_login *login = conn->conn_login; + login->login_failed = 1; iscsit_collect_login_stats(conn, status_class, status_detail); - memset(&iov, 0, sizeof(struct kvec)); - memset(&iscsi_hdr, 0x0, ISCSI_HDR_LEN); - - hdr = (struct iscsi_login_rsp *)&iscsi_hdr; + hdr = (struct iscsi_login_rsp *)&login->rsp[0]; hdr->opcode = ISCSI_OP_LOGIN_RSP; hdr->status_class = status_class; hdr->status_detail = status_detail; hdr->itt = conn->login_itt; - iov.iov_base = &iscsi_hdr; - iov.iov_len = ISCSI_HDR_LEN; - - PRINT_BUFF(iscsi_hdr, ISCSI_HDR_LEN); - - err = tx_data(conn, &iov, 1, ISCSI_HDR_LEN); - if (err != ISCSI_HDR_LEN) { - pr_err("tx_data returned less than expected\n"); - return -1; - } - - return 0; + return conn->conn_transport->iscsit_put_login_tx(conn, login, 0); } void iscsit_print_session_params(struct iscsi_session *sess) @@ -1432,7 +1436,8 @@ void iscsit_collect_login_stats( strcpy(ls->last_intr_fail_name, (intrname ? intrname->value : "Unknown")); - ls->last_intr_fail_ip_family = conn->sock->sk->sk_family; + ls->last_intr_fail_ip_family = conn->login_family; + snprintf(ls->last_intr_fail_ip_addr, IPV6_ADDRESS_SPACE, "%s", conn->login_ip); ls->last_fail_time = get_jiffies_64(); diff --git a/drivers/target/iscsi/iscsi_target_util.h b/drivers/target/iscsi/iscsi_target_util.h index 894d0f83792..4f8e01a4708 100644 --- a/drivers/target/iscsi/iscsi_target_util.h +++ b/drivers/target/iscsi/iscsi_target_util.h @@ -8,6 +8,7 @@ extern struct iscsi_r2t *iscsit_get_r2t_for_eos(struct iscsi_cmd *, u32, u32); extern struct iscsi_r2t *iscsit_get_r2t_from_list(struct iscsi_cmd *); extern void iscsit_free_r2t(struct iscsi_r2t *, struct iscsi_cmd *); extern void iscsit_free_r2ts_from_list(struct iscsi_cmd *); +extern struct iscsi_cmd *iscsit_alloc_cmd(struct iscsi_conn *, gfp_t); extern struct iscsi_cmd *iscsit_allocate_cmd(struct iscsi_conn *, gfp_t); extern struct iscsi_seq *iscsit_get_seq_holder_for_datain(struct iscsi_cmd *, u32); extern struct iscsi_seq *iscsit_get_seq_holder_for_r2t(struct iscsi_cmd *); diff --git a/drivers/target/target_core_file.c b/drivers/target/target_core_file.c index 17a6acbc3ab..58ed683e04a 100644 --- a/drivers/target/target_core_file.c +++ b/drivers/target/target_core_file.c @@ -30,8 +30,10 @@ #include <linux/slab.h> #include <linux/spinlock.h> #include <linux/module.h> +#include <linux/falloc.h> #include <scsi/scsi.h> #include <scsi/scsi_host.h> +#include <asm/unaligned.h> #include <target/target_core_base.h> #include <target/target_core_backend.h> @@ -166,6 +168,33 @@ static int fd_configure_device(struct se_device *dev) " block_device blocks: %llu logical_block_size: %d\n", dev_size, div_u64(dev_size, fd_dev->fd_block_size), fd_dev->fd_block_size); + /* + * Check if the underlying struct block_device request_queue supports + * the QUEUE_FLAG_DISCARD bit for UNMAP/WRITE_SAME in SCSI + TRIM + * in ATA and we need to set TPE=1 + */ + if (blk_queue_discard(q)) { + dev->dev_attrib.max_unmap_lba_count = + q->limits.max_discard_sectors; + /* + * Currently hardcoded to 1 in Linux/SCSI code.. + */ + dev->dev_attrib.max_unmap_block_desc_count = 1; + dev->dev_attrib.unmap_granularity = + q->limits.discard_granularity >> 9; + dev->dev_attrib.unmap_granularity_alignment = + q->limits.discard_alignment; + pr_debug("IFILE: BLOCK Discard support available," + " disabled by default\n"); + } + /* + * Enable write same emulation for IBLOCK and use 0xFFFF as + * the smaller WRITE_SAME(10) only has a two-byte block count. + */ + dev->dev_attrib.max_write_same_len = 0xFFFF; + + if (blk_queue_nonrot(q)) + dev->dev_attrib.is_nonrot = 1; } else { if (!(fd_dev->fbd_flags & FBDF_HAS_SIZE)) { pr_err("FILEIO: Missing fd_dev_size=" @@ -176,6 +205,23 @@ static int fd_configure_device(struct se_device *dev) dev->dev_attrib.hw_block_size = FD_BLOCKSIZE; dev->dev_attrib.hw_max_sectors = FD_MAX_SECTORS; + + /* + * Limit UNMAP emulation to 8k Number of LBAs (NoLB) + */ + dev->dev_attrib.max_unmap_lba_count = 0x2000; + /* + * Currently hardcoded to 1 in Linux/SCSI code.. + */ + dev->dev_attrib.max_unmap_block_desc_count = 1; + dev->dev_attrib.unmap_granularity = 1; + dev->dev_attrib.unmap_granularity_alignment = 0; + + /* + * Limit WRITE_SAME w/ UNMAP=0 emulation to 8k Number of LBAs (NoLB) + * based upon struct iovec limit for vfs_writev() + */ + dev->dev_attrib.max_write_same_len = 0x1000; } fd_dev->fd_block_size = dev->dev_attrib.hw_block_size; @@ -190,11 +236,6 @@ static int fd_configure_device(struct se_device *dev) fd_dev->fd_dev_id = fd_host->fd_host_dev_id_count++; fd_dev->fd_queue_depth = dev->queue_depth; - /* - * Limit WRITE_SAME w/ UNMAP=0 emulation to 8k Number of LBAs (NoLB) - * based upon struct iovec limit for vfs_writev() - */ - dev->dev_attrib.max_write_same_len = 0x1000; pr_debug("CORE_FILE[%u] - Added TCM FILEIO Device ID: %u at %s," " %llu total bytes\n", fd_host->fd_host_id, fd_dev->fd_dev_id, @@ -442,6 +483,75 @@ fd_execute_write_same(struct se_cmd *cmd) } static sense_reason_t +fd_do_unmap(struct se_cmd *cmd, void *priv, sector_t lba, sector_t nolb) +{ + struct file *file = priv; + struct inode *inode = file->f_mapping->host; + int ret; + + if (S_ISBLK(inode->i_mode)) { + /* The backend is block device, use discard */ + struct block_device *bdev = inode->i_bdev; + + ret = blkdev_issue_discard(bdev, lba, + nolb, GFP_KERNEL, 0); + if (ret < 0) { + pr_warn("FILEIO: blkdev_issue_discard() failed: %d\n", + ret); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + } else { + /* The backend is normal file, use fallocate */ + struct se_device *se_dev = cmd->se_dev; + loff_t pos = lba * se_dev->dev_attrib.block_size; + unsigned int len = nolb * se_dev->dev_attrib.block_size; + int mode = FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE; + + if (!file->f_op->fallocate) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + ret = file->f_op->fallocate(file, mode, pos, len); + if (ret < 0) { + pr_warn("FILEIO: fallocate() failed: %d\n", ret); + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + } + } + + return 0; +} + +static sense_reason_t +fd_execute_write_same_unmap(struct se_cmd *cmd) +{ + struct se_device *se_dev = cmd->se_dev; + struct fd_dev *fd_dev = FD_DEV(se_dev); + struct file *file = fd_dev->fd_file; + sector_t lba = cmd->t_task_lba; + sector_t nolb = sbc_get_write_same_sectors(cmd); + int ret; + + if (!nolb) { + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; + } + + ret = fd_do_unmap(cmd, file, lba, nolb); + if (ret) + return ret; + + target_complete_cmd(cmd, GOOD); + return 0; +} + +static sense_reason_t +fd_execute_unmap(struct se_cmd *cmd) +{ + struct file *file = FD_DEV(cmd->se_dev)->fd_file; + + return sbc_execute_unmap(cmd, fd_do_unmap, file); +} + +static sense_reason_t fd_execute_rw(struct se_cmd *cmd) { struct scatterlist *sgl = cmd->t_data_sg; @@ -600,6 +710,8 @@ static struct sbc_ops fd_sbc_ops = { .execute_rw = fd_execute_rw, .execute_sync_cache = fd_execute_sync_cache, .execute_write_same = fd_execute_write_same, + .execute_write_same_unmap = fd_execute_write_same_unmap, + .execute_unmap = fd_execute_unmap, }; static sense_reason_t diff --git a/drivers/target/target_core_iblock.c b/drivers/target/target_core_iblock.c index 8bcc514ec8b..07f5f94634b 100644 --- a/drivers/target/target_core_iblock.c +++ b/drivers/target/target_core_iblock.c @@ -380,104 +380,40 @@ iblock_execute_sync_cache(struct se_cmd *cmd) } static sense_reason_t -iblock_execute_unmap(struct se_cmd *cmd) +iblock_do_unmap(struct se_cmd *cmd, void *priv, + sector_t lba, sector_t nolb) { - struct se_device *dev = cmd->se_dev; - struct iblock_dev *ib_dev = IBLOCK_DEV(dev); - unsigned char *buf, *ptr = NULL; - sector_t lba; - int size; - u32 range; - sense_reason_t ret = 0; - int dl, bd_dl, err; - - /* We never set ANC_SUP */ - if (cmd->t_task_cdb[1]) - return TCM_INVALID_CDB_FIELD; - - if (cmd->data_length == 0) { - target_complete_cmd(cmd, SAM_STAT_GOOD); - return 0; - } + struct block_device *bdev = priv; + int ret; - if (cmd->data_length < 8) { - pr_warn("UNMAP parameter list length %u too small\n", - cmd->data_length); - return TCM_PARAMETER_LIST_LENGTH_ERROR; - } - - buf = transport_kmap_data_sg(cmd); - if (!buf) + ret = blkdev_issue_discard(bdev, lba, nolb, GFP_KERNEL, 0); + if (ret < 0) { + pr_err("blkdev_issue_discard() failed: %d\n", ret); return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - - dl = get_unaligned_be16(&buf[0]); - bd_dl = get_unaligned_be16(&buf[2]); - - size = cmd->data_length - 8; - if (bd_dl > size) - pr_warn("UNMAP parameter list length %u too small, ignoring bd_dl %u\n", - cmd->data_length, bd_dl); - else - size = bd_dl; - - if (size / 16 > dev->dev_attrib.max_unmap_block_desc_count) { - ret = TCM_INVALID_PARAMETER_LIST; - goto err; } - /* First UNMAP block descriptor starts at 8 byte offset */ - ptr = &buf[8]; - pr_debug("UNMAP: Sub: %s Using dl: %u bd_dl: %u size: %u" - " ptr: %p\n", dev->transport->name, dl, bd_dl, size, ptr); - - while (size >= 16) { - lba = get_unaligned_be64(&ptr[0]); - range = get_unaligned_be32(&ptr[8]); - pr_debug("UNMAP: Using lba: %llu and range: %u\n", - (unsigned long long)lba, range); - - if (range > dev->dev_attrib.max_unmap_lba_count) { - ret = TCM_INVALID_PARAMETER_LIST; - goto err; - } - - if (lba + range > dev->transport->get_blocks(dev) + 1) { - ret = TCM_ADDRESS_OUT_OF_RANGE; - goto err; - } - - err = blkdev_issue_discard(ib_dev->ibd_bd, lba, range, - GFP_KERNEL, 0); - if (err < 0) { - pr_err("blkdev_issue_discard() failed: %d\n", - err); - ret = TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - goto err; - } + return 0; +} - ptr += 16; - size -= 16; - } +static sense_reason_t +iblock_execute_unmap(struct se_cmd *cmd) +{ + struct block_device *bdev = IBLOCK_DEV(cmd->se_dev)->ibd_bd; -err: - transport_kunmap_data_sg(cmd); - if (!ret) - target_complete_cmd(cmd, GOOD); - return ret; + return sbc_execute_unmap(cmd, iblock_do_unmap, bdev); } static sense_reason_t iblock_execute_write_same_unmap(struct se_cmd *cmd) { - struct iblock_dev *ib_dev = IBLOCK_DEV(cmd->se_dev); - int rc; - - rc = blkdev_issue_discard(ib_dev->ibd_bd, cmd->t_task_lba, - sbc_get_write_same_sectors(cmd), GFP_KERNEL, 0); - if (rc < 0) { - pr_warn("blkdev_issue_discard() failed: %d\n", rc); - return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; - } + struct block_device *bdev = IBLOCK_DEV(cmd->se_dev)->ibd_bd; + sector_t lba = cmd->t_task_lba; + sector_t nolb = sbc_get_write_same_sectors(cmd); + int ret; + + ret = iblock_do_unmap(cmd, bdev, lba, nolb); + if (ret) + return ret; target_complete_cmd(cmd, GOOD); return 0; diff --git a/drivers/target/target_core_sbc.c b/drivers/target/target_core_sbc.c index 60d4b5185f3..bbc5b0ee2bd 100644 --- a/drivers/target/target_core_sbc.c +++ b/drivers/target/target_core_sbc.c @@ -596,3 +596,88 @@ u32 sbc_get_device_type(struct se_device *dev) return TYPE_DISK; } EXPORT_SYMBOL(sbc_get_device_type); + +sense_reason_t +sbc_execute_unmap(struct se_cmd *cmd, + sense_reason_t (*do_unmap_fn)(struct se_cmd *, void *, + sector_t, sector_t), + void *priv) +{ + struct se_device *dev = cmd->se_dev; + unsigned char *buf, *ptr = NULL; + sector_t lba; + int size; + u32 range; + sense_reason_t ret = 0; + int dl, bd_dl; + + /* We never set ANC_SUP */ + if (cmd->t_task_cdb[1]) + return TCM_INVALID_CDB_FIELD; + + if (cmd->data_length == 0) { + target_complete_cmd(cmd, SAM_STAT_GOOD); + return 0; + } + + if (cmd->data_length < 8) { + pr_warn("UNMAP parameter list length %u too small\n", + cmd->data_length); + return TCM_PARAMETER_LIST_LENGTH_ERROR; + } + + buf = transport_kmap_data_sg(cmd); + if (!buf) + return TCM_LOGICAL_UNIT_COMMUNICATION_FAILURE; + + dl = get_unaligned_be16(&buf[0]); + bd_dl = get_unaligned_be16(&buf[2]); + + size = cmd->data_length - 8; + if (bd_dl > size) + pr_warn("UNMAP parameter list length %u too small, ignoring bd_dl %u\n", + cmd->data_length, bd_dl); + else + size = bd_dl; + + if (size / 16 > dev->dev_attrib.max_unmap_block_desc_count) { + ret = TCM_INVALID_PARAMETER_LIST; + goto err; + } + + /* First UNMAP block descriptor starts at 8 byte offset */ + ptr = &buf[8]; + pr_debug("UNMAP: Sub: %s Using dl: %u bd_dl: %u size: %u" + " ptr: %p\n", dev->transport->name, dl, bd_dl, size, ptr); + + while (size >= 16) { + lba = get_unaligned_be64(&ptr[0]); + range = get_unaligned_be32(&ptr[8]); + pr_debug("UNMAP: Using lba: %llu and range: %u\n", + (unsigned long long)lba, range); + + if (range > dev->dev_attrib.max_unmap_lba_count) { + ret = TCM_INVALID_PARAMETER_LIST; + goto err; + } + + if (lba + range > dev->transport->get_blocks(dev) + 1) { + ret = TCM_ADDRESS_OUT_OF_RANGE; + goto err; + } + + ret = do_unmap_fn(cmd, priv, lba, range); + if (ret) + goto err; + + ptr += 16; + size -= 16; + } + +err: + transport_kunmap_data_sg(cmd); + if (!ret) + target_complete_cmd(cmd, GOOD); + return ret; +} +EXPORT_SYMBOL(sbc_execute_unmap); diff --git a/drivers/target/target_core_transport.c b/drivers/target/target_core_transport.c index 3243ea790ea..f8388b4024a 100644 --- a/drivers/target/target_core_transport.c +++ b/drivers/target/target_core_transport.c @@ -65,7 +65,6 @@ static void transport_complete_task_attr(struct se_cmd *cmd); static void transport_handle_queue_full(struct se_cmd *cmd, struct se_device *dev); static int transport_generic_get_mem(struct se_cmd *cmd); -static int target_get_sess_cmd(struct se_session *, struct se_cmd *, bool); static void transport_put_cmd(struct se_cmd *cmd); static void target_complete_ok_work(struct work_struct *work); @@ -2179,7 +2178,7 @@ EXPORT_SYMBOL(transport_generic_free_cmd); * @se_cmd: command descriptor to add * @ack_kref: Signal that fabric will perform an ack target_put_sess_cmd() */ -static int target_get_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cmd, +int target_get_sess_cmd(struct se_session *se_sess, struct se_cmd *se_cmd, bool ack_kref) { unsigned long flags; @@ -2208,6 +2207,7 @@ out: spin_unlock_irqrestore(&se_sess->sess_cmd_lock, flags); return ret; } +EXPORT_SYMBOL(target_get_sess_cmd); static void target_release_cmd_kref(struct kref *kref) { @@ -2765,8 +2765,13 @@ transport_send_check_condition_and_sense(struct se_cmd *cmd, /* CURRENT ERROR */ buffer[0] = 0x70; buffer[SPC_ADD_SENSE_LEN_OFFSET] = 10; - /* ILLEGAL REQUEST */ - buffer[SPC_SENSE_KEY_OFFSET] = ILLEGAL_REQUEST; + /* + * Returning ILLEGAL REQUEST would cause immediate IO errors on + * Solaris initiators. Returning NOT READY instead means the + * operations will be retried a finite number of times and we + * can survive intermittent errors. + */ + buffer[SPC_SENSE_KEY_OFFSET] = NOT_READY; /* LOGICAL UNIT COMMUNICATION FAILURE */ buffer[SPC_ASC_KEY_OFFSET] = 0x08; break; diff --git a/drivers/target/tcm_fc/tfc_io.c b/drivers/target/tcm_fc/tfc_io.c index b6fd4cf4284..e415af32115 100644 --- a/drivers/target/tcm_fc/tfc_io.c +++ b/drivers/target/tcm_fc/tfc_io.c @@ -103,6 +103,13 @@ int ft_queue_data_in(struct se_cmd *se_cmd) use_sg = !(remaining % 4); while (remaining) { + struct fc_seq *seq = cmd->seq; + + if (!seq) { + pr_debug("%s: Command aborted, xid 0x%x\n", + __func__, ep->xid); + break; + } if (!mem_len) { sg = sg_next(sg); mem_len = min((size_t)sg->length, remaining); @@ -169,7 +176,7 @@ int ft_queue_data_in(struct se_cmd *se_cmd) f_ctl |= FC_FC_END_SEQ; fc_fill_fc_hdr(fp, FC_RCTL_DD_SOL_DATA, ep->did, ep->sid, FC_TYPE_FCP, f_ctl, fh_off); - error = lport->tt.seq_send(lport, cmd->seq, fp); + error = lport->tt.seq_send(lport, seq, fp); if (error) { /* XXX For now, initiator will retry */ pr_err_ratelimited("%s: Failed to send frame %p, " diff --git a/drivers/target/tcm_fc/tfc_sess.c b/drivers/target/tcm_fc/tfc_sess.c index 113f33598b9..4859505ae2e 100644 --- a/drivers/target/tcm_fc/tfc_sess.c +++ b/drivers/target/tcm_fc/tfc_sess.c @@ -428,19 +428,12 @@ static int ft_prli(struct fc_rport_priv *rdata, u32 spp_len, return ret; } -static void ft_sess_rcu_free(struct rcu_head *rcu) -{ - struct ft_sess *sess = container_of(rcu, struct ft_sess, rcu); - - kfree(sess); -} - static void ft_sess_free(struct kref *kref) { struct ft_sess *sess = container_of(kref, struct ft_sess, kref); transport_deregister_session(sess->se_sess); - call_rcu(&sess->rcu, ft_sess_rcu_free); + kfree_rcu(sess, rcu); } void ft_sess_put(struct ft_sess *sess) diff --git a/drivers/vhost/tcm_vhost.c b/drivers/vhost/tcm_vhost.c index 957a0b98a5d..1677238d281 100644 --- a/drivers/vhost/tcm_vhost.c +++ b/drivers/vhost/tcm_vhost.c @@ -66,11 +66,13 @@ enum { * TODO: debug and remove the workaround. */ enum { - VHOST_SCSI_FEATURES = VHOST_FEATURES & (~VIRTIO_RING_F_EVENT_IDX) + VHOST_SCSI_FEATURES = (VHOST_FEATURES & (~VIRTIO_RING_F_EVENT_IDX)) | + (1ULL << VIRTIO_SCSI_F_HOTPLUG) }; #define VHOST_SCSI_MAX_TARGET 256 #define VHOST_SCSI_MAX_VQ 128 +#define VHOST_SCSI_MAX_EVENT 128 struct vhost_scsi { /* Protected by vhost_scsi->dev.mutex */ @@ -82,6 +84,12 @@ struct vhost_scsi { struct vhost_work vs_completion_work; /* cmd completion work item */ struct llist_head vs_completion_list; /* cmd completion queue */ + + struct vhost_work vs_event_work; /* evt injection work item */ + struct llist_head vs_event_list; /* evt injection queue */ + + bool vs_events_missed; /* any missed events, protected by vq->mutex */ + int vs_events_nr; /* num of pending events, protected by vq->mutex */ }; /* Local pointer to allocated TCM configfs fabric module */ @@ -349,6 +357,37 @@ static int tcm_vhost_queue_tm_rsp(struct se_cmd *se_cmd) return 0; } +static void tcm_vhost_free_evt(struct vhost_scsi *vs, struct tcm_vhost_evt *evt) +{ + vs->vs_events_nr--; + kfree(evt); +} + +static struct tcm_vhost_evt *tcm_vhost_allocate_evt(struct vhost_scsi *vs, + u32 event, u32 reason) +{ + struct vhost_virtqueue *vq = &vs->vqs[VHOST_SCSI_VQ_EVT]; + struct tcm_vhost_evt *evt; + + if (vs->vs_events_nr > VHOST_SCSI_MAX_EVENT) { + vs->vs_events_missed = true; + return NULL; + } + + evt = kzalloc(sizeof(*evt), GFP_KERNEL); + if (!evt) { + vq_err(vq, "Failed to allocate tcm_vhost_evt\n"); + vs->vs_events_missed = true; + return NULL; + } + + evt->event.event = event; + evt->event.reason = reason; + vs->vs_events_nr++; + + return evt; +} + static void vhost_scsi_free_cmd(struct tcm_vhost_cmd *tv_cmd) { struct se_cmd *se_cmd = &tv_cmd->tvc_se_cmd; @@ -367,6 +406,75 @@ static void vhost_scsi_free_cmd(struct tcm_vhost_cmd *tv_cmd) kfree(tv_cmd); } +static void tcm_vhost_do_evt_work(struct vhost_scsi *vs, + struct tcm_vhost_evt *evt) +{ + struct vhost_virtqueue *vq = &vs->vqs[VHOST_SCSI_VQ_EVT]; + struct virtio_scsi_event *event = &evt->event; + struct virtio_scsi_event __user *eventp; + unsigned out, in; + int head, ret; + + if (!vq->private_data) { + vs->vs_events_missed = true; + return; + } + +again: + vhost_disable_notify(&vs->dev, vq); + head = vhost_get_vq_desc(&vs->dev, vq, vq->iov, + ARRAY_SIZE(vq->iov), &out, &in, + NULL, NULL); + if (head < 0) { + vs->vs_events_missed = true; + return; + } + if (head == vq->num) { + if (vhost_enable_notify(&vs->dev, vq)) + goto again; + vs->vs_events_missed = true; + return; + } + + if ((vq->iov[out].iov_len != sizeof(struct virtio_scsi_event))) { + vq_err(vq, "Expecting virtio_scsi_event, got %zu bytes\n", + vq->iov[out].iov_len); + vs->vs_events_missed = true; + return; + } + + if (vs->vs_events_missed) { + event->event |= VIRTIO_SCSI_T_EVENTS_MISSED; + vs->vs_events_missed = false; + } + + eventp = vq->iov[out].iov_base; + ret = __copy_to_user(eventp, event, sizeof(*event)); + if (!ret) + vhost_add_used_and_signal(&vs->dev, vq, head, 0); + else + vq_err(vq, "Faulted on tcm_vhost_send_event\n"); +} + +static void tcm_vhost_evt_work(struct vhost_work *work) +{ + struct vhost_scsi *vs = container_of(work, struct vhost_scsi, + vs_event_work); + struct vhost_virtqueue *vq = &vs->vqs[VHOST_SCSI_VQ_EVT]; + struct tcm_vhost_evt *evt; + struct llist_node *llnode; + + mutex_lock(&vq->mutex); + llnode = llist_del_all(&vs->vs_event_list); + while (llnode) { + evt = llist_entry(llnode, struct tcm_vhost_evt, list); + llnode = llist_next(llnode); + tcm_vhost_do_evt_work(vs, evt); + tcm_vhost_free_evt(vs, evt); + } + mutex_unlock(&vq->mutex); +} + /* Fill in status and signal that we are done processing this command * * This is scheduled in the vhost work queue so we are called with the owner @@ -777,9 +885,46 @@ static void vhost_scsi_ctl_handle_kick(struct vhost_work *work) pr_debug("%s: The handling func for control queue.\n", __func__); } +static void tcm_vhost_send_evt(struct vhost_scsi *vs, struct tcm_vhost_tpg *tpg, + struct se_lun *lun, u32 event, u32 reason) +{ + struct tcm_vhost_evt *evt; + + evt = tcm_vhost_allocate_evt(vs, event, reason); + if (!evt) + return; + + if (tpg && lun) { + /* TODO: share lun setup code with virtio-scsi.ko */ + /* + * Note: evt->event is zeroed when we allocate it and + * lun[4-7] need to be zero according to virtio-scsi spec. + */ + evt->event.lun[0] = 0x01; + evt->event.lun[1] = tpg->tport_tpgt & 0xFF; + if (lun->unpacked_lun >= 256) + evt->event.lun[2] = lun->unpacked_lun >> 8 | 0x40 ; + evt->event.lun[3] = lun->unpacked_lun & 0xFF; + } + + llist_add(&evt->list, &vs->vs_event_list); + vhost_work_queue(&vs->dev, &vs->vs_event_work); +} + static void vhost_scsi_evt_handle_kick(struct vhost_work *work) { - pr_debug("%s: The handling func for event queue.\n", __func__); + struct vhost_virtqueue *vq = container_of(work, struct vhost_virtqueue, + poll.work); + struct vhost_scsi *vs = container_of(vq->dev, struct vhost_scsi, dev); + + mutex_lock(&vq->mutex); + if (!vq->private_data) + goto out; + + if (vs->vs_events_missed) + tcm_vhost_send_evt(vs, NULL, NULL, VIRTIO_SCSI_T_NO_EVENT, 0); +out: + mutex_unlock(&vq->mutex); } static void vhost_scsi_handle_kick(struct vhost_work *work) @@ -803,11 +948,15 @@ static void vhost_scsi_flush(struct vhost_scsi *vs) for (i = 0; i < VHOST_SCSI_MAX_VQ; i++) vhost_scsi_flush_vq(vs, i); vhost_work_flush(&vs->dev, &vs->vs_completion_work); + vhost_work_flush(&vs->dev, &vs->vs_event_work); } /* * Called from vhost_scsi_ioctl() context to walk the list of available * tcm_vhost_tpg with an active struct tcm_vhost_nexus + * + * The lock nesting rule is: + * tcm_vhost_mutex -> vs->dev.mutex -> tpg->tv_tpg_mutex -> vq->mutex */ static int vhost_scsi_set_endpoint( struct vhost_scsi *vs, @@ -820,26 +969,27 @@ static int vhost_scsi_set_endpoint( int index, ret, i, len; bool match = false; + mutex_lock(&tcm_vhost_mutex); mutex_lock(&vs->dev.mutex); + /* Verify that ring has been setup correctly. */ for (index = 0; index < vs->dev.nvqs; ++index) { /* Verify that ring has been setup correctly. */ if (!vhost_vq_access_ok(&vs->vqs[index])) { - mutex_unlock(&vs->dev.mutex); - return -EFAULT; + ret = -EFAULT; + goto out; } } len = sizeof(vs_tpg[0]) * VHOST_SCSI_MAX_TARGET; vs_tpg = kzalloc(len, GFP_KERNEL); if (!vs_tpg) { - mutex_unlock(&vs->dev.mutex); - return -ENOMEM; + ret = -ENOMEM; + goto out; } if (vs->vs_tpg) memcpy(vs_tpg, vs->vs_tpg, len); - mutex_lock(&tcm_vhost_mutex); list_for_each_entry(tv_tpg, &tcm_vhost_list, tv_tpg_list) { mutex_lock(&tv_tpg->tv_tpg_mutex); if (!tv_tpg->tpg_nexus) { @@ -854,20 +1004,19 @@ static int vhost_scsi_set_endpoint( if (!strcmp(tv_tport->tport_name, t->vhost_wwpn)) { if (vs->vs_tpg && vs->vs_tpg[tv_tpg->tport_tpgt]) { - mutex_unlock(&tv_tpg->tv_tpg_mutex); - mutex_unlock(&tcm_vhost_mutex); - mutex_unlock(&vs->dev.mutex); kfree(vs_tpg); - return -EEXIST; + mutex_unlock(&tv_tpg->tv_tpg_mutex); + ret = -EEXIST; + goto out; } tv_tpg->tv_tpg_vhost_count++; + tv_tpg->vhost_scsi = vs; vs_tpg[tv_tpg->tport_tpgt] = tv_tpg; smp_mb__after_atomic_inc(); match = true; } mutex_unlock(&tv_tpg->tv_tpg_mutex); } - mutex_unlock(&tcm_vhost_mutex); if (match) { memcpy(vs->vs_vhost_wwpn, t->vhost_wwpn, @@ -893,7 +1042,9 @@ static int vhost_scsi_set_endpoint( kfree(vs->vs_tpg); vs->vs_tpg = vs_tpg; +out: mutex_unlock(&vs->dev.mutex); + mutex_unlock(&tcm_vhost_mutex); return ret; } @@ -908,6 +1059,7 @@ static int vhost_scsi_clear_endpoint( int index, ret, i; u8 target; + mutex_lock(&tcm_vhost_mutex); mutex_lock(&vs->dev.mutex); /* Verify that ring has been setup correctly. */ for (index = 0; index < vs->dev.nvqs; ++index) { @@ -918,8 +1070,8 @@ static int vhost_scsi_clear_endpoint( } if (!vs->vs_tpg) { - mutex_unlock(&vs->dev.mutex); - return 0; + ret = 0; + goto err_dev; } for (i = 0; i < VHOST_SCSI_MAX_TARGET; i++) { @@ -944,6 +1096,7 @@ static int vhost_scsi_clear_endpoint( goto err_tpg; } tv_tpg->tv_tpg_vhost_count--; + tv_tpg->vhost_scsi = NULL; vs->vs_tpg[target] = NULL; match = true; mutex_unlock(&tv_tpg->tv_tpg_mutex); @@ -964,14 +1117,16 @@ static int vhost_scsi_clear_endpoint( vhost_scsi_flush(vs); kfree(vs->vs_tpg); vs->vs_tpg = NULL; + WARN_ON(vs->vs_events_nr); mutex_unlock(&vs->dev.mutex); - + mutex_unlock(&tcm_vhost_mutex); return 0; err_tpg: mutex_unlock(&tv_tpg->tv_tpg_mutex); err_dev: mutex_unlock(&vs->dev.mutex); + mutex_unlock(&tcm_vhost_mutex); return ret; } @@ -1003,6 +1158,10 @@ static int vhost_scsi_open(struct inode *inode, struct file *f) return -ENOMEM; vhost_work_init(&s->vs_completion_work, vhost_scsi_complete_cmd_work); + vhost_work_init(&s->vs_event_work, tcm_vhost_evt_work); + + s->vs_events_nr = 0; + s->vs_events_missed = false; s->vqs[VHOST_SCSI_VQ_CTL].handle_kick = vhost_scsi_ctl_handle_kick; s->vqs[VHOST_SCSI_VQ_EVT].handle_kick = vhost_scsi_evt_handle_kick; @@ -1029,6 +1188,8 @@ static int vhost_scsi_release(struct inode *inode, struct file *f) vhost_scsi_clear_endpoint(s, &t); vhost_dev_stop(&s->dev); vhost_dev_cleanup(&s->dev, false); + /* Jobs can re-queue themselves in evt kick handler. Do extra flush. */ + vhost_scsi_flush(s); kfree(s); return 0; } @@ -1040,8 +1201,11 @@ static long vhost_scsi_ioctl(struct file *f, unsigned int ioctl, struct vhost_scsi_target backend; void __user *argp = (void __user *)arg; u64 __user *featurep = argp; + u32 __user *eventsp = argp; + u32 events_missed; u64 features; int r, abi_version = VHOST_SCSI_ABI_VERSION; + struct vhost_virtqueue *vq = &vs->vqs[VHOST_SCSI_VQ_EVT]; switch (ioctl) { case VHOST_SCSI_SET_ENDPOINT: @@ -1062,6 +1226,20 @@ static long vhost_scsi_ioctl(struct file *f, unsigned int ioctl, if (copy_to_user(argp, &abi_version, sizeof abi_version)) return -EFAULT; return 0; + case VHOST_SCSI_SET_EVENTS_MISSED: + if (get_user(events_missed, eventsp)) + return -EFAULT; + mutex_lock(&vq->mutex); + vs->vs_events_missed = events_missed; + mutex_unlock(&vq->mutex); + return 0; + case VHOST_SCSI_GET_EVENTS_MISSED: + mutex_lock(&vq->mutex); + events_missed = vs->vs_events_missed; + mutex_unlock(&vq->mutex); + if (put_user(events_missed, eventsp)) + return -EFAULT; + return 0; case VHOST_GET_FEATURES: features = VHOST_SCSI_FEATURES; if (copy_to_user(featurep, &features, sizeof features)) @@ -1133,28 +1311,80 @@ static char *tcm_vhost_dump_proto_id(struct tcm_vhost_tport *tport) return "Unknown"; } +static void tcm_vhost_do_plug(struct tcm_vhost_tpg *tpg, + struct se_lun *lun, bool plug) +{ + + struct vhost_scsi *vs = tpg->vhost_scsi; + struct vhost_virtqueue *vq; + u32 reason; + + if (!vs) + return; + + mutex_lock(&vs->dev.mutex); + if (!vhost_has_feature(&vs->dev, VIRTIO_SCSI_F_HOTPLUG)) { + mutex_unlock(&vs->dev.mutex); + return; + } + + if (plug) + reason = VIRTIO_SCSI_EVT_RESET_RESCAN; + else + reason = VIRTIO_SCSI_EVT_RESET_REMOVED; + + vq = &vs->vqs[VHOST_SCSI_VQ_EVT]; + mutex_lock(&vq->mutex); + tcm_vhost_send_evt(vs, tpg, lun, + VIRTIO_SCSI_T_TRANSPORT_RESET, reason); + mutex_unlock(&vq->mutex); + mutex_unlock(&vs->dev.mutex); +} + +static void tcm_vhost_hotplug(struct tcm_vhost_tpg *tpg, struct se_lun *lun) +{ + tcm_vhost_do_plug(tpg, lun, true); +} + +static void tcm_vhost_hotunplug(struct tcm_vhost_tpg *tpg, struct se_lun *lun) +{ + tcm_vhost_do_plug(tpg, lun, false); +} + static int tcm_vhost_port_link(struct se_portal_group *se_tpg, struct se_lun *lun) { struct tcm_vhost_tpg *tv_tpg = container_of(se_tpg, struct tcm_vhost_tpg, se_tpg); + mutex_lock(&tcm_vhost_mutex); + mutex_lock(&tv_tpg->tv_tpg_mutex); tv_tpg->tv_tpg_port_count++; mutex_unlock(&tv_tpg->tv_tpg_mutex); + tcm_vhost_hotplug(tv_tpg, lun); + + mutex_unlock(&tcm_vhost_mutex); + return 0; } static void tcm_vhost_port_unlink(struct se_portal_group *se_tpg, - struct se_lun *se_lun) + struct se_lun *lun) { struct tcm_vhost_tpg *tv_tpg = container_of(se_tpg, struct tcm_vhost_tpg, se_tpg); + mutex_lock(&tcm_vhost_mutex); + mutex_lock(&tv_tpg->tv_tpg_mutex); tv_tpg->tv_tpg_port_count--; mutex_unlock(&tv_tpg->tv_tpg_mutex); + + tcm_vhost_hotunplug(tv_tpg, lun); + + mutex_unlock(&tcm_vhost_mutex); } static struct se_node_acl *tcm_vhost_make_nodeacl( diff --git a/drivers/vhost/tcm_vhost.h b/drivers/vhost/tcm_vhost.h index 1d2ae7a60e1..514b9fda230 100644 --- a/drivers/vhost/tcm_vhost.h +++ b/drivers/vhost/tcm_vhost.h @@ -53,6 +53,7 @@ struct tcm_vhost_nacl { struct se_node_acl se_node_acl; }; +struct vhost_scsi; struct tcm_vhost_tpg { /* Vhost port target portal group tag for TCM */ u16 tport_tpgt; @@ -70,6 +71,8 @@ struct tcm_vhost_tpg { struct tcm_vhost_tport *tport; /* Returned by tcm_vhost_make_tpg() */ struct se_portal_group se_tpg; + /* Pointer back to vhost_scsi, protected by tv_tpg_mutex */ + struct vhost_scsi *vhost_scsi; }; struct tcm_vhost_tport { @@ -83,6 +86,13 @@ struct tcm_vhost_tport { struct se_wwn tport_wwn; }; +struct tcm_vhost_evt { + /* event to be sent to guest */ + struct virtio_scsi_event event; + /* event list, serviced from vhost worker thread */ + struct llist_node list; +}; + /* * As per request from MST, keep TCM_VHOST related ioctl defines out of * linux/vhost.h (user-space) for now.. @@ -113,3 +123,6 @@ struct vhost_scsi_target { #define VHOST_SCSI_CLEAR_ENDPOINT _IOW(VHOST_VIRTIO, 0x41, struct vhost_scsi_target) /* Changing this breaks userspace. */ #define VHOST_SCSI_GET_ABI_VERSION _IOW(VHOST_VIRTIO, 0x42, int) +/* Set and get the events missed flag */ +#define VHOST_SCSI_SET_EVENTS_MISSED _IOW(VHOST_VIRTIO, 0x43, __u32) +#define VHOST_SCSI_GET_EVENTS_MISSED _IOW(VHOST_VIRTIO, 0x44, __u32) |