| /* | |
| * IUCV base infrastructure. | |
| * | |
| * Copyright IBM Corp. 2001, 2009 | |
| * | |
| * Author(s): | |
| * Original source: | |
| * Alan Altmark (Alan_Altmark@us.ibm.com) Sept. 2000 | |
| * Xenia Tkatschow (xenia@us.ibm.com) | |
| * 2Gb awareness and general cleanup: | |
| * 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 | |
| * CP Programming Service, IBM document # SC24-5760 | |
| * | |
| * 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. | |
| */ | |
| #define KMSG_COMPONENT "iucv" | |
| #define pr_fmt(fmt) KMSG_COMPONENT ": " fmt | |
| #include <linux/kernel_stat.h> | |
| #include <linux/module.h> | |
| #include <linux/moduleparam.h> | |
| #include <linux/spinlock.h> | |
| #include <linux/kernel.h> | |
| #include <linux/slab.h> | |
| #include <linux/init.h> | |
| #include <linux/interrupt.h> | |
| #include <linux/list.h> | |
| #include <linux/errno.h> | |
| #include <linux/err.h> | |
| #include <linux/device.h> | |
| #include <linux/cpu.h> | |
| #include <linux/reboot.h> | |
| #include <net/iucv/iucv.h> | |
| #include <linux/atomic.h> | |
| #include <asm/ebcdic.h> | |
| #include <asm/io.h> | |
| #include <asm/irq.h> | |
| #include <asm/smp.h> | |
| /* | |
| * FLAGS: | |
| * All flags are defined in the field IPFLAGS1 of each function | |
| * and can be found in CP Programming Services. | |
| * IPSRCCLS - Indicates you have specified a source class. | |
| * IPTRGCLS - Indicates you have specified a target class. | |
| * IPFGPID - Indicates you have specified a pathid. | |
| * IPFGMID - Indicates you have specified a message ID. | |
| * IPNORPY - Indicates a one-way message. No reply expected. | |
| * IPALL - Indicates that all paths are affected. | |
| */ | |
| #define IUCV_IPSRCCLS 0x01 | |
| #define IUCV_IPTRGCLS 0x01 | |
| #define IUCV_IPFGPID 0x02 | |
| #define IUCV_IPFGMID 0x04 | |
| #define IUCV_IPNORPY 0x10 | |
| #define IUCV_IPALL 0x80 | |
| static int iucv_bus_match(struct device *dev, struct device_driver *drv) | |
| { | |
| return 0; | |
| } | |
| enum iucv_pm_states { | |
| IUCV_PM_INITIAL = 0, | |
| IUCV_PM_FREEZING = 1, | |
| IUCV_PM_THAWING = 2, | |
| IUCV_PM_RESTORING = 3, | |
| }; | |
| static enum iucv_pm_states iucv_pm_state; | |
| 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 const 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); | |
| struct device *iucv_root; | |
| EXPORT_SYMBOL(iucv_root); | |
| static int iucv_available; | |
| /* General IUCV interrupt structure */ | |
| struct iucv_irq_data { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iptype; | |
| u32 res2[8]; | |
| }; | |
| struct iucv_irq_list { | |
| struct list_head list; | |
| struct iucv_irq_data data; | |
| }; | |
| static struct iucv_irq_data *iucv_irq_data[NR_CPUS]; | |
| static cpumask_t iucv_buffer_cpumask = { CPU_BITS_NONE }; | |
| static cpumask_t iucv_irq_cpumask = { CPU_BITS_NONE }; | |
| /* | |
| * Queue of interrupt buffers lock for delivery via the tasklet | |
| * (fast but can't call smp_call_function). | |
| */ | |
| static LIST_HEAD(iucv_task_queue); | |
| /* | |
| * The tasklet for fast delivery of iucv interrupts. | |
| */ | |
| static void iucv_tasklet_fn(unsigned long); | |
| static DECLARE_TASKLET(iucv_tasklet, iucv_tasklet_fn,0); | |
| /* | |
| * Queue of interrupt buffers for delivery via a work queue | |
| * (slower but can call smp_call_function). | |
| */ | |
| static LIST_HEAD(iucv_work_queue); | |
| /* | |
| * The work element to deliver path pending interrupts. | |
| */ | |
| static void iucv_work_fn(struct work_struct *work); | |
| static DECLARE_WORK(iucv_work, iucv_work_fn); | |
| /* | |
| * Spinlock protecting task and work queue. | |
| */ | |
| static DEFINE_SPINLOCK(iucv_queue_lock); | |
| enum iucv_command_codes { | |
| IUCV_QUERY = 0, | |
| IUCV_RETRIEVE_BUFFER = 2, | |
| IUCV_SEND = 4, | |
| IUCV_RECEIVE = 5, | |
| IUCV_REPLY = 6, | |
| IUCV_REJECT = 8, | |
| IUCV_PURGE = 9, | |
| IUCV_ACCEPT = 10, | |
| IUCV_CONNECT = 11, | |
| IUCV_DECLARE_BUFFER = 12, | |
| IUCV_QUIESCE = 13, | |
| IUCV_RESUME = 14, | |
| IUCV_SEVER = 15, | |
| IUCV_SETMASK = 16, | |
| IUCV_SETCONTROLMASK = 17, | |
| }; | |
| /* | |
| * Error messages that are used with the iucv_sever function. They get | |
| * converted to EBCDIC. | |
| */ | |
| static char iucv_error_no_listener[16] = "NO LISTENER"; | |
| static char iucv_error_no_memory[16] = "NO MEMORY"; | |
| static char iucv_error_pathid[16] = "INVALID PATHID"; | |
| /* | |
| * iucv_handler_list: List of registered handlers. | |
| */ | |
| static LIST_HEAD(iucv_handler_list); | |
| /* | |
| * iucv_path_table: an array of iucv_path structures. | |
| */ | |
| static struct iucv_path **iucv_path_table; | |
| static unsigned long iucv_max_pathid; | |
| /* | |
| * iucv_lock: spinlock protecting iucv_handler_list and iucv_pathid_table | |
| */ | |
| static DEFINE_SPINLOCK(iucv_table_lock); | |
| /* | |
| * iucv_active_cpu: contains the number of the cpu executing the tasklet | |
| * or the work handler. Needed for iucv_path_sever called from tasklet. | |
| */ | |
| static int iucv_active_cpu = -1; | |
| /* | |
| * Mutex and wait queue for iucv_register/iucv_unregister. | |
| */ | |
| static DEFINE_MUTEX(iucv_register_mutex); | |
| /* | |
| * Counter for number of non-smp capable handlers. | |
| */ | |
| static int iucv_nonsmp_handler; | |
| /* | |
| * IUCV control data structure. Used by iucv_path_accept, iucv_path_connect, | |
| * iucv_path_quiesce and iucv_path_sever. | |
| */ | |
| struct iucv_cmd_control { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iprcode; | |
| u16 ipmsglim; | |
| u16 res1; | |
| u8 ipvmid[8]; | |
| u8 ipuser[16]; | |
| u8 iptarget[8]; | |
| } __attribute__ ((packed,aligned(8))); | |
| /* | |
| * Data in parameter list iucv structure. Used by iucv_message_send, | |
| * iucv_message_send2way and iucv_message_reply. | |
| */ | |
| struct iucv_cmd_dpl { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iprcode; | |
| u32 ipmsgid; | |
| u32 iptrgcls; | |
| u8 iprmmsg[8]; | |
| u32 ipsrccls; | |
| u32 ipmsgtag; | |
| u32 ipbfadr2; | |
| u32 ipbfln2f; | |
| u32 res; | |
| } __attribute__ ((packed,aligned(8))); | |
| /* | |
| * Data in buffer iucv structure. Used by iucv_message_receive, | |
| * iucv_message_reject, iucv_message_send, iucv_message_send2way | |
| * and iucv_declare_cpu. | |
| */ | |
| struct iucv_cmd_db { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iprcode; | |
| u32 ipmsgid; | |
| u32 iptrgcls; | |
| u32 ipbfadr1; | |
| u32 ipbfln1f; | |
| u32 ipsrccls; | |
| u32 ipmsgtag; | |
| u32 ipbfadr2; | |
| u32 ipbfln2f; | |
| u32 res; | |
| } __attribute__ ((packed,aligned(8))); | |
| /* | |
| * Purge message iucv structure. Used by iucv_message_purge. | |
| */ | |
| struct iucv_cmd_purge { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iprcode; | |
| u32 ipmsgid; | |
| u8 ipaudit[3]; | |
| u8 res1[5]; | |
| u32 res2; | |
| u32 ipsrccls; | |
| u32 ipmsgtag; | |
| u32 res3[3]; | |
| } __attribute__ ((packed,aligned(8))); | |
| /* | |
| * Set mask iucv structure. Used by iucv_enable_cpu. | |
| */ | |
| struct iucv_cmd_set_mask { | |
| u8 ipmask; | |
| u8 res1[2]; | |
| u8 iprcode; | |
| u32 res2[9]; | |
| } __attribute__ ((packed,aligned(8))); | |
| union iucv_param { | |
| struct iucv_cmd_control ctrl; | |
| struct iucv_cmd_dpl dpl; | |
| struct iucv_cmd_db db; | |
| struct iucv_cmd_purge purge; | |
| struct iucv_cmd_set_mask set_mask; | |
| }; | |
| /* | |
| * 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 | |
| * @code: identifier of IUCV call to CP. | |
| * @parm: pointer to a struct iucv_parm block | |
| * | |
| * Calls CP to execute IUCV commands. | |
| * | |
| * Returns the result of the CP IUCV call. | |
| */ | |
| static inline int __iucv_call_b2f0(int command, union iucv_param *parm) | |
| { | |
| register unsigned long reg0 asm ("0"); | |
| register unsigned long reg1 asm ("1"); | |
| int ccode; | |
| reg0 = command; | |
| reg1 = (unsigned long)parm; | |
| asm volatile( | |
| " .long 0xb2f01000\n" | |
| " ipm %0\n" | |
| " srl %0,28\n" | |
| : "=d" (ccode), "=m" (*parm), "+d" (reg0), "+a" (reg1) | |
| : "m" (*parm) : "cc"); | |
| return ccode; | |
| } | |
| static inline int iucv_call_b2f0(int command, union iucv_param *parm) | |
| { | |
| int ccode; | |
| ccode = __iucv_call_b2f0(command, parm); | |
| return ccode == 1 ? parm->ctrl.iprcode : ccode; | |
| } | |
| /** | |
| * iucv_query_maxconn | |
| * | |
| * Determines the maximum number of connections that may be established. | |
| * | |
| * Returns the maximum number of connections or -EPERM is IUCV is not | |
| * available. | |
| */ | |
| static int __iucv_query_maxconn(void *param, unsigned long *max_pathid) | |
| { | |
| register unsigned long reg0 asm ("0"); | |
| register unsigned long reg1 asm ("1"); | |
| int ccode; | |
| reg0 = IUCV_QUERY; | |
| reg1 = (unsigned long) param; | |
| asm volatile ( | |
| " .long 0xb2f01000\n" | |
| " ipm %0\n" | |
| " srl %0,28\n" | |
| : "=d" (ccode), "+d" (reg0), "+d" (reg1) : : "cc"); | |
| *max_pathid = reg1; | |
| return ccode; | |
| } | |
| static int iucv_query_maxconn(void) | |
| { | |
| unsigned long max_pathid; | |
| void *param; | |
| int ccode; | |
| param = kzalloc(sizeof(union iucv_param), GFP_KERNEL | GFP_DMA); | |
| if (!param) | |
| return -ENOMEM; | |
| ccode = __iucv_query_maxconn(param, &max_pathid); | |
| if (ccode == 0) | |
| iucv_max_pathid = max_pathid; | |
| kfree(param); | |
| return ccode ? -EPERM : 0; | |
| } | |
| /** | |
| * iucv_allow_cpu | |
| * @data: unused | |
| * | |
| * Allow iucv interrupts on this cpu. | |
| */ | |
| static void iucv_allow_cpu(void *data) | |
| { | |
| int cpu = smp_processor_id(); | |
| union iucv_param *parm; | |
| /* | |
| * Enable all iucv interrupts. | |
| * ipmask contains bits for the different interrupts | |
| * 0x80 - Flag to allow nonpriority message pending interrupts | |
| * 0x40 - Flag to allow priority message pending interrupts | |
| * 0x20 - Flag to allow nonpriority message completion interrupts | |
| * 0x10 - Flag to allow priority message completion interrupts | |
| * 0x08 - Flag to allow IUCV control interrupts | |
| */ | |
| 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. */ | |
| cpumask_set_cpu(cpu, &iucv_irq_cpumask); | |
| } | |
| /** | |
| * iucv_block_cpu | |
| * @data: unused | |
| * | |
| * Block iucv interrupts on this cpu. | |
| */ | |
| static void iucv_block_cpu(void *data) | |
| { | |
| int cpu = smp_processor_id(); | |
| union iucv_param *parm; | |
| /* Disable all iucv interrupts. */ | |
| parm = iucv_param_irq[cpu]; | |
| memset(parm, 0, sizeof(union iucv_param)); | |
| iucv_call_b2f0(IUCV_SETMASK, parm); | |
| /* Clear indication that iucv interrupts are allowed for this cpu. */ | |
| cpumask_clear_cpu(cpu, &iucv_irq_cpumask); | |
| } | |
| /** | |
| * 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. */ | |
| cpumask_clear_cpu(cpu, &iucv_irq_cpumask); | |
| } | |
| /** | |
| * iucv_declare_cpu | |
| * @data: unused | |
| * | |
| * Declare a interrupt buffer on this cpu. | |
| */ | |
| static void iucv_declare_cpu(void *data) | |
| { | |
| int cpu = smp_processor_id(); | |
| union iucv_param *parm; | |
| int rc; | |
| if (cpumask_test_cpu(cpu, &iucv_buffer_cpumask)) | |
| return; | |
| /* Declare interrupt buffer. */ | |
| 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); | |
| if (rc) { | |
| char *err = "Unknown"; | |
| switch (rc) { | |
| case 0x03: | |
| err = "Directory error"; | |
| break; | |
| case 0x0a: | |
| err = "Invalid length"; | |
| break; | |
| case 0x13: | |
| err = "Buffer already exists"; | |
| break; | |
| case 0x3e: | |
| err = "Buffer overlap"; | |
| break; | |
| case 0x5c: | |
| err = "Paging or storage error"; | |
| break; | |
| } | |
| pr_warn("Defining an interrupt buffer on CPU %i failed with 0x%02x (%s)\n", | |
| cpu, rc, err); | |
| return; | |
| } | |
| /* Set indication that an iucv buffer exists for this cpu. */ | |
| cpumask_set_cpu(cpu, &iucv_buffer_cpumask); | |
| if (iucv_nonsmp_handler == 0 || cpumask_empty(&iucv_irq_cpumask)) | |
| /* Enable iucv interrupts on this cpu. */ | |
| iucv_allow_cpu(NULL); | |
| else | |
| /* Disable iucv interrupts on this cpu. */ | |
| iucv_block_cpu(NULL); | |
| } | |
| /** | |
| * iucv_retrieve_cpu | |
| * @data: unused | |
| * | |
| * Retrieve interrupt buffer on this cpu. | |
| */ | |
| static void iucv_retrieve_cpu(void *data) | |
| { | |
| int cpu = smp_processor_id(); | |
| union iucv_param *parm; | |
| if (!cpumask_test_cpu(cpu, &iucv_buffer_cpumask)) | |
| return; | |
| /* Block iucv interrupts. */ | |
| iucv_block_cpu(NULL); | |
| /* Retrieve interrupt buffer. */ | |
| parm = iucv_param_irq[cpu]; | |
| iucv_call_b2f0(IUCV_RETRIEVE_BUFFER, parm); | |
| /* Clear indication that an iucv buffer exists for this cpu. */ | |
| cpumask_clear_cpu(cpu, &iucv_buffer_cpumask); | |
| } | |
| /** | |
| * iucv_setmask_smp | |
| * | |
| * Allow iucv interrupts on all cpus. | |
| */ | |
| static void iucv_setmask_mp(void) | |
| { | |
| int cpu; | |
| get_online_cpus(); | |
| for_each_online_cpu(cpu) | |
| /* Enable all cpus with a declared buffer. */ | |
| if (cpumask_test_cpu(cpu, &iucv_buffer_cpumask) && | |
| !cpumask_test_cpu(cpu, &iucv_irq_cpumask)) | |
| smp_call_function_single(cpu, iucv_allow_cpu, | |
| NULL, 1); | |
| put_online_cpus(); | |
| } | |
| /** | |
| * iucv_setmask_up | |
| * | |
| * Allow iucv interrupts on a single cpu. | |
| */ | |
| static void iucv_setmask_up(void) | |
| { | |
| cpumask_t cpumask; | |
| int cpu; | |
| /* Disable all cpu but the first in cpu_irq_cpumask. */ | |
| cpumask_copy(&cpumask, &iucv_irq_cpumask); | |
| cpumask_clear_cpu(cpumask_first(&iucv_irq_cpumask), &cpumask); | |
| for_each_cpu(cpu, &cpumask) | |
| smp_call_function_single(cpu, iucv_block_cpu, NULL, 1); | |
| } | |
| /** | |
| * iucv_enable | |
| * | |
| * This function makes iucv ready for use. It allocates the pathid | |
| * table, declares an iucv interrupt buffer and enables the iucv | |
| * interrupts. Called when the first user has registered an iucv | |
| * handler. | |
| */ | |
| static int iucv_enable(void) | |
| { | |
| size_t alloc_size; | |
| int cpu, rc; | |
| get_online_cpus(); | |
| rc = -ENOMEM; | |
| alloc_size = iucv_max_pathid * sizeof(struct iucv_path); | |
| iucv_path_table = kzalloc(alloc_size, GFP_KERNEL); | |
| if (!iucv_path_table) | |
| goto out; | |
| /* Declare per cpu buffers. */ | |
| rc = -EIO; | |
| for_each_online_cpu(cpu) | |
| smp_call_function_single(cpu, iucv_declare_cpu, NULL, 1); | |
| if (cpumask_empty(&iucv_buffer_cpumask)) | |
| /* No cpu could declare an iucv buffer. */ | |
| goto out; | |
| put_online_cpus(); | |
| return 0; | |
| out: | |
| kfree(iucv_path_table); | |
| iucv_path_table = NULL; | |
| put_online_cpus(); | |
| return rc; | |
| } | |
| /** | |
| * iucv_disable | |
| * | |
| * This function shuts down iucv. It disables iucv interrupts, retrieves | |
| * the iucv interrupt buffer and frees the pathid table. Called after the | |
| * last user unregister its iucv handler. | |
| */ | |
| static void iucv_disable(void) | |
| { | |
| get_online_cpus(); | |
| on_each_cpu(iucv_retrieve_cpu, NULL, 1); | |
| kfree(iucv_path_table); | |
| iucv_path_table = NULL; | |
| put_online_cpus(); | |
| } | |
| static int iucv_cpu_dead(unsigned int cpu) | |
| { | |
| kfree(iucv_param_irq[cpu]); | |
| iucv_param_irq[cpu] = NULL; | |
| kfree(iucv_param[cpu]); | |
| iucv_param[cpu] = NULL; | |
| kfree(iucv_irq_data[cpu]); | |
| iucv_irq_data[cpu] = NULL; | |
| return 0; | |
| } | |
| static int iucv_cpu_prepare(unsigned int cpu) | |
| { | |
| /* Note: GFP_DMA used to get memory below 2G */ | |
| iucv_irq_data[cpu] = kmalloc_node(sizeof(struct iucv_irq_data), | |
| GFP_KERNEL|GFP_DMA, cpu_to_node(cpu)); | |
| if (!iucv_irq_data[cpu]) | |
| goto out_free; | |
| /* Allocate parameter blocks. */ | |
| iucv_param[cpu] = kmalloc_node(sizeof(union iucv_param), | |
| GFP_KERNEL|GFP_DMA, cpu_to_node(cpu)); | |
| if (!iucv_param[cpu]) | |
| 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]) | |
| goto out_free; | |
| return 0; | |
| out_free: | |
| iucv_cpu_dead(cpu); | |
| return -ENOMEM; | |
| } | |
| static int iucv_cpu_online(unsigned int cpu) | |
| { | |
| if (!iucv_path_table) | |
| return 0; | |
| iucv_declare_cpu(NULL); | |
| return 0; | |
| } | |
| static int iucv_cpu_down_prep(unsigned int cpu) | |
| { | |
| cpumask_t cpumask; | |
| if (!iucv_path_table) | |
| return 0; | |
| cpumask_copy(&cpumask, &iucv_buffer_cpumask); | |
| cpumask_clear_cpu(cpu, &cpumask); | |
| if (cpumask_empty(&cpumask)) | |
| /* Can't offline last IUCV enabled cpu. */ | |
| return -EINVAL; | |
| iucv_retrieve_cpu(NULL); | |
| if (!cpumask_empty(&iucv_irq_cpumask)) | |
| return 0; | |
| smp_call_function_single(cpumask_first(&iucv_buffer_cpumask), | |
| iucv_allow_cpu, NULL, 1); | |
| return 0; | |
| } | |
| /** | |
| * iucv_sever_pathid | |
| * @pathid: path identification number. | |
| * @userdata: 16-bytes of user data. | |
| * | |
| * Sever an iucv path to free up the pathid. Used internally. | |
| */ | |
| static int iucv_sever_pathid(u16 pathid, u8 *userdata) | |
| { | |
| union iucv_param *parm; | |
| 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)); | |
| parm->ctrl.ippathid = pathid; | |
| return iucv_call_b2f0(IUCV_SEVER, parm); | |
| } | |
| /** | |
| * __iucv_cleanup_queue | |
| * @dummy: unused dummy argument | |
| * | |
| * Nop function called via smp_call_function to force work items from | |
| * pending external iucv interrupts to the work queue. | |
| */ | |
| static void __iucv_cleanup_queue(void *dummy) | |
| { | |
| } | |
| /** | |
| * iucv_cleanup_queue | |
| * | |
| * Function called after a path has been severed to find all remaining | |
| * work items for the now stale pathid. The caller needs to hold the | |
| * iucv_table_lock. | |
| */ | |
| static void iucv_cleanup_queue(void) | |
| { | |
| struct iucv_irq_list *p, *n; | |
| /* | |
| * When a path is severed, the pathid can be reused immediately | |
| * on a iucv connect or a connection pending interrupt. Remove | |
| * all entries from the task queue that refer to a stale pathid | |
| * (iucv_path_table[ix] == NULL). Only then do the iucv connect | |
| * or deliver the connection pending interrupt. To get all the | |
| * pending interrupts force them to the work queue by calling | |
| * an empty function on all cpus. | |
| */ | |
| smp_call_function(__iucv_cleanup_queue, NULL, 1); | |
| spin_lock_irq(&iucv_queue_lock); | |
| list_for_each_entry_safe(p, n, &iucv_task_queue, list) { | |
| /* Remove stale work items from the task queue. */ | |
| if (iucv_path_table[p->data.ippathid] == NULL) { | |
| list_del(&p->list); | |
| kfree(p); | |
| } | |
| } | |
| spin_unlock_irq(&iucv_queue_lock); | |
| } | |
| /** | |
| * iucv_register: | |
| * @handler: address of iucv handler structure | |
| * @smp: != 0 indicates that the handler can deal with out of order messages | |
| * | |
| * Registers a driver with IUCV. | |
| * | |
| * Returns 0 on success, -ENOMEM if the memory allocation for the pathid | |
| * table failed, or -EIO if IUCV_DECLARE_BUFFER failed on all cpus. | |
| */ | |
| int iucv_register(struct iucv_handler *handler, int smp) | |
| { | |
| int rc; | |
| if (!iucv_available) | |
| return -ENOSYS; | |
| mutex_lock(&iucv_register_mutex); | |
| if (!smp) | |
| iucv_nonsmp_handler++; | |
| if (list_empty(&iucv_handler_list)) { | |
| rc = iucv_enable(); | |
| if (rc) | |
| goto out_mutex; | |
| } else if (!smp && iucv_nonsmp_handler == 1) | |
| iucv_setmask_up(); | |
| INIT_LIST_HEAD(&handler->paths); | |
| spin_lock_bh(&iucv_table_lock); | |
| list_add_tail(&handler->list, &iucv_handler_list); | |
| spin_unlock_bh(&iucv_table_lock); | |
| rc = 0; | |
| out_mutex: | |
| mutex_unlock(&iucv_register_mutex); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(iucv_register); | |
| /** | |
| * iucv_unregister | |
| * @handler: address of iucv handler structure | |
| * @smp: != 0 indicates that the handler can deal with out of order messages | |
| * | |
| * Unregister driver from IUCV. | |
| */ | |
| void iucv_unregister(struct iucv_handler *handler, int smp) | |
| { | |
| struct iucv_path *p, *n; | |
| mutex_lock(&iucv_register_mutex); | |
| spin_lock_bh(&iucv_table_lock); | |
| /* Remove handler from the iucv_handler_list. */ | |
| list_del_init(&handler->list); | |
| /* Sever all pathids still referring to the handler. */ | |
| list_for_each_entry_safe(p, n, &handler->paths, list) { | |
| iucv_sever_pathid(p->pathid, NULL); | |
| iucv_path_table[p->pathid] = NULL; | |
| list_del(&p->list); | |
| iucv_path_free(p); | |
| } | |
| spin_unlock_bh(&iucv_table_lock); | |
| if (!smp) | |
| iucv_nonsmp_handler--; | |
| if (list_empty(&iucv_handler_list)) | |
| iucv_disable(); | |
| else if (!smp && iucv_nonsmp_handler == 0) | |
| iucv_setmask_mp(); | |
| mutex_unlock(&iucv_register_mutex); | |
| } | |
| EXPORT_SYMBOL(iucv_unregister); | |
| static int iucv_reboot_event(struct notifier_block *this, | |
| unsigned long event, void *ptr) | |
| { | |
| int i; | |
| if (cpumask_empty(&iucv_irq_cpumask)) | |
| return NOTIFY_DONE; | |
| get_online_cpus(); | |
| on_each_cpu_mask(&iucv_irq_cpumask, iucv_block_cpu, NULL, 1); | |
| preempt_disable(); | |
| for (i = 0; i < iucv_max_pathid; i++) { | |
| if (iucv_path_table[i]) | |
| 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 | |
| * @handler: address of iucv handler structure | |
| * @userdata: 16 bytes of data reflected to the communication partner | |
| * @private: private data passed to interrupt handlers for this path | |
| * | |
| * This function is issued after the user received a connection pending | |
| * external interrupt and now wishes to complete the IUCV communication path. | |
| * | |
| * Returns the result of the CP IUCV call. | |
| */ | |
| int iucv_path_accept(struct iucv_path *path, struct iucv_handler *handler, | |
| u8 *userdata, void *private) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| local_bh_disable(); | |
| if (cpumask_empty(&iucv_buffer_cpumask)) { | |
| rc = -EIO; | |
| goto out; | |
| } | |
| /* Prepare parameter block. */ | |
| parm = iucv_param[smp_processor_id()]; | |
| memset(parm, 0, sizeof(union iucv_param)); | |
| parm->ctrl.ippathid = path->pathid; | |
| parm->ctrl.ipmsglim = path->msglim; | |
| if (userdata) | |
| memcpy(parm->ctrl.ipuser, userdata, sizeof(parm->ctrl.ipuser)); | |
| parm->ctrl.ipflags1 = path->flags; | |
| rc = iucv_call_b2f0(IUCV_ACCEPT, parm); | |
| if (!rc) { | |
| path->private = private; | |
| path->msglim = parm->ctrl.ipmsglim; | |
| path->flags = parm->ctrl.ipflags1; | |
| } | |
| out: | |
| local_bh_enable(); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(iucv_path_accept); | |
| /** | |
| * iucv_path_connect | |
| * @path: address of iucv path structure | |
| * @handler: address of iucv handler structure | |
| * @userid: 8-byte user identification | |
| * @system: 8-byte target system identification | |
| * @userdata: 16 bytes of data reflected to the communication partner | |
| * @private: private data passed to interrupt handlers for this path | |
| * | |
| * This function establishes an IUCV path. Although the connect may complete | |
| * successfully, you are not able to use the path until you receive an IUCV | |
| * Connection Complete external interrupt. | |
| * | |
| * Returns the result of the CP IUCV call. | |
| */ | |
| int iucv_path_connect(struct iucv_path *path, struct iucv_handler *handler, | |
| u8 *userid, u8 *system, u8 *userdata, | |
| void *private) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| spin_lock_bh(&iucv_table_lock); | |
| iucv_cleanup_queue(); | |
| if (cpumask_empty(&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; | |
| parm->ctrl.ipflags1 = path->flags; | |
| if (userid) { | |
| memcpy(parm->ctrl.ipvmid, userid, sizeof(parm->ctrl.ipvmid)); | |
| ASCEBC(parm->ctrl.ipvmid, sizeof(parm->ctrl.ipvmid)); | |
| EBC_TOUPPER(parm->ctrl.ipvmid, sizeof(parm->ctrl.ipvmid)); | |
| } | |
| if (system) { | |
| memcpy(parm->ctrl.iptarget, system, | |
| sizeof(parm->ctrl.iptarget)); | |
| ASCEBC(parm->ctrl.iptarget, sizeof(parm->ctrl.iptarget)); | |
| EBC_TOUPPER(parm->ctrl.iptarget, sizeof(parm->ctrl.iptarget)); | |
| } | |
| if (userdata) | |
| memcpy(parm->ctrl.ipuser, userdata, sizeof(parm->ctrl.ipuser)); | |
| rc = iucv_call_b2f0(IUCV_CONNECT, parm); | |
| if (!rc) { | |
| if (parm->ctrl.ippathid < iucv_max_pathid) { | |
| path->pathid = parm->ctrl.ippathid; | |
| path->msglim = parm->ctrl.ipmsglim; | |
| path->flags = parm->ctrl.ipflags1; | |
| path->handler = handler; | |
| path->private = private; | |
| list_add_tail(&path->list, &handler->paths); | |
| iucv_path_table[path->pathid] = path; | |
| } else { | |
| iucv_sever_pathid(parm->ctrl.ippathid, | |
| iucv_error_pathid); | |
| rc = -EIO; | |
| } | |
| } | |
| out: | |
| spin_unlock_bh(&iucv_table_lock); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(iucv_path_connect); | |
| /** | |
| * iucv_path_quiesce: | |
| * @path: address of iucv path structure | |
| * @userdata: 16 bytes of data reflected to the communication partner | |
| * | |
| * This function temporarily suspends incoming messages on an IUCV path. | |
| * You can later reactivate the path by invoking the iucv_resume function. | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_path_quiesce(struct iucv_path *path, u8 *userdata) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| local_bh_disable(); | |
| if (cpumask_empty(&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; | |
| } | |
| EXPORT_SYMBOL(iucv_path_quiesce); | |
| /** | |
| * iucv_path_resume: | |
| * @path: address of iucv path structure | |
| * @userdata: 16 bytes of data reflected to the communication partner | |
| * | |
| * This function resumes incoming messages on an IUCV path that has | |
| * been stopped with iucv_path_quiesce. | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_path_resume(struct iucv_path *path, u8 *userdata) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| local_bh_disable(); | |
| if (cpumask_empty(&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; | |
| } | |
| /** | |
| * iucv_path_sever | |
| * @path: address of iucv path structure | |
| * @userdata: 16 bytes of data reflected to the communication partner | |
| * | |
| * This function terminates an IUCV path. | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_path_sever(struct iucv_path *path, u8 *userdata) | |
| { | |
| int rc; | |
| preempt_disable(); | |
| if (cpumask_empty(&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); | |
| 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; | |
| } | |
| EXPORT_SYMBOL(iucv_path_sever); | |
| /** | |
| * iucv_message_purge | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * @srccls: source class of message | |
| * | |
| * Cancels a message you have sent. | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_message_purge(struct iucv_path *path, struct iucv_message *msg, | |
| u32 srccls) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| local_bh_disable(); | |
| if (cpumask_empty(&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; | |
| parm->purge.ipmsgid = msg->id; | |
| parm->purge.ipsrccls = srccls; | |
| parm->purge.ipflags1 = IUCV_IPSRCCLS | IUCV_IPFGMID | IUCV_IPFGPID; | |
| rc = iucv_call_b2f0(IUCV_PURGE, parm); | |
| if (!rc) { | |
| msg->audit = (*(u32 *) &parm->purge.ipaudit) >> 8; | |
| msg->tag = parm->purge.ipmsgtag; | |
| } | |
| out: | |
| local_bh_enable(); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(iucv_message_purge); | |
| /** | |
| * iucv_message_receive_iprmdata | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * @flags: how the message is received (IUCV_IPBUFLST) | |
| * @buffer: address of data buffer or address of struct iucv_array | |
| * @size: length of data buffer | |
| * @residual: | |
| * | |
| * Internal function used by iucv_message_receive and __iucv_message_receive | |
| * to receive RMDATA data stored in struct iucv_message. | |
| */ | |
| static int iucv_message_receive_iprmdata(struct iucv_path *path, | |
| struct iucv_message *msg, | |
| u8 flags, void *buffer, | |
| size_t size, size_t *residual) | |
| { | |
| struct iucv_array *array; | |
| u8 *rmmsg; | |
| size_t copy; | |
| /* | |
| * Message is 8 bytes long and has been stored to the | |
| * message descriptor itself. | |
| */ | |
| if (residual) | |
| *residual = abs(size - 8); | |
| rmmsg = msg->rmmsg; | |
| if (flags & IUCV_IPBUFLST) { | |
| /* Copy to struct iucv_array. */ | |
| size = (size < 8) ? size : 8; | |
| for (array = buffer; size > 0; array++) { | |
| copy = min_t(size_t, size, array->length); | |
| memcpy((u8 *)(addr_t) array->address, | |
| rmmsg, copy); | |
| rmmsg += copy; | |
| size -= copy; | |
| } | |
| } else { | |
| /* Copy to direct buffer. */ | |
| memcpy(buffer, rmmsg, min_t(size_t, size, 8)); | |
| } | |
| return 0; | |
| } | |
| /** | |
| * __iucv_message_receive | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * @flags: how the message is received (IUCV_IPBUFLST) | |
| * @buffer: address of data buffer or address of struct iucv_array | |
| * @size: length of data buffer | |
| * @residual: | |
| * | |
| * This function receives messages that are being sent to you over | |
| * established paths. This function will deal with RMDATA messages | |
| * embedded in struct iucv_message as well. | |
| * | |
| * Locking: no locking | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int __iucv_message_receive(struct iucv_path *path, struct iucv_message *msg, | |
| u8 flags, void *buffer, size_t size, size_t *residual) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| if (msg->flags & IUCV_IPRMDATA) | |
| return iucv_message_receive_iprmdata(path, msg, flags, | |
| buffer, size, residual); | |
| if (cpumask_empty(&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; | |
| parm->db.ipbfln1f = (u32) size; | |
| parm->db.ipmsgid = msg->id; | |
| parm->db.ippathid = path->pathid; | |
| parm->db.iptrgcls = msg->class; | |
| parm->db.ipflags1 = (flags | IUCV_IPFGPID | | |
| IUCV_IPFGMID | IUCV_IPTRGCLS); | |
| rc = iucv_call_b2f0(IUCV_RECEIVE, parm); | |
| if (!rc || rc == 5) { | |
| msg->flags = parm->db.ipflags1; | |
| if (residual) | |
| *residual = parm->db.ipbfln1f; | |
| } | |
| out: | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(__iucv_message_receive); | |
| /** | |
| * iucv_message_receive | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * @flags: how the message is received (IUCV_IPBUFLST) | |
| * @buffer: address of data buffer or address of struct iucv_array | |
| * @size: length of data buffer | |
| * @residual: | |
| * | |
| * This function receives messages that are being sent to you over | |
| * established paths. This function will deal with RMDATA messages | |
| * embedded in struct iucv_message as well. | |
| * | |
| * Locking: local_bh_enable/local_bh_disable | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_message_receive(struct iucv_path *path, struct iucv_message *msg, | |
| u8 flags, void *buffer, size_t size, size_t *residual) | |
| { | |
| int rc; | |
| if (msg->flags & IUCV_IPRMDATA) | |
| return iucv_message_receive_iprmdata(path, msg, flags, | |
| buffer, size, residual); | |
| local_bh_disable(); | |
| rc = __iucv_message_receive(path, msg, flags, buffer, size, residual); | |
| local_bh_enable(); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(iucv_message_receive); | |
| /** | |
| * iucv_message_reject | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * | |
| * The reject function refuses a specified message. Between the time you | |
| * are notified of a message and the time that you complete the message, | |
| * the message may be rejected. | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_message_reject(struct iucv_path *path, struct iucv_message *msg) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| local_bh_disable(); | |
| if (cpumask_empty(&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; | |
| parm->db.ipmsgid = msg->id; | |
| 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; | |
| } | |
| EXPORT_SYMBOL(iucv_message_reject); | |
| /** | |
| * iucv_message_reply | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * @flags: how the reply is sent (IUCV_IPRMDATA, IUCV_IPPRTY, IUCV_IPBUFLST) | |
| * @reply: address of reply data buffer or address of struct iucv_array | |
| * @size: length of reply data buffer | |
| * | |
| * This function responds to the two-way messages that you receive. You | |
| * must identify completely the message to which you wish to reply. ie, | |
| * pathid, msgid, and trgcls. Prmmsg signifies the data is moved into | |
| * the parameter list. | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_message_reply(struct iucv_path *path, struct iucv_message *msg, | |
| u8 flags, void *reply, size_t size) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| local_bh_disable(); | |
| if (cpumask_empty(&iucv_buffer_cpumask)) { | |
| rc = -EIO; | |
| goto out; | |
| } | |
| parm = iucv_param[smp_processor_id()]; | |
| memset(parm, 0, sizeof(union iucv_param)); | |
| if (flags & IUCV_IPRMDATA) { | |
| parm->dpl.ippathid = path->pathid; | |
| parm->dpl.ipflags1 = flags; | |
| parm->dpl.ipmsgid = msg->id; | |
| parm->dpl.iptrgcls = msg->class; | |
| memcpy(parm->dpl.iprmmsg, reply, min_t(size_t, size, 8)); | |
| } else { | |
| parm->db.ipbfadr1 = (u32)(addr_t) reply; | |
| parm->db.ipbfln1f = (u32) size; | |
| parm->db.ippathid = path->pathid; | |
| parm->db.ipflags1 = flags; | |
| parm->db.ipmsgid = msg->id; | |
| parm->db.iptrgcls = msg->class; | |
| } | |
| rc = iucv_call_b2f0(IUCV_REPLY, parm); | |
| out: | |
| local_bh_enable(); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(iucv_message_reply); | |
| /** | |
| * __iucv_message_send | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * @flags: how the message is sent (IUCV_IPRMDATA, IUCV_IPPRTY, IUCV_IPBUFLST) | |
| * @srccls: source class of message | |
| * @buffer: address of send buffer or address of struct iucv_array | |
| * @size: length of send buffer | |
| * | |
| * This function transmits data to another application. Data to be | |
| * transmitted is in a buffer and this is a one-way message and the | |
| * receiver will not reply to the message. | |
| * | |
| * Locking: no locking | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int __iucv_message_send(struct iucv_path *path, struct iucv_message *msg, | |
| u8 flags, u32 srccls, void *buffer, size_t size) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| if (cpumask_empty(&iucv_buffer_cpumask)) { | |
| rc = -EIO; | |
| goto out; | |
| } | |
| parm = iucv_param[smp_processor_id()]; | |
| memset(parm, 0, sizeof(union iucv_param)); | |
| if (flags & IUCV_IPRMDATA) { | |
| /* Message of 8 bytes can be placed into the parameter list. */ | |
| parm->dpl.ippathid = path->pathid; | |
| parm->dpl.ipflags1 = flags | IUCV_IPNORPY; | |
| parm->dpl.iptrgcls = msg->class; | |
| parm->dpl.ipsrccls = srccls; | |
| parm->dpl.ipmsgtag = msg->tag; | |
| memcpy(parm->dpl.iprmmsg, buffer, 8); | |
| } else { | |
| parm->db.ipbfadr1 = (u32)(addr_t) buffer; | |
| parm->db.ipbfln1f = (u32) size; | |
| parm->db.ippathid = path->pathid; | |
| parm->db.ipflags1 = flags | IUCV_IPNORPY; | |
| parm->db.iptrgcls = msg->class; | |
| parm->db.ipsrccls = srccls; | |
| parm->db.ipmsgtag = msg->tag; | |
| } | |
| rc = iucv_call_b2f0(IUCV_SEND, parm); | |
| if (!rc) | |
| msg->id = parm->db.ipmsgid; | |
| out: | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(__iucv_message_send); | |
| /** | |
| * iucv_message_send | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * @flags: how the message is sent (IUCV_IPRMDATA, IUCV_IPPRTY, IUCV_IPBUFLST) | |
| * @srccls: source class of message | |
| * @buffer: address of send buffer or address of struct iucv_array | |
| * @size: length of send buffer | |
| * | |
| * This function transmits data to another application. Data to be | |
| * transmitted is in a buffer and this is a one-way message and the | |
| * receiver will not reply to the message. | |
| * | |
| * Locking: local_bh_enable/local_bh_disable | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_message_send(struct iucv_path *path, struct iucv_message *msg, | |
| u8 flags, u32 srccls, void *buffer, size_t size) | |
| { | |
| int rc; | |
| local_bh_disable(); | |
| rc = __iucv_message_send(path, msg, flags, srccls, buffer, size); | |
| local_bh_enable(); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(iucv_message_send); | |
| /** | |
| * iucv_message_send2way | |
| * @path: address of iucv path structure | |
| * @msg: address of iucv msg structure | |
| * @flags: how the message is sent and the reply is received | |
| * (IUCV_IPRMDATA, IUCV_IPBUFLST, IUCV_IPPRTY, IUCV_ANSLST) | |
| * @srccls: source class of message | |
| * @buffer: address of send buffer or address of struct iucv_array | |
| * @size: length of send buffer | |
| * @ansbuf: address of answer buffer or address of struct iucv_array | |
| * @asize: size of reply buffer | |
| * | |
| * This function transmits data to another application. Data to be | |
| * transmitted is in a buffer. The receiver of the send is expected to | |
| * reply to the message and a buffer is provided into which IUCV moves | |
| * the reply to this message. | |
| * | |
| * Returns the result from the CP IUCV call. | |
| */ | |
| int iucv_message_send2way(struct iucv_path *path, struct iucv_message *msg, | |
| u8 flags, u32 srccls, void *buffer, size_t size, | |
| void *answer, size_t asize, size_t *residual) | |
| { | |
| union iucv_param *parm; | |
| int rc; | |
| local_bh_disable(); | |
| if (cpumask_empty(&iucv_buffer_cpumask)) { | |
| rc = -EIO; | |
| goto out; | |
| } | |
| parm = iucv_param[smp_processor_id()]; | |
| memset(parm, 0, sizeof(union iucv_param)); | |
| if (flags & IUCV_IPRMDATA) { | |
| parm->dpl.ippathid = path->pathid; | |
| parm->dpl.ipflags1 = path->flags; /* priority message */ | |
| parm->dpl.iptrgcls = msg->class; | |
| parm->dpl.ipsrccls = srccls; | |
| parm->dpl.ipmsgtag = msg->tag; | |
| parm->dpl.ipbfadr2 = (u32)(addr_t) answer; | |
| parm->dpl.ipbfln2f = (u32) asize; | |
| memcpy(parm->dpl.iprmmsg, buffer, 8); | |
| } else { | |
| parm->db.ippathid = path->pathid; | |
| parm->db.ipflags1 = path->flags; /* priority message */ | |
| parm->db.iptrgcls = msg->class; | |
| parm->db.ipsrccls = srccls; | |
| parm->db.ipmsgtag = msg->tag; | |
| parm->db.ipbfadr1 = (u32)(addr_t) buffer; | |
| parm->db.ipbfln1f = (u32) size; | |
| parm->db.ipbfadr2 = (u32)(addr_t) answer; | |
| parm->db.ipbfln2f = (u32) asize; | |
| } | |
| rc = iucv_call_b2f0(IUCV_SEND, parm); | |
| if (!rc) | |
| msg->id = parm->db.ipmsgid; | |
| out: | |
| local_bh_enable(); | |
| return rc; | |
| } | |
| EXPORT_SYMBOL(iucv_message_send2way); | |
| /** | |
| * iucv_path_pending | |
| * @data: Pointer to external interrupt buffer | |
| * | |
| * Process connection pending work item. Called from tasklet while holding | |
| * iucv_table_lock. | |
| */ | |
| struct iucv_path_pending { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iptype; | |
| u16 ipmsglim; | |
| u16 res1; | |
| u8 ipvmid[8]; | |
| u8 ipuser[16]; | |
| u32 res3; | |
| u8 ippollfg; | |
| u8 res4[3]; | |
| } __packed; | |
| static void iucv_path_pending(struct iucv_irq_data *data) | |
| { | |
| struct iucv_path_pending *ipp = (void *) data; | |
| struct iucv_handler *handler; | |
| struct iucv_path *path; | |
| char *error; | |
| BUG_ON(iucv_path_table[ipp->ippathid]); | |
| /* New pathid, handler found. Create a new path struct. */ | |
| error = iucv_error_no_memory; | |
| path = iucv_path_alloc(ipp->ipmsglim, ipp->ipflags1, GFP_ATOMIC); | |
| if (!path) | |
| goto out_sever; | |
| path->pathid = ipp->ippathid; | |
| iucv_path_table[path->pathid] = path; | |
| EBCASC(ipp->ipvmid, 8); | |
| /* Call registered handler until one is found that wants the path. */ | |
| list_for_each_entry(handler, &iucv_handler_list, list) { | |
| if (!handler->path_pending) | |
| continue; | |
| /* | |
| * Add path to handler to allow a call to iucv_path_sever | |
| * inside the path_pending function. If the handler returns | |
| * an error remove the path from the handler again. | |
| */ | |
| list_add(&path->list, &handler->paths); | |
| path->handler = handler; | |
| if (!handler->path_pending(path, ipp->ipvmid, ipp->ipuser)) | |
| return; | |
| list_del(&path->list); | |
| path->handler = NULL; | |
| } | |
| /* No handler wanted the path. */ | |
| iucv_path_table[path->pathid] = NULL; | |
| iucv_path_free(path); | |
| error = iucv_error_no_listener; | |
| out_sever: | |
| iucv_sever_pathid(ipp->ippathid, error); | |
| } | |
| /** | |
| * iucv_path_complete | |
| * @data: Pointer to external interrupt buffer | |
| * | |
| * Process connection complete work item. Called from tasklet while holding | |
| * iucv_table_lock. | |
| */ | |
| struct iucv_path_complete { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iptype; | |
| u16 ipmsglim; | |
| u16 res1; | |
| u8 res2[8]; | |
| u8 ipuser[16]; | |
| u32 res3; | |
| u8 ippollfg; | |
| u8 res4[3]; | |
| } __packed; | |
| 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); | |
| } | |
| /** | |
| * iucv_path_severed | |
| * @data: Pointer to external interrupt buffer | |
| * | |
| * Process connection severed work item. Called from tasklet while holding | |
| * iucv_table_lock. | |
| */ | |
| struct iucv_path_severed { | |
| u16 ippathid; | |
| u8 res1; | |
| u8 iptype; | |
| u32 res2; | |
| u8 res3[8]; | |
| u8 ipuser[16]; | |
| u32 res4; | |
| u8 ippollfg; | |
| u8 res5[3]; | |
| } __packed; | |
| static void iucv_path_severed(struct iucv_irq_data *data) | |
| { | |
| struct iucv_path_severed *ips = (void *) data; | |
| struct iucv_path *path = iucv_path_table[ips->ippathid]; | |
| if (!path || !path->handler) /* Already severed */ | |
| return; | |
| if (path->handler->path_severed) | |
| path->handler->path_severed(path, ips->ipuser); | |
| else { | |
| iucv_sever_pathid(path->pathid, NULL); | |
| iucv_path_table[path->pathid] = NULL; | |
| list_del(&path->list); | |
| iucv_path_free(path); | |
| } | |
| } | |
| /** | |
| * iucv_path_quiesced | |
| * @data: Pointer to external interrupt buffer | |
| * | |
| * Process connection quiesced work item. Called from tasklet while holding | |
| * iucv_table_lock. | |
| */ | |
| struct iucv_path_quiesced { | |
| u16 ippathid; | |
| u8 res1; | |
| u8 iptype; | |
| u32 res2; | |
| u8 res3[8]; | |
| u8 ipuser[16]; | |
| u32 res4; | |
| u8 ippollfg; | |
| u8 res5[3]; | |
| } __packed; | |
| static void iucv_path_quiesced(struct iucv_irq_data *data) | |
| { | |
| struct iucv_path_quiesced *ipq = (void *) data; | |
| struct iucv_path *path = iucv_path_table[ipq->ippathid]; | |
| if (path && path->handler && path->handler->path_quiesced) | |
| path->handler->path_quiesced(path, ipq->ipuser); | |
| } | |
| /** | |
| * iucv_path_resumed | |
| * @data: Pointer to external interrupt buffer | |
| * | |
| * Process connection resumed work item. Called from tasklet while holding | |
| * iucv_table_lock. | |
| */ | |
| struct iucv_path_resumed { | |
| u16 ippathid; | |
| u8 res1; | |
| u8 iptype; | |
| u32 res2; | |
| u8 res3[8]; | |
| u8 ipuser[16]; | |
| u32 res4; | |
| u8 ippollfg; | |
| u8 res5[3]; | |
| } __packed; | |
| static void iucv_path_resumed(struct iucv_irq_data *data) | |
| { | |
| struct iucv_path_resumed *ipr = (void *) data; | |
| struct iucv_path *path = iucv_path_table[ipr->ippathid]; | |
| if (path && path->handler && path->handler->path_resumed) | |
| path->handler->path_resumed(path, ipr->ipuser); | |
| } | |
| /** | |
| * iucv_message_complete | |
| * @data: Pointer to external interrupt buffer | |
| * | |
| * Process message complete work item. Called from tasklet while holding | |
| * iucv_table_lock. | |
| */ | |
| struct iucv_message_complete { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iptype; | |
| u32 ipmsgid; | |
| u32 ipaudit; | |
| u8 iprmmsg[8]; | |
| u32 ipsrccls; | |
| u32 ipmsgtag; | |
| u32 res; | |
| u32 ipbfln2f; | |
| u8 ippollfg; | |
| u8 res2[3]; | |
| } __packed; | |
| static void iucv_message_complete(struct iucv_irq_data *data) | |
| { | |
| struct iucv_message_complete *imc = (void *) data; | |
| struct iucv_path *path = iucv_path_table[imc->ippathid]; | |
| struct iucv_message msg; | |
| if (path && path->handler && path->handler->message_complete) { | |
| msg.flags = imc->ipflags1; | |
| msg.id = imc->ipmsgid; | |
| msg.audit = imc->ipaudit; | |
| memcpy(msg.rmmsg, imc->iprmmsg, 8); | |
| msg.class = imc->ipsrccls; | |
| msg.tag = imc->ipmsgtag; | |
| msg.length = imc->ipbfln2f; | |
| path->handler->message_complete(path, &msg); | |
| } | |
| } | |
| /** | |
| * iucv_message_pending | |
| * @data: Pointer to external interrupt buffer | |
| * | |
| * Process message pending work item. Called from tasklet while holding | |
| * iucv_table_lock. | |
| */ | |
| struct iucv_message_pending { | |
| u16 ippathid; | |
| u8 ipflags1; | |
| u8 iptype; | |
| u32 ipmsgid; | |
| u32 iptrgcls; | |
| union { | |
| u32 iprmmsg1_u32; | |
| u8 iprmmsg1[4]; | |
| } ln1msg1; | |
| union { | |
| u32 ipbfln1f; | |
| u8 iprmmsg2[4]; | |
| } ln1msg2; | |
| u32 res1[3]; | |
| u32 ipbfln2f; | |
| u8 ippollfg; | |
| u8 res2[3]; | |
| } __packed; | |
| static void iucv_message_pending(struct iucv_irq_data *data) | |
| { | |
| struct iucv_message_pending *imp = (void *) data; | |
| struct iucv_path *path = iucv_path_table[imp->ippathid]; | |
| struct iucv_message msg; | |
| if (path && path->handler && path->handler->message_pending) { | |
| msg.flags = imp->ipflags1; | |
| msg.id = imp->ipmsgid; | |
| msg.class = imp->iptrgcls; | |
| if (imp->ipflags1 & IUCV_IPRMDATA) { | |
| memcpy(msg.rmmsg, imp->ln1msg1.iprmmsg1, 8); | |
| msg.length = 8; | |
| } else | |
| msg.length = imp->ln1msg2.ipbfln1f; | |
| msg.reply_size = imp->ipbfln2f; | |
| path->handler->message_pending(path, &msg); | |
| } | |
| } | |
| /** | |
| * iucv_tasklet_fn: | |
| * | |
| * This tasklet loops over the queue of irq buffers created by | |
| * iucv_external_interrupt, calls the appropriate action handler | |
| * and then frees the buffer. | |
| */ | |
| static void iucv_tasklet_fn(unsigned long ignored) | |
| { | |
| typedef void iucv_irq_fn(struct iucv_irq_data *); | |
| static iucv_irq_fn *irq_fn[] = { | |
| [0x02] = iucv_path_complete, | |
| [0x03] = iucv_path_severed, | |
| [0x04] = iucv_path_quiesced, | |
| [0x05] = iucv_path_resumed, | |
| [0x06] = iucv_message_complete, | |
| [0x07] = iucv_message_complete, | |
| [0x08] = iucv_message_pending, | |
| [0x09] = iucv_message_pending, | |
| }; | |
| LIST_HEAD(task_queue); | |
| struct iucv_irq_list *p, *n; | |
| /* Serialize tasklet, iucv_path_sever and iucv_path_connect. */ | |
| if (!spin_trylock(&iucv_table_lock)) { | |
| tasklet_schedule(&iucv_tasklet); | |
| return; | |
| } | |
| iucv_active_cpu = smp_processor_id(); | |
| spin_lock_irq(&iucv_queue_lock); | |
| list_splice_init(&iucv_task_queue, &task_queue); | |
| spin_unlock_irq(&iucv_queue_lock); | |
| list_for_each_entry_safe(p, n, &task_queue, list) { | |
| list_del_init(&p->list); | |
| irq_fn[p->data.iptype](&p->data); | |
| kfree(p); | |
| } | |
| iucv_active_cpu = -1; | |
| spin_unlock(&iucv_table_lock); | |
| } | |
| /** | |
| * iucv_work_fn: | |
| * | |
| * This work function loops over the queue of path pending irq blocks | |
| * created by iucv_external_interrupt, calls the appropriate action | |
| * handler and then frees the buffer. | |
| */ | |
| static void iucv_work_fn(struct work_struct *work) | |
| { | |
| LIST_HEAD(work_queue); | |
| struct iucv_irq_list *p, *n; | |
| /* Serialize tasklet, iucv_path_sever and iucv_path_connect. */ | |
| spin_lock_bh(&iucv_table_lock); | |
| iucv_active_cpu = smp_processor_id(); | |
| spin_lock_irq(&iucv_queue_lock); | |
| list_splice_init(&iucv_work_queue, &work_queue); | |
| spin_unlock_irq(&iucv_queue_lock); | |
| iucv_cleanup_queue(); | |
| list_for_each_entry_safe(p, n, &work_queue, list) { | |
| list_del_init(&p->list); | |
| iucv_path_pending(&p->data); | |
| kfree(p); | |
| } | |
| iucv_active_cpu = -1; | |
| spin_unlock_bh(&iucv_table_lock); | |
| } | |
| /** | |
| * iucv_external_interrupt | |
| * @code: irq code | |
| * | |
| * Handles external interrupts coming in from CP. | |
| * Places the interrupt buffer on a queue and schedules iucv_tasklet_fn(). | |
| */ | |
| static void iucv_external_interrupt(struct ext_code ext_code, | |
| unsigned int param32, unsigned long param64) | |
| { | |
| struct iucv_irq_data *p; | |
| struct iucv_irq_list *work; | |
| inc_irq_stat(IRQEXT_IUC); | |
| p = iucv_irq_data[smp_processor_id()]; | |
| if (p->ippathid >= iucv_max_pathid) { | |
| WARN_ON(p->ippathid >= iucv_max_pathid); | |
| iucv_sever_pathid(p->ippathid, iucv_error_no_listener); | |
| return; | |
| } | |
| BUG_ON(p->iptype < 0x01 || p->iptype > 0x09); | |
| work = kmalloc(sizeof(struct iucv_irq_list), GFP_ATOMIC); | |
| if (!work) { | |
| pr_warn("iucv_external_interrupt: out of memory\n"); | |
| return; | |
| } | |
| memcpy(&work->data, p, sizeof(work->data)); | |
| spin_lock(&iucv_queue_lock); | |
| if (p->iptype == 0x01) { | |
| /* Path pending interrupt. */ | |
| list_add_tail(&work->list, &iucv_work_queue); | |
| schedule_work(&iucv_work); | |
| } else { | |
| /* The other interrupts. */ | |
| list_add_tail(&work->list, &iucv_task_queue); | |
| tasklet_schedule(&iucv_tasklet); | |
| } | |
| 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; | |
| struct iucv_irq_list *p, *n; | |
| int rc = 0; | |
| #ifdef CONFIG_PM_DEBUG | |
| printk(KERN_WARNING "iucv_pm_freeze\n"); | |
| #endif | |
| if (iucv_pm_state != IUCV_PM_FREEZING) { | |
| for_each_cpu(cpu, &iucv_irq_cpumask) | |
| smp_call_function_single(cpu, iucv_block_cpu_almost, | |
| NULL, 1); | |
| cancel_work_sync(&iucv_work); | |
| list_for_each_entry_safe(p, n, &iucv_work_queue, list) { | |
| list_del_init(&p->list); | |
| iucv_sever_pathid(p->data.ippathid, | |
| iucv_error_no_listener); | |
| kfree(p); | |
| } | |
| } | |
| iucv_pm_state = IUCV_PM_FREEZING; | |
| 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 | |
| iucv_pm_state = IUCV_PM_THAWING; | |
| if (!iucv_path_table) { | |
| rc = iucv_enable(); | |
| if (rc) | |
| goto out; | |
| } | |
| if (cpumask_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 ((iucv_pm_state != IUCV_PM_RESTORING) && iucv_path_table) | |
| pr_warn("Suspending Linux did not completely close all IUCV connections\n"); | |
| iucv_pm_state = IUCV_PM_RESTORING; | |
| if (cpumask_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; | |
| } | |
| struct iucv_interface iucv_if = { | |
| .message_receive = iucv_message_receive, | |
| .__message_receive = __iucv_message_receive, | |
| .message_reply = iucv_message_reply, | |
| .message_reject = iucv_message_reject, | |
| .message_send = iucv_message_send, | |
| .__message_send = __iucv_message_send, | |
| .message_send2way = iucv_message_send2way, | |
| .message_purge = iucv_message_purge, | |
| .path_accept = iucv_path_accept, | |
| .path_connect = iucv_path_connect, | |
| .path_quiesce = iucv_path_quiesce, | |
| .path_resume = iucv_path_resume, | |
| .path_sever = iucv_path_sever, | |
| .iucv_register = iucv_register, | |
| .iucv_unregister = iucv_unregister, | |
| .bus = NULL, | |
| .root = NULL, | |
| }; | |
| EXPORT_SYMBOL(iucv_if); | |
| static enum cpuhp_state iucv_online; | |
| /** | |
| * iucv_init | |
| * | |
| * Allocates and initializes various data structures. | |
| */ | |
| static int __init iucv_init(void) | |
| { | |
| int rc; | |
| if (!MACHINE_IS_VM) { | |
| rc = -EPROTONOSUPPORT; | |
| goto out; | |
| } | |
| ctl_set_bit(0, 1); | |
| rc = iucv_query_maxconn(); | |
| if (rc) | |
| goto out_ctl; | |
| rc = register_external_irq(EXT_IRQ_IUCV, iucv_external_interrupt); | |
| if (rc) | |
| goto out_ctl; | |
| iucv_root = root_device_register("iucv"); | |
| if (IS_ERR(iucv_root)) { | |
| rc = PTR_ERR(iucv_root); | |
| goto out_int; | |
| } | |
| rc = cpuhp_setup_state(CPUHP_NET_IUCV_PREPARE, "net/iucv:prepare", | |
| iucv_cpu_prepare, iucv_cpu_dead); | |
| if (rc) | |
| goto out_dev; | |
| rc = cpuhp_setup_state(CPUHP_AP_ONLINE_DYN, "net/iucv:online", | |
| iucv_cpu_online, iucv_cpu_down_prep); | |
| if (rc < 0) | |
| goto out_prep; | |
| iucv_online = rc; | |
| rc = register_reboot_notifier(&iucv_reboot_notifier); | |
| if (rc) | |
| goto out_remove_hp; | |
| 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_reboot; | |
| iucv_if.root = iucv_root; | |
| iucv_if.bus = &iucv_bus; | |
| return 0; | |
| out_reboot: | |
| unregister_reboot_notifier(&iucv_reboot_notifier); | |
| out_remove_hp: | |
| cpuhp_remove_state(iucv_online); | |
| out_prep: | |
| cpuhp_remove_state(CPUHP_NET_IUCV_PREPARE); | |
| out_dev: | |
| root_device_unregister(iucv_root); | |
| out_int: | |
| unregister_external_irq(EXT_IRQ_IUCV, iucv_external_interrupt); | |
| out_ctl: | |
| ctl_clear_bit(0, 1); | |
| out: | |
| return rc; | |
| } | |
| /** | |
| * iucv_exit | |
| * | |
| * Frees everything allocated from iucv_init. | |
| */ | |
| static void __exit iucv_exit(void) | |
| { | |
| struct iucv_irq_list *p, *n; | |
| spin_lock_irq(&iucv_queue_lock); | |
| list_for_each_entry_safe(p, n, &iucv_task_queue, list) | |
| kfree(p); | |
| 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); | |
| cpuhp_remove_state_nocalls(iucv_online); | |
| cpuhp_remove_state(CPUHP_NET_IUCV_PREPARE); | |
| root_device_unregister(iucv_root); | |
| bus_unregister(&iucv_bus); | |
| unregister_external_irq(EXT_IRQ_IUCV, iucv_external_interrupt); | |
| } | |
| subsys_initcall(iucv_init); | |
| module_exit(iucv_exit); | |
| MODULE_AUTHOR("(C) 2001 IBM Corp. by Fritz Elfert (felfert@millenux.com)"); | |
| MODULE_DESCRIPTION("Linux for S/390 IUCV lowlevel driver"); | |
| MODULE_LICENSE("GPL"); |