summaryrefslogtreecommitdiffstats
path: root/net/iucv/iucv.c
diff options
context:
space:
mode:
Diffstat (limited to 'net/iucv/iucv.c')
-rw-r--r--net/iucv/iucv.c311
1 files changed, 299 insertions, 12 deletions
diff --git a/net/iucv/iucv.c b/net/iucv/iucv.c
index a35240f61ec..c833481d32e 100644
--- a/net/iucv/iucv.c
+++ b/net/iucv/iucv.c
@@ -1,7 +1,8 @@
/*
* IUCV base infrastructure.
*
- * Copyright 2001, 2006 IBM Deutschland Entwicklung GmbH, IBM Corporation
+ * Copyright IBM Corp. 2001, 2009
+ *
* Author(s):
* Original source:
* Alan Altmark (Alan_Altmark@us.ibm.com) Sept. 2000
@@ -10,6 +11,8 @@
* Fritz Elfert (elfert@de.ibm.com, felfert@millenux.com)
* Rewritten for af_iucv:
* Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * PM functions:
+ * Ursula Braun (ursula.braun@de.ibm.com)
*
* Documentation used:
* The original source
@@ -45,6 +48,7 @@
#include <linux/err.h>
#include <linux/device.h>
#include <linux/cpu.h>
+#include <linux/reboot.h>
#include <net/iucv/iucv.h>
#include <asm/atomic.h>
#include <asm/ebcdic.h>
@@ -75,9 +79,24 @@ static int iucv_bus_match(struct device *dev, struct device_driver *drv)
return 0;
}
+static int iucv_pm_prepare(struct device *);
+static void iucv_pm_complete(struct device *);
+static int iucv_pm_freeze(struct device *);
+static int iucv_pm_thaw(struct device *);
+static int iucv_pm_restore(struct device *);
+
+static struct dev_pm_ops iucv_pm_ops = {
+ .prepare = iucv_pm_prepare,
+ .complete = iucv_pm_complete,
+ .freeze = iucv_pm_freeze,
+ .thaw = iucv_pm_thaw,
+ .restore = iucv_pm_restore,
+};
+
struct bus_type iucv_bus = {
.name = "iucv",
.match = iucv_bus_match,
+ .pm = &iucv_pm_ops,
};
EXPORT_SYMBOL(iucv_bus);
@@ -147,6 +166,7 @@ enum iucv_command_codes {
IUCV_RESUME = 14,
IUCV_SEVER = 15,
IUCV_SETMASK = 16,
+ IUCV_SETCONTROLMASK = 17,
};
/*
@@ -280,6 +300,7 @@ union iucv_param {
* Anchor for per-cpu IUCV command parameter block.
*/
static union iucv_param *iucv_param[NR_CPUS];
+static union iucv_param *iucv_param_irq[NR_CPUS];
/**
* iucv_call_b2f0
@@ -358,11 +379,23 @@ static void iucv_allow_cpu(void *data)
* 0x10 - Flag to allow priority message completion interrupts
* 0x08 - Flag to allow IUCV control interrupts
*/
- parm = iucv_param[cpu];
+ parm = iucv_param_irq[cpu];
memset(parm, 0, sizeof(union iucv_param));
parm->set_mask.ipmask = 0xf8;
iucv_call_b2f0(IUCV_SETMASK, parm);
+ /*
+ * Enable all iucv control interrupts.
+ * ipmask contains bits for the different interrupts
+ * 0x80 - Flag to allow pending connections interrupts
+ * 0x40 - Flag to allow connection complete interrupts
+ * 0x20 - Flag to allow connection severed interrupts
+ * 0x10 - Flag to allow connection quiesced interrupts
+ * 0x08 - Flag to allow connection resumed interrupts
+ */
+ memset(parm, 0, sizeof(union iucv_param));
+ parm->set_mask.ipmask = 0xf8;
+ iucv_call_b2f0(IUCV_SETCONTROLMASK, parm);
/* Set indication that iucv interrupts are allowed for this cpu. */
cpu_set(cpu, iucv_irq_cpumask);
}
@@ -379,7 +412,7 @@ static void iucv_block_cpu(void *data)
union iucv_param *parm;
/* Disable all iucv interrupts. */
- parm = iucv_param[cpu];
+ parm = iucv_param_irq[cpu];
memset(parm, 0, sizeof(union iucv_param));
iucv_call_b2f0(IUCV_SETMASK, parm);
@@ -388,6 +421,31 @@ static void iucv_block_cpu(void *data)
}
/**
+ * iucv_block_cpu_almost
+ * @data: unused
+ *
+ * Allow connection-severed interrupts only on this cpu.
+ */
+static void iucv_block_cpu_almost(void *data)
+{
+ int cpu = smp_processor_id();
+ union iucv_param *parm;
+
+ /* Allow iucv control interrupts only */
+ parm = iucv_param_irq[cpu];
+ memset(parm, 0, sizeof(union iucv_param));
+ parm->set_mask.ipmask = 0x08;
+ iucv_call_b2f0(IUCV_SETMASK, parm);
+ /* Allow iucv-severed interrupt only */
+ memset(parm, 0, sizeof(union iucv_param));
+ parm->set_mask.ipmask = 0x20;
+ iucv_call_b2f0(IUCV_SETCONTROLMASK, parm);
+
+ /* Clear indication that iucv interrupts are allowed for this cpu. */
+ cpu_clear(cpu, iucv_irq_cpumask);
+}
+
+/**
* iucv_declare_cpu
* @data: unused
*
@@ -403,7 +461,7 @@ static void iucv_declare_cpu(void *data)
return;
/* Declare interrupt buffer. */
- parm = iucv_param[cpu];
+ parm = iucv_param_irq[cpu];
memset(parm, 0, sizeof(union iucv_param));
parm->db.ipbfadr1 = virt_to_phys(iucv_irq_data[cpu]);
rc = iucv_call_b2f0(IUCV_DECLARE_BUFFER, parm);
@@ -460,7 +518,7 @@ static void iucv_retrieve_cpu(void *data)
iucv_block_cpu(NULL);
/* Retrieve interrupt buffer. */
- parm = iucv_param[cpu];
+ parm = iucv_param_irq[cpu];
iucv_call_b2f0(IUCV_RETRIEVE_BUFFER, parm);
/* Clear indication that an iucv buffer exists for this cpu. */
@@ -574,11 +632,22 @@ static int __cpuinit iucv_cpu_notify(struct notifier_block *self,
iucv_irq_data[cpu] = NULL;
return NOTIFY_BAD;
}
+ iucv_param_irq[cpu] = kmalloc_node(sizeof(union iucv_param),
+ GFP_KERNEL|GFP_DMA, cpu_to_node(cpu));
+ if (!iucv_param_irq[cpu]) {
+ kfree(iucv_param[cpu]);
+ iucv_param[cpu] = NULL;
+ kfree(iucv_irq_data[cpu]);
+ iucv_irq_data[cpu] = NULL;
+ return NOTIFY_BAD;
+ }
break;
case CPU_UP_CANCELED:
case CPU_UP_CANCELED_FROZEN:
case CPU_DEAD:
case CPU_DEAD_FROZEN:
+ kfree(iucv_param_irq[cpu]);
+ iucv_param_irq[cpu] = NULL;
kfree(iucv_param[cpu]);
iucv_param[cpu] = NULL;
kfree(iucv_irq_data[cpu]);
@@ -625,7 +694,7 @@ static int iucv_sever_pathid(u16 pathid, u8 userdata[16])
{
union iucv_param *parm;
- parm = iucv_param[smp_processor_id()];
+ parm = iucv_param_irq[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
if (userdata)
memcpy(parm->ctrl.ipuser, userdata, sizeof(parm->ctrl.ipuser));
@@ -746,6 +815,28 @@ void iucv_unregister(struct iucv_handler *handler, int smp)
}
EXPORT_SYMBOL(iucv_unregister);
+static int iucv_reboot_event(struct notifier_block *this,
+ unsigned long event, void *ptr)
+{
+ int i, rc;
+
+ get_online_cpus();
+ on_each_cpu(iucv_block_cpu, NULL, 1);
+ preempt_disable();
+ for (i = 0; i < iucv_max_pathid; i++) {
+ if (iucv_path_table[i])
+ rc = iucv_sever_pathid(i, NULL);
+ }
+ preempt_enable();
+ put_online_cpus();
+ iucv_disable();
+ return NOTIFY_DONE;
+}
+
+static struct notifier_block iucv_reboot_notifier = {
+ .notifier_call = iucv_reboot_event,
+};
+
/**
* iucv_path_accept
* @path: address of iucv path structure
@@ -765,6 +856,10 @@ int iucv_path_accept(struct iucv_path *path, struct iucv_handler *handler,
int rc;
local_bh_disable();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
/* Prepare parameter block. */
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
@@ -780,6 +875,7 @@ int iucv_path_accept(struct iucv_path *path, struct iucv_handler *handler,
path->msglim = parm->ctrl.ipmsglim;
path->flags = parm->ctrl.ipflags1;
}
+out:
local_bh_enable();
return rc;
}
@@ -809,6 +905,10 @@ int iucv_path_connect(struct iucv_path *path, struct iucv_handler *handler,
spin_lock_bh(&iucv_table_lock);
iucv_cleanup_queue();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
parm->ctrl.ipmsglim = path->msglim;
@@ -843,6 +943,7 @@ int iucv_path_connect(struct iucv_path *path, struct iucv_handler *handler,
rc = -EIO;
}
}
+out:
spin_unlock_bh(&iucv_table_lock);
return rc;
}
@@ -864,12 +965,17 @@ int iucv_path_quiesce(struct iucv_path *path, u8 userdata[16])
int rc;
local_bh_disable();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
if (userdata)
memcpy(parm->ctrl.ipuser, userdata, sizeof(parm->ctrl.ipuser));
parm->ctrl.ippathid = path->pathid;
rc = iucv_call_b2f0(IUCV_QUIESCE, parm);
+out:
local_bh_enable();
return rc;
}
@@ -891,12 +997,17 @@ int iucv_path_resume(struct iucv_path *path, u8 userdata[16])
int rc;
local_bh_disable();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
if (userdata)
memcpy(parm->ctrl.ipuser, userdata, sizeof(parm->ctrl.ipuser));
parm->ctrl.ippathid = path->pathid;
rc = iucv_call_b2f0(IUCV_RESUME, parm);
+out:
local_bh_enable();
return rc;
}
@@ -915,15 +1026,18 @@ int iucv_path_sever(struct iucv_path *path, u8 userdata[16])
int rc;
preempt_disable();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
if (iucv_active_cpu != smp_processor_id())
spin_lock_bh(&iucv_table_lock);
rc = iucv_sever_pathid(path->pathid, userdata);
- if (!rc) {
- iucv_path_table[path->pathid] = NULL;
- list_del_init(&path->list);
- }
+ iucv_path_table[path->pathid] = NULL;
+ list_del_init(&path->list);
if (iucv_active_cpu != smp_processor_id())
spin_unlock_bh(&iucv_table_lock);
+out:
preempt_enable();
return rc;
}
@@ -946,6 +1060,10 @@ int iucv_message_purge(struct iucv_path *path, struct iucv_message *msg,
int rc;
local_bh_disable();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
parm->purge.ippathid = path->pathid;
@@ -957,6 +1075,7 @@ int iucv_message_purge(struct iucv_path *path, struct iucv_message *msg,
msg->audit = (*(u32 *) &parm->purge.ipaudit) >> 8;
msg->tag = parm->purge.ipmsgtag;
}
+out:
local_bh_enable();
return rc;
}
@@ -1033,6 +1152,10 @@ int __iucv_message_receive(struct iucv_path *path, struct iucv_message *msg,
if (msg->flags & IUCV_IPRMDATA)
return iucv_message_receive_iprmdata(path, msg, flags,
buffer, size, residual);
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
parm->db.ipbfadr1 = (u32)(addr_t) buffer;
@@ -1048,6 +1171,7 @@ int __iucv_message_receive(struct iucv_path *path, struct iucv_message *msg,
if (residual)
*residual = parm->db.ipbfln1f;
}
+out:
return rc;
}
EXPORT_SYMBOL(__iucv_message_receive);
@@ -1101,6 +1225,10 @@ int iucv_message_reject(struct iucv_path *path, struct iucv_message *msg)
int rc;
local_bh_disable();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
parm->db.ippathid = path->pathid;
@@ -1108,6 +1236,7 @@ int iucv_message_reject(struct iucv_path *path, struct iucv_message *msg)
parm->db.iptrgcls = msg->class;
parm->db.ipflags1 = (IUCV_IPTRGCLS | IUCV_IPFGMID | IUCV_IPFGPID);
rc = iucv_call_b2f0(IUCV_REJECT, parm);
+out:
local_bh_enable();
return rc;
}
@@ -1135,6 +1264,10 @@ int iucv_message_reply(struct iucv_path *path, struct iucv_message *msg,
int rc;
local_bh_disable();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
if (flags & IUCV_IPRMDATA) {
@@ -1152,6 +1285,7 @@ int iucv_message_reply(struct iucv_path *path, struct iucv_message *msg,
parm->db.iptrgcls = msg->class;
}
rc = iucv_call_b2f0(IUCV_REPLY, parm);
+out:
local_bh_enable();
return rc;
}
@@ -1180,6 +1314,10 @@ int __iucv_message_send(struct iucv_path *path, struct iucv_message *msg,
union iucv_param *parm;
int rc;
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
if (flags & IUCV_IPRMDATA) {
@@ -1202,6 +1340,7 @@ int __iucv_message_send(struct iucv_path *path, struct iucv_message *msg,
rc = iucv_call_b2f0(IUCV_SEND, parm);
if (!rc)
msg->id = parm->db.ipmsgid;
+out:
return rc;
}
EXPORT_SYMBOL(__iucv_message_send);
@@ -1262,6 +1401,10 @@ int iucv_message_send2way(struct iucv_path *path, struct iucv_message *msg,
int rc;
local_bh_disable();
+ if (!cpu_isset(smp_processor_id(), iucv_buffer_cpumask)) {
+ rc = -EIO;
+ goto out;
+ }
parm = iucv_param[smp_processor_id()];
memset(parm, 0, sizeof(union iucv_param));
if (flags & IUCV_IPRMDATA) {
@@ -1287,6 +1430,7 @@ int iucv_message_send2way(struct iucv_path *path, struct iucv_message *msg,
rc = iucv_call_b2f0(IUCV_SEND, parm);
if (!rc)
msg->id = parm->db.ipmsgid;
+out:
local_bh_enable();
return rc;
}
@@ -1378,6 +1522,8 @@ static void iucv_path_complete(struct iucv_irq_data *data)
struct iucv_path_complete *ipc = (void *) data;
struct iucv_path *path = iucv_path_table[ipc->ippathid];
+ if (path)
+ path->flags = ipc->ipflags1;
if (path && path->handler && path->handler->path_complete)
path->handler->path_complete(path, ipc->ipuser);
}
@@ -1413,7 +1559,7 @@ static void iucv_path_severed(struct iucv_irq_data *data)
else {
iucv_sever_pathid(path->pathid, NULL);
iucv_path_table[path->pathid] = NULL;
- list_del_init(&path->list);
+ list_del(&path->list);
iucv_path_free(path);
}
}
@@ -1675,6 +1821,130 @@ static void iucv_external_interrupt(u16 code)
spin_unlock(&iucv_queue_lock);
}
+static int iucv_pm_prepare(struct device *dev)
+{
+ int rc = 0;
+
+#ifdef CONFIG_PM_DEBUG
+ printk(KERN_INFO "iucv_pm_prepare\n");
+#endif
+ if (dev->driver && dev->driver->pm && dev->driver->pm->prepare)
+ rc = dev->driver->pm->prepare(dev);
+ return rc;
+}
+
+static void iucv_pm_complete(struct device *dev)
+{
+#ifdef CONFIG_PM_DEBUG
+ printk(KERN_INFO "iucv_pm_complete\n");
+#endif
+ if (dev->driver && dev->driver->pm && dev->driver->pm->complete)
+ dev->driver->pm->complete(dev);
+}
+
+/**
+ * iucv_path_table_empty() - determine if iucv path table is empty
+ *
+ * Returns 0 if there are still iucv pathes defined
+ * 1 if there are no iucv pathes defined
+ */
+int iucv_path_table_empty(void)
+{
+ int i;
+
+ for (i = 0; i < iucv_max_pathid; i++) {
+ if (iucv_path_table[i])
+ return 0;
+ }
+ return 1;
+}
+
+/**
+ * iucv_pm_freeze() - Freeze PM callback
+ * @dev: iucv-based device
+ *
+ * disable iucv interrupts
+ * invoke callback function of the iucv-based driver
+ * shut down iucv, if no iucv-pathes are established anymore
+ */
+static int iucv_pm_freeze(struct device *dev)
+{
+ int cpu;
+ int rc = 0;
+
+#ifdef CONFIG_PM_DEBUG
+ printk(KERN_WARNING "iucv_pm_freeze\n");
+#endif
+ for_each_cpu_mask_nr(cpu, iucv_irq_cpumask)
+ smp_call_function_single(cpu, iucv_block_cpu_almost, NULL, 1);
+ if (dev->driver && dev->driver->pm && dev->driver->pm->freeze)
+ rc = dev->driver->pm->freeze(dev);
+ if (iucv_path_table_empty())
+ iucv_disable();
+ return rc;
+}
+
+/**
+ * iucv_pm_thaw() - Thaw PM callback
+ * @dev: iucv-based device
+ *
+ * make iucv ready for use again: allocate path table, declare interrupt buffers
+ * and enable iucv interrupts
+ * invoke callback function of the iucv-based driver
+ */
+static int iucv_pm_thaw(struct device *dev)
+{
+ int rc = 0;
+
+#ifdef CONFIG_PM_DEBUG
+ printk(KERN_WARNING "iucv_pm_thaw\n");
+#endif
+ if (!iucv_path_table) {
+ rc = iucv_enable();
+ if (rc)
+ goto out;
+ }
+ if (cpus_empty(iucv_irq_cpumask)) {
+ if (iucv_nonsmp_handler)
+ /* enable interrupts on one cpu */
+ iucv_allow_cpu(NULL);
+ else
+ /* enable interrupts on all cpus */
+ iucv_setmask_mp();
+ }
+ if (dev->driver && dev->driver->pm && dev->driver->pm->thaw)
+ rc = dev->driver->pm->thaw(dev);
+out:
+ return rc;
+}
+
+/**
+ * iucv_pm_restore() - Restore PM callback
+ * @dev: iucv-based device
+ *
+ * make iucv ready for use again: allocate path table, declare interrupt buffers
+ * and enable iucv interrupts
+ * invoke callback function of the iucv-based driver
+ */
+static int iucv_pm_restore(struct device *dev)
+{
+ int rc = 0;
+
+#ifdef CONFIG_PM_DEBUG
+ printk(KERN_WARNING "iucv_pm_restore %p\n", iucv_path_table);
+#endif
+ if (cpus_empty(iucv_irq_cpumask)) {
+ rc = iucv_query_maxconn();
+ rc = iucv_enable();
+ if (rc)
+ goto out;
+ }
+ if (dev->driver && dev->driver->pm && dev->driver->pm->restore)
+ rc = dev->driver->pm->restore(dev);
+out:
+ return rc;
+}
+
/**
* iucv_init
*
@@ -1717,23 +1987,37 @@ static int __init iucv_init(void)
rc = -ENOMEM;
goto out_free;
}
+ iucv_param_irq[cpu] = kmalloc_node(sizeof(union iucv_param),
+ GFP_KERNEL|GFP_DMA, cpu_to_node(cpu));
+ if (!iucv_param_irq[cpu]) {
+ rc = -ENOMEM;
+ goto out_free;
+ }
+
}
rc = register_hotcpu_notifier(&iucv_cpu_notifier);
if (rc)
goto out_free;
+ rc = register_reboot_notifier(&iucv_reboot_notifier);
+ if (rc)
+ goto out_cpu;
ASCEBC(iucv_error_no_listener, 16);
ASCEBC(iucv_error_no_memory, 16);
ASCEBC(iucv_error_pathid, 16);
iucv_available = 1;
rc = bus_register(&iucv_bus);
if (rc)
- goto out_cpu;
+ goto out_reboot;
return 0;
+out_reboot:
+ unregister_reboot_notifier(&iucv_reboot_notifier);
out_cpu:
unregister_hotcpu_notifier(&iucv_cpu_notifier);
out_free:
for_each_possible_cpu(cpu) {
+ kfree(iucv_param_irq[cpu]);
+ iucv_param_irq[cpu] = NULL;
kfree(iucv_param[cpu]);
iucv_param[cpu] = NULL;
kfree(iucv_irq_data[cpu]);
@@ -1762,8 +2046,11 @@ static void __exit iucv_exit(void)
list_for_each_entry_safe(p, n, &iucv_work_queue, list)
kfree(p);
spin_unlock_irq(&iucv_queue_lock);
+ unregister_reboot_notifier(&iucv_reboot_notifier);
unregister_hotcpu_notifier(&iucv_cpu_notifier);
for_each_possible_cpu(cpu) {
+ kfree(iucv_param_irq[cpu]);
+ iucv_param_irq[cpu] = NULL;
kfree(iucv_param[cpu]);
iucv_param[cpu] = NULL;
kfree(iucv_irq_data[cpu]);