- patches.arch/s390-04-01-clear-high-regs.patch: kernel:
authorJohn Jolly <jjolly@suse.de>
Mon, 14 Dec 2009 21:58:33 +0000 (22:58 +0100)
committerJohn Jolly <jjolly@suse.de>
Mon, 14 Dec 2009 21:58:33 +0000 (22:58 +0100)
  clear high-order bits after switching to 64-bit mode
  (BNC#563999,LTC#58088).
- patches.arch/s390-04-02-zcrypt-hrtimer.patch: zcrypt: Do not
  simultaneously schedule hrtimer (BNC#563999,LTC#58222).
- patches.arch/s390-04-03-dasd-diag-ro.patch: dasd: support DIAG
  access for read-only devices (BNC#563999,LTC#57147).
- patches.arch/s390-04-04-mm-fault-fix.patch: kernel: performance
  counter fix and page fault optimization (BNC#563999).
- patches.arch/s390-04-05-sclp-dump-indicator.patch: kernel:
  fix dump indicator (BNC#563999).
- patches.arch/s390-04-06-dasd-move-diag-kmsg.patch: dasd:
  move diag kmsg to generic dasd kmsg (BNC#563999).
- patches.arch/s390-04-07-cio-fix-double-free.patch: cio: double
  free under memory pressure (BNC#563999).
- patches.arch/s390-04-08-cio-fix-dev-stall.patch: cio: device
  recovery stalls after multiple hardware events (BNC#563999).
- patches.arch/s390-04-09-cio-recover-hw-changes.patch: cio:
  device recovery fails after concurrent hardware changes
  (BNC#563999).
- patches.arch/s390-04-10-cio-fix-onoffline-failure.patch: cio:
  setting a device online or offline fails for unknown reasons
  (BNC#563999).
- patches.arch/s390-04-11-cio-error-reporting.patch: cio:
  incorrect device state after device recognition and recovery
  (BNC#563999).
- patches.arch/s390-04-12-cio-avoid-panic.patch: cio: kernel
  panic after unexpected interrupt (BNC#563999).
- patches.arch/s390-04-13-cio-internal-io.patch: cio:
  initialization of I/O devices fails (BNC#563999).
- patches.arch/s390-04-14-cio-allow-offline.patch: cio: not
  operational devices cannot be deactivated (BNC#563999).
- patches.arch/s390-04-15-cio-split-pgid.patch: cio: erratic
  DASD I/O behavior (BNC#563999).
- patches.arch/s390-04-16-cio-path-verification.patch: cio:
  DASD cannot be set online (BNC#563999).
- patches.arch/s390-04-17-cio-steal-lock.patch: cio: DASD steal
  lock task hangs (BNC#563999).
- patches.arch/s390-04-18-cio-fix-memleak-chk-dev.patch: cio:
  memory leaks when checking unusable devices (BNC#563999).
- patches.arch/s390-04-19-cio-fix-deact-dev-panic.patch: cio:
  deactivated devices can cause use after free panic (BNC#563999).

suse-commit: a757d6456461fe5f1bb33178b4a42846b3ae9e3d

35 files changed:
Documentation/kmsg/s390/dasd
Documentation/kmsg/s390/dasd-diag [deleted file]
arch/s390/Kconfig
arch/s390/include/asm/ccwdev.h
arch/s390/include/asm/mmu_context.h
arch/s390/include/asm/pgalloc.h
arch/s390/include/asm/setup.h
arch/s390/include/asm/uaccess.h
arch/s390/kernel/head64.S
arch/s390/kernel/setup.c
arch/s390/kernel/vdso.c
arch/s390/kvm/Kconfig
arch/s390/lib/uaccess_mvcos.c
arch/s390/lib/uaccess_pt.c
arch/s390/mm/fault.c
arch/s390/mm/pgtable.c
drivers/s390/block/dasd.c
drivers/s390/block/dasd_diag.c
drivers/s390/block/dasd_eckd.c
drivers/s390/char/sclp_cmd.c
drivers/s390/char/tape_core.c
drivers/s390/cio/Makefile
drivers/s390/cio/ccwreq.c [new file with mode: 0644]
drivers/s390/cio/cio.h
drivers/s390/cio/css.c
drivers/s390/cio/css.h
drivers/s390/cio/device.c
drivers/s390/cio/device.h
drivers/s390/cio/device_fsm.c
drivers/s390/cio/device_id.c
drivers/s390/cio/device_ops.c
drivers/s390/cio/device_pgid.c
drivers/s390/cio/device_status.c
drivers/s390/cio/io_sch.h
drivers/s390/crypto/ap_bus.c

index d3c5342..2944c5f 100644 (file)
  * User action:
  * Note the reason code and contact your support organization.
 */
+
+
+------------------------------------------------------------------------------------
+/* dasd_diag */
+
+/*?
+ * Text: "%s: A 64-bit DIAG call failed\n"
+ * Severity: Warning
+ * Parameter:
+ *   @1: bus ID of the DASD
+ * Description:
+ * 64-bit DIAG calls require a 64-bit z/VM version.
+ * User action:
+ * Use z/VM 5.2 or later or set the sysfs 'use_diag' attribute of the DASD to 0
+ * to switch off DIAG.
+ */
+
+/*?
+ * Text: "%s: Accessing the DASD failed because of an incorrect format (rc=%d)\n"
+ * Severity: Warning
+ * Parameter:
+ *   @1: bus ID of the DASD
+ *   @2: return code
+ * Description:
+ * The format of the DASD is not correct.
+ * User action:
+ * Check the device format. For details about the return code see the
+ * section about the INITIALIZE function for DIAGNOSE Code X'250'
+ * in "z/VM CP Programming Services". If you cannot resolve the error, note
+ * the return code and contact your support organization.
+ */
+
+/*?
+ * Text: "%s: New DASD with %ld byte/block, total size %ld KB%s\n"
+ * Severity: Informational
+ * Parameter:
+ *   @1: bus ID of the DASD
+ *   @2: bytes per block
+ *   @3: size
+ *   @4: access mode
+ * Description:
+ * A DASD with the indicated block size and total size has been set online.
+ * If the DASD is configured as read-only to the real or virtual hardware,
+ * the message includes an indication of this hardware access mode. The
+ * hardware access mode is independent from the 'readonly' attribute of
+ * the device in sysfs.
+ * User action:
+ * None.
+ */
+
+/*?
+ * Text: "%s: DIAG ERP failed with rc=%d\n"
+ * Severity: Warning
+ * Parameter:
+ *   @1: bus ID of the DASD
+ *   @2: return code
+ * Description:
+ * An error in the DIAG processing could not be recovered by the error
+ * recovery procedure (ERP) of the DIAG discipline.
+ * User action:
+ * Note the return code, check for related I/O errors, and report this problem
+ * to your support organization.
+ */
+
+/*?
+ * Text: "%s: DIAG initialization failed with rc=%d\n"
+ * Severity: Warning
+ * Parameter:
+ *   @1: bus ID of the DASD
+ *   @2: return code
+ * Description:
+ * Initializing the DASD with the DIAG discipline failed. Possible reasons for
+ * this problem are that the device has a device type other than FBA or ECKD,
+ * or has a block size other than one of the supported sizes:
+ * 512 byte, 1024 byte, 2048 byte, or 4096 byte.
+ * User action:
+ * Ensure that the device can be written to and has a supported device type
+ * and block size. For details about the return code see the section about
+ * the INITIALIZE function for DIAGNOSE Code X'250' in "z/VM CP Programming
+ * Services". If you cannot resolve the error, note the error code and contact
+ * your support organization.
+ */
+
+/*?
+ * Text: "%s: Device type %d is not supported in DIAG mode\n"
+ * Severity: Warning
+ * Parameter:
+ *   @1: bus ID of the DASD
+ *   @2: device type
+ * Description:
+ * Only DASD of type FBA and ECKD are supported in DIAG mode.
+ * User action:
+ * Set the sysfs 'use_diag' attribute of the DASD to 0 and try again to access
+ * the DASD.
+ */
+
+/*?
+ * Text: "Discipline %s cannot be used without z/VM\n"
+ * Severity: Informational
+ * Parameter:
+ *   @1: discipline name
+ * Description:
+ * The discipline that is specified with the dasd= kernel or module parameter
+ * is only available for Linux instances that run as guest operating
+ * systems of the z/VM hypervisor.
+ * User action:
+ * Remove the unsupported discipline from the parameter string.
+ */
+
+/*?
+ * Text: "%s: The access mode of a DIAG device changed to read-only\n"
+ * Severity: Warning
+ * Parameter:
+ *   @1: bus ID of the DASD
+ * Description:
+ * A device changed its access mode from writeable to
+ * read-only while in use.
+ * User action:
+ * Set the device offline, ensure that the device is configured correctly in
+ * z/VM, then set the device online again.
+ */
+
+------------------------------------------------------------------------------------
diff --git a/Documentation/kmsg/s390/dasd-diag b/Documentation/kmsg/s390/dasd-diag
deleted file mode 100644 (file)
index d276860..0000000
+++ /dev/null
@@ -1,118 +0,0 @@
-/* dasd_diag */
-
-/*?
- * Text: "%s: A 64-bit DIAG call failed\n"
- * Severity: Warning
- * Parameter:
- *   @1: bus ID of the DASD
- * Description:
- * 64-bit DIAG calls require a 64-bit z/VM version.
- * User action:
- * Use z/VM 5.2 or later or set the sysfs 'use_diag' attribute of the DASD to 0
- * to switch off DIAG.
- */
-
-/*?
- * Text: "%s: Accessing the DASD failed because of an incorrect format (rc=%d)\n"
- * Severity: Warning
- * Parameter:
- *   @1: bus ID of the DASD
- *   @2: return code
- * Description:
- * The format of the DASD is not correct.
- * User action:
- * Check the device format. For details about the return code see the
- * section about the INITIALIZE function for DIAGNOSE Code X'250'
- * in "z/VM CP Programming Services". If you cannot resolve the error, note
- * the return code and contact your support organization.
- */
-
-/*?
- * Text: "%s: New DASD with %ld byte/block, total size %ld KB%s\n"
- * Severity: Informational
- * Parameter:
- *   @1: bus ID of the DASD
- *   @2: bytes per block
- *   @3: size
- *   @4: access mode
- * Description:
- * A DASD with the indicated block size and total size has been set online.
- * If the DASD is configured as read-only to the real or virtual hardware,
- * the message includes an indication of this hardware access mode. The
- * hardware access mode is independent from the 'readonly' attribute of
- * the device in sysfs.
- * User action:
- * None.
- */
-
-/*?
- * Text: "%s: DIAG ERP failed with rc=%d\n"
- * Severity: Warning
- * Parameter:
- *   @1: bus ID of the DASD
- *   @2: return code
- * Description:
- * An error in the DIAG processing could not be recovered by the error
- * recovery procedure (ERP) of the DIAG discipline.
- * User action:
- * Note the return code, check for related I/O errors, and report this problem
- * to your support organization.
- */
-
-/*?
- * Text: "%s: DIAG initialization failed with rc=%d\n"
- * Severity: Warning
- * Parameter:
- *   @1: bus ID of the DASD
- *   @2: return code
- * Description:
- * Initializing the DASD with the DIAG discipline failed. Possible reasons for
- * this problem are that the device has a device type other than FBA or ECKD,
- * or has a block size other than one of the supported sizes:
- * 512 byte, 1024 byte, 2048 byte, or 4096 byte.
- * User action:
- * Ensure that the device can be written to and has a supported device type
- * and block size. For details about the return code see the section about
- * the INITIALIZE function for DIAGNOSE Code X'250' in "z/VM CP Programming
- * Services". If you cannot resolve the error, note the error code and contact
- * your support organization.
- */
-
-/*?
- * Text: "%s: Device type %d is not supported in DIAG mode\n"
- * Severity: Warning
- * Parameter:
- *   @1: bus ID of the DASD
- *   @2: device type
- * Description:
- * Only DASD of type FBA and ECKD are supported in DIAG mode.
- * User action:
- * Set the sysfs 'use_diag' attribute of the DASD to 0 and try again to access
- * the DASD.
- */
-
-/*?
- * Text: "Discipline %s cannot be used without z/VM\n"
- * Severity: Informational
- * Parameter:
- *   @1: discipline name
- * Description:
- * The discipline that is specified with the dasd= kernel or module parameter
- * is only available for Linux instances that run as guest operating
- * systems of the z/VM hypervisor.
- * User action:
- * Remove the unsupported discipline from the parameter string.
- */
-
-/*?
- * Text: "%s: The access mode of a DIAG device changed to read-only"
- * Severity: Warning
- * Parameter:
- *   @1: bus ID of the DASD
- * Description:
- * A device changed its access mode from writeable to
- * read-only while in use.
- * User action:
- * Set the device offline, ensure that the device is configured correctly in
- * z/VM, then set the device online again.
- */
index c801ee3..41f442e 100644 (file)
@@ -192,23 +192,8 @@ config AUDIT_ARCH
        bool
        default y
 
-config S390_SWITCH_AMODE
-       bool "Switch kernel/user addressing modes"
-       help
-         This option allows to switch the addressing modes of kernel and user
-         space. The kernel parameter switch_amode=on will enable this feature,
-         default is disabled. Enabling this (via kernel parameter) on machines
-         earlier than IBM System z9-109 EC/BC will reduce system performance.
-
-         Note that this option will also be selected by selecting the execute
-         protection option below. Enabling the execute protection via the
-         noexec kernel parameter will also switch the addressing modes,
-         independent of the switch_amode kernel parameter.
-
-
 config S390_EXEC_PROTECT
        bool "Data execute protection"
-       select S390_SWITCH_AMODE
        help
          This option allows to enable a buffer overflow protection for user
          space programs and it also selects the addressing mode option above.
index 2a54195..f4bd346 100644 (file)
@@ -142,6 +142,8 @@ struct ccw1;
 extern int ccw_device_set_options_mask(struct ccw_device *, unsigned long);
 extern int ccw_device_set_options(struct ccw_device *, unsigned long);
 extern void ccw_device_clear_options(struct ccw_device *, unsigned long);
+int ccw_device_is_pathgroup(struct ccw_device *cdev);
+int ccw_device_is_multipath(struct ccw_device *cdev);
 
 /* Allow for i/o completion notification after primary interrupt status. */
 #define CCWDEV_EARLY_NOTIFICATION      0x0001
@@ -151,6 +153,8 @@ extern void ccw_device_clear_options(struct ccw_device *, unsigned long);
 #define CCWDEV_DO_PATHGROUP             0x0004
 /* Allow forced onlining of boxed devices. */
 #define CCWDEV_ALLOW_FORCE              0x0008
+/* Try to use multipath mode. */
+#define CCWDEV_DO_MULTIPATH            0x0010
 
 extern int ccw_device_start(struct ccw_device *, struct ccw1 *,
                            unsigned long, __u8, unsigned long);
index fc7edd6..976e273 100644 (file)
@@ -36,7 +36,7 @@ static inline int init_new_context(struct task_struct *tsk,
                mm->context.has_pgste = 1;
                mm->context.alloc_pgste = 1;
        } else {
-               mm->context.noexec = s390_noexec;
+               mm->context.noexec = (user_mode == SECONDARY_SPACE_MODE);
                mm->context.has_pgste = 0;
                mm->context.alloc_pgste = 0;
        }
@@ -58,7 +58,7 @@ static inline void update_mm(struct mm_struct *mm, struct task_struct *tsk)
        pgd_t *pgd = mm->pgd;
 
        S390_lowcore.user_asce = mm->context.asce_bits | __pa(pgd);
-       if (switch_amode) {
+       if (user_mode != HOME_SPACE_MODE) {
                /* Load primary space page table origin. */
                pgd = mm->context.noexec ? get_shadow_table(pgd) : pgd;
                S390_lowcore.user_exec_asce = mm->context.asce_bits | __pa(pgd);
index ddad590..68940d0 100644 (file)
@@ -143,7 +143,8 @@ static inline pgd_t *pgd_alloc(struct mm_struct *mm)
        spin_lock_init(&mm->context.list_lock);
        INIT_LIST_HEAD(&mm->context.crst_list);
        INIT_LIST_HEAD(&mm->context.pgtable_list);
-       return (pgd_t *) crst_table_alloc(mm, s390_noexec);
+       return (pgd_t *)
+               crst_table_alloc(mm, user_mode == SECONDARY_SPACE_MODE);
 }
 #define pgd_free(mm, pgd) crst_table_free(mm, (unsigned long *) pgd)
 
index e37478e..52a779c 100644 (file)
@@ -49,17 +49,12 @@ extern unsigned long memory_end;
 
 void detect_memory_layout(struct mem_chunk chunk[]);
 
-#ifdef CONFIG_S390_SWITCH_AMODE
-extern unsigned int switch_amode;
-#else
-#define switch_amode   (0)
-#endif
-
-#ifdef CONFIG_S390_EXEC_PROTECT
-extern unsigned int s390_noexec;
-#else
-#define s390_noexec    (0)
-#endif
+#define PRIMARY_SPACE_MODE     0
+#define ACCESS_REGISTER_MODE   1
+#define SECONDARY_SPACE_MODE   2
+#define HOME_SPACE_MODE                3
+
+extern unsigned int user_mode;
 
 /*
  * Machine features detected in head.S
index 8377e91..cbf0a87 100644 (file)
@@ -93,6 +93,8 @@ extern struct uaccess_ops uaccess_mvcos;
 extern struct uaccess_ops uaccess_mvcos_switch;
 extern struct uaccess_ops uaccess_pt;
 
+extern int __handle_fault(unsigned long, unsigned long, int);
+
 static inline int __put_user_fn(size_t size, void __user *ptr, void *x)
 {
        size = uaccess.copy_to_user_small(size, ptr, x);
index 6a25080..d984a2a 100644 (file)
@@ -83,6 +83,8 @@ startup_continue:
        slr     %r0,%r0                 # set cpuid to zero
        sigp    %r1,%r0,0x12            # switch to esame mode
        sam64                           # switch to 64 bit mode
+       llgfr   %r13,%r13               # clear high-order half of base reg
+       lmh     %r0,%r15,.Lzero64-.LPG1(%r13)   # clear high-order half
        lctlg   %c0,%c15,.Lctl-.LPG1(%r13)      # load control registers
        lg      %r12,.Lparmaddr-.LPG1(%r13)     # pointer to parameter area
                                        # move IPL device to lowcore
@@ -127,6 +129,7 @@ startup_continue:
 .L4malign:.quad 0xffffffffffc00000
 .Lscan2g:.quad 0x80000000 + 0x20000 - 8        # 2GB + 128K - 8
 .Lnop: .long   0x07000700
+.Lzero64:.fill 16,4,0x0
 #ifdef CONFIG_ZFCPDUMP
 .Lcurrent_cpu:
        .long 0x0
index 061479f..0663287 100644 (file)
@@ -305,9 +305,8 @@ static int __init early_parse_mem(char *p)
 }
 early_param("mem", early_parse_mem);
 
-#ifdef CONFIG_S390_SWITCH_AMODE
-unsigned int switch_amode = 0;
-EXPORT_SYMBOL_GPL(switch_amode);
+unsigned int user_mode = HOME_SPACE_MODE;
+EXPORT_SYMBOL_GPL(user_mode);
 
 static int set_amode_and_uaccess(unsigned long user_amode,
                                 unsigned long user32_amode)
@@ -340,23 +339,29 @@ static int set_amode_and_uaccess(unsigned long user_amode,
  */
 static int __init early_parse_switch_amode(char *p)
 {
-       switch_amode = 1;
+       if (user_mode != SECONDARY_SPACE_MODE)
+               user_mode = PRIMARY_SPACE_MODE;
        return 0;
 }
 early_param("switch_amode", early_parse_switch_amode);
 
-#else /* CONFIG_S390_SWITCH_AMODE */
-static inline int set_amode_and_uaccess(unsigned long user_amode,
-                                       unsigned long user32_amode)
+static int __init early_parse_user_mode(char *p)
 {
+       if (p && strcmp(p, "primary") == 0)
+               user_mode = PRIMARY_SPACE_MODE;
+#ifdef CONFIG_S390_EXEC_PROTECT
+       else if (p && strcmp(p, "secondary") == 0)
+               user_mode = SECONDARY_SPACE_MODE;
+#endif
+       else if (!p || strcmp(p, "home") == 0)
+               user_mode = HOME_SPACE_MODE;
+       else
+               return 1;
        return 0;
 }
-#endif /* CONFIG_S390_SWITCH_AMODE */
+early_param("user_mode", early_parse_user_mode);
 
 #ifdef CONFIG_S390_EXEC_PROTECT
-unsigned int s390_noexec = 0;
-EXPORT_SYMBOL_GPL(s390_noexec);
-
 /*
  * Enable execute protection?
  */
@@ -364,8 +369,7 @@ static int __init early_parse_noexec(char *p)
 {
        if (!strncmp(p, "off", 3))
                return 0;
-       switch_amode = 1;
-       s390_noexec = 1;
+       user_mode = SECONDARY_SPACE_MODE;
        return 0;
 }
 early_param("noexec", early_parse_noexec);
@@ -373,7 +377,7 @@ early_param("noexec", early_parse_noexec);
 
 static void setup_addressing_mode(void)
 {
-       if (s390_noexec) {
+       if (user_mode == SECONDARY_SPACE_MODE) {
                if (set_amode_and_uaccess(PSW_ASC_SECONDARY,
                                          PSW32_ASC_SECONDARY))
                        pr_info("Execute protection active, "
@@ -381,7 +385,7 @@ static void setup_addressing_mode(void)
                else
                        pr_info("Execute protection active, "
                                "mvcos not available\n");
-       } else if (switch_amode) {
+       } else if (user_mode == PRIMARY_SPACE_MODE) {
                if (set_amode_and_uaccess(PSW_ASC_PRIMARY, PSW32_ASC_PRIMARY))
                        pr_info("Address spaces switched, "
                                "mvcos available\n");
@@ -411,7 +415,7 @@ setup_lowcore(void)
        lc->restart_psw.mask = PSW_BASE_BITS | PSW_DEFAULT_KEY;
        lc->restart_psw.addr =
                PSW_ADDR_AMODE | (unsigned long) restart_int_handler;
-       if (switch_amode)
+       if (user_mode != HOME_SPACE_MODE)
                lc->restart_psw.mask |= PSW_ASC_HOME;
        lc->external_new_psw.mask = psw_kernel_bits;
        lc->external_new_psw.addr =
index adfb32a..5f99e66 100644 (file)
@@ -86,7 +86,8 @@ static void vdso_init_data(struct vdso_data *vd)
        unsigned int facility_list;
 
        facility_list = stfl();
-       vd->ectg_available = switch_amode && (facility_list & 1);
+       vd->ectg_available =
+               user_mode != HOME_SPACE_MODE && (facility_list & 1);
 }
 
 #ifdef CONFIG_64BIT
@@ -114,7 +115,7 @@ int vdso_alloc_per_cpu(int cpu, struct _lowcore *lowcore)
 
        lowcore->vdso_per_cpu_data = __LC_PASTE;
 
-       if (!switch_amode || !vdso_enabled)
+       if (user_mode == HOME_SPACE_MODE || !vdso_enabled)
                return 0;
 
        segment_table = __get_free_pages(GFP_KERNEL, SEGMENT_ORDER);
@@ -160,7 +161,7 @@ void vdso_free_per_cpu(int cpu, struct _lowcore *lowcore)
        unsigned long segment_table, page_table, page_frame;
        u32 *psal, *aste;
 
-       if (!switch_amode || !vdso_enabled)
+       if (user_mode == HOME_SPACE_MODE || !vdso_enabled)
                return;
 
        psal = (u32 *)(addr_t) lowcore->paste[4];
@@ -184,7 +185,7 @@ static void __vdso_init_cr5(void *dummy)
 
 static void vdso_init_cr5(void)
 {
-       if (switch_amode && vdso_enabled)
+       if (user_mode != HOME_SPACE_MODE && vdso_enabled)
                on_each_cpu(__vdso_init_cr5, NULL, 1);
 }
 #endif /* CONFIG_64BIT */
index bf164fc..6ee55ae 100644 (file)
@@ -20,7 +20,6 @@ config KVM
        depends on HAVE_KVM && EXPERIMENTAL
        select PREEMPT_NOTIFIERS
        select ANON_INODES
-       select S390_SWITCH_AMODE
        ---help---
          Support hosting paravirtualized guest machines using the SIE
          virtualization capability on the mainframe. This should work
index 58da3f4..60455f1 100644 (file)
@@ -162,7 +162,6 @@ static size_t clear_user_mvcos(size_t size, void __user *to)
        return size;
 }
 
-#ifdef CONFIG_S390_SWITCH_AMODE
 static size_t strnlen_user_mvcos(size_t count, const char __user *src)
 {
        char buf[256];
@@ -200,7 +199,6 @@ static size_t strncpy_from_user_mvcos(size_t count, const char __user *src,
        } while ((len_str == len) && (done < count));
        return done;
 }
-#endif /* CONFIG_S390_SWITCH_AMODE */
 
 struct uaccess_ops uaccess_mvcos = {
        .copy_from_user = copy_from_user_mvcos_check,
@@ -215,7 +213,6 @@ struct uaccess_ops uaccess_mvcos = {
        .futex_atomic_cmpxchg = futex_atomic_cmpxchg_std,
 };
 
-#ifdef CONFIG_S390_SWITCH_AMODE
 struct uaccess_ops uaccess_mvcos_switch = {
        .copy_from_user = copy_from_user_mvcos,
        .copy_from_user_small = copy_from_user_mvcos,
@@ -228,4 +225,3 @@ struct uaccess_ops uaccess_mvcos_switch = {
        .futex_atomic_op = futex_atomic_op_pt,
        .futex_atomic_cmpxchg = futex_atomic_cmpxchg_pt,
 };
-#endif
index cb5d59e..404f2de 100644 (file)
@@ -23,86 +23,21 @@ static inline pte_t *follow_table(struct mm_struct *mm, unsigned long addr)
 
        pgd = pgd_offset(mm, addr);
        if (pgd_none(*pgd) || unlikely(pgd_bad(*pgd)))
-               return NULL;
+               return (pte_t *) 0x3a;
 
        pud = pud_offset(pgd, addr);
        if (pud_none(*pud) || unlikely(pud_bad(*pud)))
-               return NULL;
+               return (pte_t *) 0x3b;
 
        pmd = pmd_offset(pud, addr);
        if (pmd_none(*pmd) || unlikely(pmd_bad(*pmd)))
-               return NULL;
+               return (pte_t *) 0x10;
 
        return pte_offset_map(pmd, addr);
 }
 
-static int __handle_fault(struct mm_struct *mm, unsigned long address,
-                         int write_access)
-{
-       struct vm_area_struct *vma;
-       int ret = -EFAULT;
-       int fault;
-
-       if (in_atomic())
-               return ret;
-       down_read(&mm->mmap_sem);
-       vma = find_vma(mm, address);
-       if (unlikely(!vma))
-               goto out;
-       if (unlikely(vma->vm_start > address)) {
-               if (!(vma->vm_flags & VM_GROWSDOWN))
-                       goto out;
-               if (expand_stack(vma, address))
-                       goto out;
-       }
-
-       if (!write_access) {
-               /* page not present, check vm flags */
-               if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE)))
-                       goto out;
-       } else {
-               if (!(vma->vm_flags & VM_WRITE))
-                       goto out;
-       }
-
-survive:
-       fault = handle_mm_fault(mm, vma, address, write_access ? FAULT_FLAG_WRITE : 0);
-       if (unlikely(fault & VM_FAULT_ERROR)) {
-               if (fault & VM_FAULT_OOM)
-                       goto out_of_memory;
-               else if (fault & VM_FAULT_SIGBUS)
-                       goto out_sigbus;
-               BUG();
-       }
-       if (fault & VM_FAULT_MAJOR)
-               current->maj_flt++;
-       else
-               current->min_flt++;
-       ret = 0;
-out:
-       up_read(&mm->mmap_sem);
-       return ret;
-
-out_of_memory:
-       up_read(&mm->mmap_sem);
-       if (is_global_init(current)) {
-               yield();
-               down_read(&mm->mmap_sem);
-               goto survive;
-       }
-       printk("VM: killing process %s\n", current->comm);
-       return ret;
-
-out_sigbus:
-       up_read(&mm->mmap_sem);
-       current->thread.prot_addr = address;
-       current->thread.trap_no = 0x11;
-       force_sig(SIGBUS, current);
-       return ret;
-}
-
-static size_t __user_copy_pt(unsigned long uaddr, void *kptr,
-                            size_t n, int write_user)
+static __always_inline size_t __user_copy_pt(unsigned long uaddr, void *kptr,
+                                            size_t n, int write_user)
 {
        struct mm_struct *mm = current->mm;
        unsigned long offset, pfn, done, size;
@@ -114,12 +49,17 @@ retry:
        spin_lock(&mm->page_table_lock);
        do {
                pte = follow_table(mm, uaddr);
-               if (!pte || !pte_present(*pte) ||
-                   (write_user && !pte_write(*pte)))
+               if ((unsigned long) pte < 0x1000)
                        goto fault;
+               if (!pte_present(*pte)) {
+                       pte = (pte_t *) 0x11;
+                       goto fault;
+               } else if (write_user && !pte_write(*pte)) {
+                       pte = (pte_t *) 0x04;
+                       goto fault;
+               }
 
                pfn = pte_pfn(*pte);
-
                offset = uaddr & (PAGE_SIZE - 1);
                size = min(n - done, PAGE_SIZE - offset);
                if (write_user) {
@@ -137,7 +77,7 @@ retry:
        return n - done;
 fault:
        spin_unlock(&mm->page_table_lock);
-       if (__handle_fault(mm, uaddr, write_user))
+       if (__handle_fault(uaddr, (unsigned long) pte, write_user))
                return n - done;
        goto retry;
 }
@@ -146,30 +86,31 @@ fault:
  * Do DAT for user address by page table walk, return kernel address.
  * This function needs to be called with current->mm->page_table_lock held.
  */
-static unsigned long __dat_user_addr(unsigned long uaddr)
+static __always_inline unsigned long __dat_user_addr(unsigned long uaddr)
 {
        struct mm_struct *mm = current->mm;
-       unsigned long pfn, ret;
+       unsigned long pfn;
        pte_t *pte;
        int rc;
 
-       ret = 0;
 retry:
        pte = follow_table(mm, uaddr);
-       if (!pte || !pte_present(*pte))
+       if ((unsigned long) pte < 0x1000)
                goto fault;
+       if (!pte_present(*pte)) {
+               pte = (pte_t *) 0x11;
+               goto fault;
+       }
 
        pfn = pte_pfn(*pte);
-       ret = (pfn << PAGE_SHIFT) + (uaddr & (PAGE_SIZE - 1));
-out:
-       return ret;
+       return (pfn << PAGE_SHIFT) + (uaddr & (PAGE_SIZE - 1));
 fault:
        spin_unlock(&mm->page_table_lock);
-       rc = __handle_fault(mm, uaddr, 0);
+       rc = __handle_fault(uaddr, (unsigned long) pte, 0);
        spin_lock(&mm->page_table_lock);
-       if (rc)
-               goto out;
-       goto retry;
+       if (!rc)
+               goto retry;
+       return 0;
 }
 
 size_t copy_from_user_pt(size_t n, const void __user *from, void *to)
@@ -234,8 +175,12 @@ retry:
        spin_lock(&mm->page_table_lock);
        do {
                pte = follow_table(mm, uaddr);
-               if (!pte || !pte_present(*pte))
+               if ((unsigned long) pte < 0x1000)
+                       goto fault;
+               if (!pte_present(*pte)) {
+                       pte = (pte_t *) 0x11;
                        goto fault;
+               }
 
                pfn = pte_pfn(*pte);
                offset = uaddr & (PAGE_SIZE-1);
@@ -249,9 +194,8 @@ retry:
        return done + 1;
 fault:
        spin_unlock(&mm->page_table_lock);
-       if (__handle_fault(mm, uaddr, 0)) {
+       if (__handle_fault(uaddr, (unsigned long) pte, 0))
                return 0;
-       }
        goto retry;
 }
 
@@ -284,7 +228,7 @@ static size_t copy_in_user_pt(size_t n, void __user *to,
 {
        struct mm_struct *mm = current->mm;
        unsigned long offset_from, offset_to, offset_max, pfn_from, pfn_to,
-                     uaddr, done, size;
+                     uaddr, done, size, error_code;
        unsigned long uaddr_from = (unsigned long) from;
        unsigned long uaddr_to = (unsigned long) to;
        pte_t *pte_from, *pte_to;
@@ -298,17 +242,28 @@ static size_t copy_in_user_pt(size_t n, void __user *to,
 retry:
        spin_lock(&mm->page_table_lock);
        do {
+               write_user = 0;
+               uaddr = uaddr_from;
                pte_from = follow_table(mm, uaddr_from);
-               if (!pte_from || !pte_present(*pte_from)) {
-                       uaddr = uaddr_from;
-                       write_user = 0;
+               error_code = (unsigned long) pte_from;
+               if (error_code < 0x1000)
+                       goto fault;
+               if (!pte_present(*pte_from)) {
+                       error_code = 0x11;
                        goto fault;
                }
 
+               write_user = 1;
+               uaddr = uaddr_to;
                pte_to = follow_table(mm, uaddr_to);
-               if (!pte_to || !pte_present(*pte_to) || !pte_write(*pte_to)) {
-                       uaddr = uaddr_to;
-                       write_user = 1;
+               error_code = (unsigned long) pte_to;
+               if (error_code < 0x1000)
+                       goto fault;
+               if (!pte_present(*pte_to)) {
+                       error_code = 0x11;
+                       goto fault;
+               } else if (!pte_write(*pte_to)) {
+                       error_code = 0x04;
                        goto fault;
                }
 
@@ -329,7 +284,7 @@ retry:
        return n - done;
 fault:
        spin_unlock(&mm->page_table_lock);
-       if (__handle_fault(mm, uaddr, write_user))
+       if (__handle_fault(uaddr, error_code, write_user))
                return n - done;
        goto retry;
 }
index 6d50746..fc102e7 100644 (file)
 #include <asm/pgtable.h>
 #include <asm/s390_ext.h>
 #include <asm/mmu_context.h>
+#include <asm/compat.h>
 #include "../kernel/entry.h"
 
 #ifndef CONFIG_64BIT
 #define __FAIL_ADDR_MASK 0x7ffff000
-#define __FIXUP_MASK 0x7fffffff
 #define __SUBCODE_MASK 0x0200
 #define __PF_RES_FIELD 0ULL
 #else /* CONFIG_64BIT */
 #define __FAIL_ADDR_MASK -4096L
-#define __FIXUP_MASK ~0L
 #define __SUBCODE_MASK 0x0600
 #define __PF_RES_FIELD 0x8000000000000000ULL
 #endif /* CONFIG_64BIT */
 extern int sysctl_userprocess_debug;
 #endif
 
-#ifdef CONFIG_KPROBES
-static inline int notify_page_fault(struct pt_regs *regs, long err)
+#define VM_FAULT_BADCONTEXT    0x010000
+#define VM_FAULT_BADMAP                0x020000
+#define VM_FAULT_BADACCESS     0x040000
+
+static inline int notify_page_fault(struct pt_regs *regs)
 {
        int ret = 0;
 
+#ifdef CONFIG_KPROBES
        /* kprobe_running() needs smp_processor_id() */
        if (!user_mode(regs)) {
                preempt_disable();
@@ -64,15 +67,9 @@ static inline int notify_page_fault(struct pt_regs *regs, long err)
                        ret = 1;
                preempt_enable();
        }
-
+#endif
        return ret;
 }
-#else
-static inline int notify_page_fault(struct pt_regs *regs, long err)
-{
-       return 0;
-}
-#endif
 
 
 /*
@@ -100,57 +97,50 @@ void bust_spinlocks(int yes)
 
 /*
  * Returns the address space associated with the fault.
- * Returns 0 for kernel space, 1 for user space and
- * 2 for code execution in user space with noexec=on.
+ * Returns 0 for kernel space and 1 for user space.
  */
-static inline int check_space(struct task_struct *tsk)
+static inline int user_space_fault(unsigned long trans_exc_code)
 {
        /*
-        * The lowest two bits of S390_lowcore.trans_exc_code
-        * indicate which paging table was used.
+        * The lowest two bits of the translation exception
+        * identification indicate which paging table was used.
         */
-       int desc = S390_lowcore.trans_exc_code & 3;
-
-       if (desc == 3)  /* Home Segment Table Descriptor */
-               return switch_amode == 0;
-       if (desc == 2)  /* Secondary Segment Table Descriptor */
-               return tsk->thread.mm_segment.ar4;
-#ifdef CONFIG_S390_SWITCH_AMODE
-       if (unlikely(desc == 1)) { /* STD determined via access register */
-               /* %a0 always indicates primary space. */
-               if (S390_lowcore.exc_access_id != 0) {
-                       save_access_regs(tsk->thread.acrs);
-                       /*
-                        * An alet of 0 indicates primary space.
-                        * An alet of 1 indicates secondary space.
-                        * Any other alet values generate an
-                        * alen-translation exception.
-                        */
-                       if (tsk->thread.acrs[S390_lowcore.exc_access_id])
-                               return tsk->thread.mm_segment.ar4;
-               }
-       }
-#endif
-       /* Primary Segment Table Descriptor */
-       return switch_amode << s390_noexec;
+       trans_exc_code &= 3;
+       if (trans_exc_code == 2)
+               /* Access via secondary space, set_fs setting decides */
+               return current->thread.mm_segment.ar4;
+       if (user_mode == HOME_SPACE_MODE)
+               /* User space if the access has been done via home space. */
+               return trans_exc_code == 3;
+       /*
+        * If the user space is not the home space the kernel runs in home
+        * space. Access via secondary space has already been covered,
+        * access via primary space or access register is from user space
+        * and access via home space is from the kernel.
+        */
+       return trans_exc_code != 3;
 }
 
 /*
  * Send SIGSEGV to task.  This is an external routine
  * to keep the stack usage of do_page_fault small.
  */
-static void do_sigsegv(struct pt_regs *regs, unsigned long error_code,
-                      int si_code, unsigned long address)
+static noinline void do_sigsegv(struct pt_regs *regs, long int_code,
+                               int si_code, unsigned long trans_exc_code)
 {
        struct siginfo si;
+       unsigned long address;
 
+       address = trans_exc_code & __FAIL_ADDR_MASK;
+       current->thread.prot_addr = address;
+       current->thread.trap_no = int_code;
 #if defined(CONFIG_SYSCTL) || defined(CONFIG_PROCESS_DEBUG)
 #if defined(CONFIG_SYSCTL)
        if (sysctl_userprocess_debug)
 #endif
        {
                printk("User process fault: interruption code 0x%lX\n",
-                      error_code);
+                      int_code);
                printk("failing address: %lX\n", address);
                show_regs(regs);
        }
@@ -161,13 +151,14 @@ static void do_sigsegv(struct pt_regs *regs, unsigned long error_code,
        force_sig_info(SIGSEGV, &si, current);
 }
 
-static void do_no_context(struct pt_regs *regs, unsigned long error_code,
-                         unsigned long address)
+static noinline void do_no_context(struct pt_regs *regs, long int_code,
+                                  unsigned long trans_exc_code)
 {
        const struct exception_table_entry *fixup;
+       unsigned long address;
 
        /* Are we prepared to handle this kernel fault?  */
-       fixup = search_exception_tables(regs->psw.addr & __FIXUP_MASK);
+       fixup = search_exception_tables(regs->psw.addr & PSW_ADDR_INSN);
        if (fixup) {
                regs->psw.addr = fixup->fixup | PSW_ADDR_AMODE;
                return;
@@ -177,129 +168,149 @@ static void do_no_context(struct pt_regs *regs, unsigned long error_code,
         * Oops. The kernel tried to access some bad page. We'll have to
         * terminate things with extreme prejudice.
         */
-       if (check_space(current) == 0)
+       address = trans_exc_code & __FAIL_ADDR_MASK;
+       if (!user_space_fault(trans_exc_code))
                printk(KERN_ALERT "Unable to handle kernel pointer dereference"
                       " at virtual kernel address %p\n", (void *)address);
        else
                printk(KERN_ALERT "Unable to handle kernel paging request"
                       " at virtual user address %p\n", (void *)address);
 
-       die("Oops", regs, error_code);
+       die("Oops", regs, int_code);
        do_exit(SIGKILL);
 }
 
-static void do_low_address(struct pt_regs *regs, unsigned long error_code)
+static noinline void do_low_address(struct pt_regs *regs, long int_code,
+                                   unsigned long trans_exc_code)
 {
        /* Low-address protection hit in kernel mode means
           NULL pointer write access in kernel mode.  */
        if (regs->psw.mask & PSW_MASK_PSTATE) {
                /* Low-address protection hit in user mode 'cannot happen'. */
-               die ("Low-address protection", regs, error_code);
+               die ("Low-address protection", regs, int_code);
                do_exit(SIGKILL);
        }
 
-       do_no_context(regs, error_code, 0);
+       do_no_context(regs, int_code, trans_exc_code);
 }
 
-static void do_sigbus(struct pt_regs *regs, unsigned long error_code,
-                     unsigned long address)
+static noinline void do_sigbus(struct pt_regs *regs, long int_code,
+                              unsigned long trans_exc_code)
 {
        struct task_struct *tsk = current;
-       struct mm_struct *mm = tsk->mm;
 
-       up_read(&mm->mmap_sem);
        /*
         * Send a sigbus, regardless of whether we were in kernel
         * or user mode.
         */
-       tsk->thread.prot_addr = address;
-       tsk->thread.trap_no = error_code;
+       tsk->thread.prot_addr = trans_exc_code & __FAIL_ADDR_MASK;
+       tsk->thread.trap_no = int_code;
        force_sig(SIGBUS, tsk);
-
-       /* Kernel mode? Handle exceptions or die */
-       if (!(regs->psw.mask & PSW_MASK_PSTATE))
-               do_no_context(regs, error_code, address);
 }
 
 #ifdef CONFIG_S390_EXEC_PROTECT
-static int signal_return(struct mm_struct *mm, struct pt_regs *regs,
-                        unsigned long address, unsigned long error_code)
+static noinline int signal_return(struct pt_regs *regs, long int_code,
+                                 unsigned long trans_exc_code)
 {
        u16 instruction;
        int rc;
-#ifdef CONFIG_COMPAT
-       int compat;
-#endif
 
-       pagefault_disable();
        rc = __get_user(instruction, (u16 __user *) regs->psw.addr);
-       pagefault_enable();
-       if (rc)
-               return -EFAULT;
 
-       up_read(&mm->mmap_sem);
-       clear_tsk_thread_flag(current, TIF_SINGLE_STEP);
-#ifdef CONFIG_COMPAT
-       compat = is_compat_task();
-       if (compat && instruction == 0x0a77)
-               sys32_sigreturn();
-       else if (compat && instruction == 0x0aad)
-               sys32_rt_sigreturn();
-       else
-#endif
-       if (instruction == 0x0a77)
-               sys_sigreturn();
-       else if (instruction == 0x0aad)
-               sys_rt_sigreturn();
-       else {
-               current->thread.prot_addr = address;
-               current->thread.trap_no = error_code;
-               do_sigsegv(regs, error_code, SEGV_MAPERR, address);
-       }
+       if (!rc && instruction == 0x0a77) {
+               clear_tsk_thread_flag(current, TIF_SINGLE_STEP);
+               if (is_compat_task())
+                       sys32_sigreturn();
+               else
+                       sys_sigreturn();
+       } else if (!rc && instruction == 0x0aad) {
+               clear_tsk_thread_flag(current, TIF_SINGLE_STEP);
+               if (is_compat_task())
+                       sys32_rt_sigreturn();
+               else
+                       sys_rt_sigreturn();
+       } else
+               do_sigsegv(regs, int_code, SEGV_MAPERR, trans_exc_code);
        return 0;
 }
 #endif /* CONFIG_S390_EXEC_PROTECT */
 
+static noinline void do_fault_error(struct pt_regs *regs, long int_code,
+                                   unsigned long trans_exc_code, int fault)
+{
+       int si_code;
+
+       switch (fault) {
+       case VM_FAULT_BADACCESS:
+#ifdef CONFIG_S390_EXEC_PROTECT
+               if ((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_SECONDARY &&
+                   (trans_exc_code & 3) == 0) {
+                       signal_return(regs, int_code, trans_exc_code);
+                       break;
+               }
+#endif /* CONFIG_S390_EXEC_PROTECT */
+       case VM_FAULT_BADMAP:
+               /* Bad memory access. Check if it is kernel or user space. */
+               if (regs->psw.mask & PSW_MASK_PSTATE) {
+                       /* User mode accesses just cause a SIGSEGV */
+                       si_code = (fault == VM_FAULT_BADMAP) ?
+                               SEGV_MAPERR : SEGV_ACCERR;
+                       do_sigsegv(regs, int_code, si_code, trans_exc_code);
+                       return;
+               }
+       case VM_FAULT_BADCONTEXT:
+               do_no_context(regs, int_code, trans_exc_code);
+               break;
+       default: /* fault & VM_FAULT_ERROR */
+               if (fault & VM_FAULT_OOM)
+                       pagefault_out_of_memory();
+               else if (fault & VM_FAULT_SIGBUS) {
+                       do_sigbus(regs, int_code, trans_exc_code);
+                       /* Kernel mode? Handle exceptions or die */
+                       if (!(regs->psw.mask & PSW_MASK_PSTATE))
+                               do_no_context(regs, int_code, trans_exc_code);
+               } else
+                       BUG();
+               break;
+       }
+}
+
 /*
  * This routine handles page faults.  It determines the address,
  * and the problem, and then passes it off to one of the appropriate
  * routines.
  *
- * error_code:
+ * interruption code (int_code):
  *   04       Protection           ->  Write-Protection  (suprression)
  *   10       Segment translation  ->  Not present       (nullification)
  *   11       Page translation     ->  Not present       (nullification)
  *   3b       Region third trans.  ->  Not present       (nullification)
  */
-static inline void
-do_exception(struct pt_regs *regs, unsigned long error_code, int write)
+static inline int do_exception(struct pt_regs *regs, int access,
+                              unsigned long trans_exc_code)
 {
        struct task_struct *tsk;
        struct mm_struct *mm;
        struct vm_area_struct *vma;
        unsigned long address;
-       int space;
-       int si_code;
        int fault;
 
-       if (notify_page_fault(regs, error_code))
-               return;
+       if (notify_page_fault(regs))
+               return 0;
 
        tsk = current;
        mm = tsk->mm;
 
-       /* get the failing address and the affected space */
-       address = S390_lowcore.trans_exc_code & __FAIL_ADDR_MASK;
-       space = check_space(tsk);
-
        /*
         * Verify that the fault happened in user space, that
         * we are not in an interrupt and that there is a 
         * user context.
         */
-       if (unlikely(space == 0 || in_atomic() || !mm))
-               goto no_context;
+       fault = VM_FAULT_BADCONTEXT;
+       if (unlikely(!user_space_fault(trans_exc_code) || in_atomic() || !mm))
+               goto out;
 
+       address = trans_exc_code & __FAIL_ADDR_MASK;
        /*
         * When we get here, the fault happened in the current
         * task's user address space, so we can switch on the
@@ -309,42 +320,26 @@ do_exception(struct pt_regs *regs, unsigned long error_code, int write)
        perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS, 1, 0, regs, address);
        down_read(&mm->mmap_sem);
 
-       si_code = SEGV_MAPERR;
+       fault = VM_FAULT_BADMAP;
        vma = find_vma(mm, address);
        if (!vma)
-               goto bad_area;
-
-#ifdef CONFIG_S390_EXEC_PROTECT
-       if (unlikely((space == 2) && !(vma->vm_flags & VM_EXEC)))
-               if (!signal_return(mm, regs, address, error_code))
-                       /*
-                        * signal_return() has done an up_read(&mm->mmap_sem)
-                        * if it returns 0.
-                        */
-                       return;
-#endif
+               goto out_up;
 
-       if (vma->vm_start <= address)
-               goto good_area;
-       if (!(vma->vm_flags & VM_GROWSDOWN))
-               goto bad_area;
-       if (expand_stack(vma, address))
-               goto bad_area;
-/*
- * Ok, we have a good vm_area for this memory access, so
- * we can handle it..
- */
-good_area:
-       si_code = SEGV_ACCERR;
-       if (!write) {
-               /* page not present, check vm flags */
-               if (!(vma->vm_flags & (VM_READ | VM_EXEC | VM_WRITE)))
-                       goto bad_area;
-       } else {
-               if (!(vma->vm_flags & VM_WRITE))
-                       goto bad_area;
+       if (unlikely(vma->vm_start > address)) {
+               if (!(vma->vm_flags & VM_GROWSDOWN))
+                       goto out_up;
+               if (expand_stack(vma, address))
+                       goto out_up;
        }
 
+       /*
+        * Ok, we have a good vm_area for this memory access, so
+        * we can handle it..
+        */
+       fault = VM_FAULT_BADACCESS;
+       if (unlikely(!(vma->vm_flags & access)))
+               goto out_up;
+
        if (is_vm_hugetlb_page(vma))
                address &= HPAGE_MASK;
        /*
@@ -352,18 +347,11 @@ good_area:
         * make sure we exit gracefully rather than endlessly redo
         * the fault.
         */
-       fault = handle_mm_fault(mm, vma, address, write ? FAULT_FLAG_WRITE : 0);
-       if (unlikely(fault & VM_FAULT_ERROR)) {
-               if (fault & VM_FAULT_OOM) {
-                       up_read(&mm->mmap_sem);
-                       pagefault_out_of_memory();
-                       return;
-               } else if (fault & VM_FAULT_SIGBUS) {
-                       do_sigbus(regs, error_code, address);
-                       return;
-               }
-               BUG();
-       }
+       fault = handle_mm_fault(mm, vma, address,
+                               (access == VM_WRITE) ? FAULT_FLAG_WRITE : 0);
+       if (unlikely(fault & VM_FAULT_ERROR))
+               goto out_up;
+
        if (fault & VM_FAULT_MAJOR) {
                tsk->maj_flt++;
                perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MAJ, 1, 0,
@@ -373,74 +361,69 @@ good_area:
                perf_sw_event(PERF_COUNT_SW_PAGE_FAULTS_MIN, 1, 0,
                                     regs, address);
        }
-        up_read(&mm->mmap_sem);
        /*
         * The instruction that caused the program check will
         * be repeated. Don't signal single step via SIGTRAP.
         */
        clear_tsk_thread_flag(tsk, TIF_SINGLE_STEP);
-        return;
-
-/*
- * Something tried to access memory that isn't in our memory map..
- * Fix it, but check if it's kernel or user first..
- */
-bad_area:
+       fault = 0;
+out_up:
        up_read(&mm->mmap_sem);
-
-       /* User mode accesses just cause a SIGSEGV */
-       if (regs->psw.mask & PSW_MASK_PSTATE) {
-               tsk->thread.prot_addr = address;
-               tsk->thread.trap_no = error_code;
-               do_sigsegv(regs, error_code, si_code, address);
-               return;
-       }
-
-no_context:
-       do_no_context(regs, error_code, address);
+out:
+       return fault;
 }
 
-void __kprobes do_protection_exception(struct pt_regs *regs,
-                                      long error_code)
+void __kprobes do_protection_exception(struct pt_regs *regs, long int_code)
 {
+       unsigned long trans_exc_code = S390_lowcore.trans_exc_code;
+       int fault;
+
        /* Protection exception is supressing, decrement psw address. */
-       regs->psw.addr -= (error_code >> 16);
+       regs->psw.addr -= (int_code >> 16);
        /*
         * Check for low-address protection.  This needs to be treated
         * as a special case because the translation exception code
         * field is not guaranteed to contain valid data in this case.
         */
-       if (unlikely(!(S390_lowcore.trans_exc_code & 4))) {
-               do_low_address(regs, error_code);
+       if (unlikely(!(trans_exc_code & 4))) {
+               do_low_address(regs, int_code, trans_exc_code);
                return;
        }
-       do_exception(regs, 4, 1);
+       fault = do_exception(regs, VM_WRITE, trans_exc_code);
+       if (unlikely(fault))
+               do_fault_error(regs, 4, trans_exc_code, fault);
 }
 
-void __kprobes do_dat_exception(struct pt_regs *regs, long error_code)
+void __kprobes do_dat_exception(struct pt_regs *regs, long int_code)
 {
-       do_exception(regs, error_code & 0xff, 0);
+       unsigned long trans_exc_code = S390_lowcore.trans_exc_code;
+       int access, fault;
+
+       access = VM_READ | VM_EXEC | VM_WRITE;
+#ifdef CONFIG_S390_EXEC_PROTECT
+       if ((regs->psw.mask & PSW_MASK_ASC) == PSW_ASC_SECONDARY &&
+           (trans_exc_code & 3) == 0)
+               access = VM_EXEC;
+#endif
+       fault = do_exception(regs, access, trans_exc_code);
+       if (unlikely(fault))
+               do_fault_error(regs, int_code & 255, trans_exc_code, fault);
 }
 
 #ifdef CONFIG_64BIT
-void __kprobes do_asce_exception(struct pt_regs *regs, unsigned long error_code)
+void __kprobes do_asce_exception(struct pt_regs *regs, long int_code)
 {
-       struct mm_struct *mm;
+       unsigned long trans_exc_code = S390_lowcore.trans_exc_code;
+       struct mm_struct *mm = current->mm;
        struct vm_area_struct *vma;
-       unsigned long address;
-       int space;
-
-       mm = current->mm;
-       address = S390_lowcore.trans_exc_code & __FAIL_ADDR_MASK;
-       space = check_space(current);
 
-       if (unlikely(space == 0 || in_atomic() || !mm))
+       if (unlikely(!user_space_fault(trans_exc_code) || in_atomic() || !mm))
                goto no_context;
 
        local_irq_enable();
 
        down_read(&mm->mmap_sem);
-       vma = find_vma(mm, address);
+       vma = find_vma(mm, trans_exc_code & __FAIL_ADDR_MASK);
        up_read(&mm->mmap_sem);
 
        if (vma) {
@@ -450,17 +433,38 @@ void __kprobes do_asce_exception(struct pt_regs *regs, unsigned long error_code)
 
        /* User mode accesses just cause a SIGSEGV */
        if (regs->psw.mask & PSW_MASK_PSTATE) {
-               current->thread.prot_addr = address;
-               current->thread.trap_no = error_code;
-               do_sigsegv(regs, error_code, SEGV_MAPERR, address);
+               do_sigsegv(regs, int_code, SEGV_MAPERR, trans_exc_code);
                return;
        }
 
 no_context:
-       do_no_context(regs, error_code, address);
+       do_no_context(regs, int_code, trans_exc_code);
 }
 #endif
 
+int __handle_fault(unsigned long uaddr, unsigned long int_code, int write_user)
+{
+       struct pt_regs regs;
+       int access, fault;
+
+       regs.psw.mask = psw_kernel_bits;
+       if (!irqs_disabled())
+               regs.psw.mask |= PSW_MASK_IO | PSW_MASK_EXT;
+       regs.psw.addr = (unsigned long) __builtin_return_address(0);
+       regs.psw.addr |= PSW_ADDR_AMODE;
+       uaddr &= PAGE_MASK;
+       access = write_user ? VM_WRITE : VM_READ;
+       fault = do_exception(&regs, access, uaddr | 2);
+       if (unlikely(fault)) {
+               if (fault & VM_FAULT_OOM) {
+                       pagefault_out_of_memory();
+                       fault = 0;
+               } else if (fault & VM_FAULT_SIGBUS)
+                       do_sigbus(&regs, int_code, uaddr);
+       }
+       return fault ? -EFAULT : 0;
+}
+
 #ifdef CONFIG_PFAULT 
 /*
  * 'pfault' pseudo page faults routines.
@@ -522,7 +526,7 @@ void pfault_fini(void)
                : : "a" (&refbk), "m" (refbk) : "cc");
 }
 
-static void pfault_interrupt(__u16 error_code)
+static void pfault_interrupt(__u16 int_code)
 {
        struct task_struct *tsk;
        __u16 subcode;
index 2757c56..ad621e0 100644 (file)
@@ -269,7 +269,7 @@ int s390_enable_sie(void)
        struct mm_struct *mm, *old_mm;
 
        /* Do we have switched amode? If no, we cannot do sie */
-       if (!switch_amode)
+       if (user_mode == HOME_SPACE_MODE)
                return -EINVAL;
 
        /* Do we have pgstes? if yes, we are done */
index aaccc8e..58ffbd1 100644 (file)
@@ -2208,13 +2208,6 @@ int dasd_generic_probe(struct ccw_device *cdev,
 {
        int ret;
 
-       ret = ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP);
-       if (ret) {
-               DBF_EVENT(DBF_WARNING,
-                      "dasd_generic_probe: could not set ccw-device options "
-                      "for %s\n", dev_name(&cdev->dev));
-               return ret;
-       }
        ret = dasd_add_sysfs_files(cdev);
        if (ret) {
                DBF_EVENT(DBF_WARNING,
index 4e49b4a..d52593b 100644 (file)
@@ -8,7 +8,7 @@
  *
  */
 
-#define KMSG_COMPONENT "dasd-diag"
+#define KMSG_COMPONENT "dasd"
 
 #include <linux/stddef.h>
 #include <linux/kernel.h>
@@ -145,9 +145,18 @@ dasd_diag_erp(struct dasd_device *device)
 
        mdsk_term_io(device);
        rc = mdsk_init_io(device, device->block->bp_block, 0, NULL);
+       if (rc == 4) {
+               if (!(device->features & DASD_FEATURE_READONLY)) {
+                       pr_warning("%s: The access mode of a DIAG device "
+                                  "changed to read-only\n",
+                                  dev_name(&device->cdev->dev));
+                       device->features |= DASD_FEATURE_READONLY;
+               }
+               rc = 0;
+       }
        if (rc)
-               dev_warn(&device->cdev->dev, "DIAG ERP failed with "
-                           "rc=%d\n", rc);
+               pr_warning("%s: DIAG ERP failed with "
+                           "rc=%d\n", dev_name(&device->cdev->dev), rc);
 }
 
 /* Start a given request at the device. Return zero on success, non-zero
@@ -363,8 +372,9 @@ dasd_diag_check_device(struct dasd_device *device)
                private->pt_block = 2;
                break;
        default:
-               dev_warn(&device->cdev->dev, "Device type %d is not supported "
-                           "in DIAG mode\n", private->rdc_data.vdev_class);
+               pr_warning("%s: Device type %d is not supported "
+                          "in DIAG mode\n", dev_name(&device->cdev->dev),
+                          private->rdc_data.vdev_class);
                rc = -EOPNOTSUPP;
                goto out;
        }
@@ -405,8 +415,8 @@ dasd_diag_check_device(struct dasd_device *device)
                private->iob.flaga = DASD_DIAG_FLAGA_DEFAULT;
                rc = dia250(&private->iob, RW_BIO);
                if (rc == 3) {
-                       dev_warn(&device->cdev->dev,
-                               "A 64-bit DIAG call failed\n");
+                       pr_warning("%s: A 64-bit DIAG call failed\n",
+                                  dev_name(&device->cdev->dev));
                        rc = -EOPNOTSUPP;
                        goto out_label;
                }
@@ -415,8 +425,9 @@ dasd_diag_check_device(struct dasd_device *device)
                        break;
        }
        if (bsize > PAGE_SIZE) {
-               dev_warn(&device->cdev->dev, "Accessing the DASD failed because"
-                        " of an incorrect format (rc=%d)\n", rc);
+               pr_warning("%s: Accessing the DASD failed because of an "
+                          "incorrect format (rc=%d)\n",
+                          dev_name(&device->cdev->dev), rc);
                rc = -EIO;
                goto out_label;
        }
@@ -433,16 +444,20 @@ dasd_diag_check_device(struct dasd_device *device)
        for (sb = 512; sb < bsize; sb = sb << 1)
                block->s2b_shift++;
        rc = mdsk_init_io(device, block->bp_block, 0, NULL);
-       if (rc) {
-               dev_warn(&device->cdev->dev, "DIAG initialization "
-                       "failed with rc=%d\n", rc);
+       if (rc && (rc != 4)) {
+               pr_warning("%s: DIAG initialization failed with rc=%d\n",
+                          dev_name(&device->cdev->dev), rc);
                rc = -EIO;
        } else {
-               dev_info(&device->cdev->dev,
-                        "New DASD with %ld byte/block, total size %ld KB\n",
-                        (unsigned long) block->bp_block,
-                        (unsigned long) (block->blocks <<
-                                         block->s2b_shift) >> 1);
+               if (rc == 4)
+                       device->features |= DASD_FEATURE_READONLY;
+               pr_info("%s: New DASD with %ld byte/block, total size %ld "
+                       "KB%s\n", dev_name(&device->cdev->dev),
+                       (unsigned long) block->bp_block,
+                       (unsigned long) (block->blocks <<
+                                        block->s2b_shift) >> 1,
+                       (rc == 4) ? ", read-only device" : "");
+               rc = 0;
        }
 out_label:
        free_page((long) label);
index 417b97c..a8ec073 100644 (file)
@@ -86,7 +86,8 @@ dasd_eckd_probe (struct ccw_device *cdev)
        int ret;
 
        /* set ECKD specific ccw-device options */
-       ret = ccw_device_set_options(cdev, CCWDEV_ALLOW_FORCE);
+       ret = ccw_device_set_options(cdev, CCWDEV_ALLOW_FORCE |
+                                    CCWDEV_DO_PATHGROUP | CCWDEV_DO_MULTIPATH);
        if (ret) {
                DBF_EVENT(DBF_WARNING,
                       "dasd_eckd_probe: could not set ccw-device options "
@@ -1090,6 +1091,15 @@ dasd_eckd_check_characteristics(struct dasd_device *device)
        struct dasd_block *block;
        int is_known, rc;
 
+       if (!ccw_device_is_pathgroup(device->cdev)) {
+               dev_warn(&device->cdev->dev,
+                        "A channel path group could not be established\n");
+               return -EIO;
+       }
+       if (!ccw_device_is_multipath(device->cdev)) {
+               dev_info(&device->cdev->dev,
+                        "The DASD is not operating in multipath mode\n");
+       }
        private = (struct dasd_eckd_private *) device->private;
        if (!private) {
                private = kzalloc(sizeof(*private), GFP_KERNEL | GFP_DMA);
index 5cc11c6..28b5afc 100644 (file)
@@ -84,6 +84,7 @@ static void __init sclp_read_info_early(void)
                do {
                        memset(sccb, 0, sizeof(*sccb));
                        sccb->header.length = sizeof(*sccb);
+                       sccb->header.function_code = 0x80;
                        sccb->header.control_mask[2] = 0x80;
                        rc = sclp_cmd_sync_early(commands[i], sccb);
                } while (rc == -EBUSY);
index 5cd31e0..27503a7 100644 (file)
@@ -579,7 +579,8 @@ tape_generic_probe(struct ccw_device *cdev)
        device = tape_alloc_device();
        if (IS_ERR(device))
                return -ENODEV;
-       ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP);
+       ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP |
+                                    CCWDEV_DO_MULTIPATH);
        ret = sysfs_create_group(&cdev->dev.kobj, &tape_attr_group);
        if (ret) {
                tape_put_device(device);
index fa4c966..d033414 100644 (file)
@@ -3,7 +3,7 @@
 #
 
 obj-y += airq.o blacklist.o chsc.o cio.o css.o chp.o idset.o isc.o \
-       fcx.o itcw.o crw.o
+       fcx.o itcw.o crw.o ccwreq.o
 ccw_device-objs += device.o device_fsm.o device_ops.o
 ccw_device-objs += device_id.o device_pgid.o device_status.o
 obj-y += ccw_device.o cmf.o
diff --git a/drivers/s390/cio/ccwreq.c b/drivers/s390/cio/ccwreq.c
new file mode 100644 (file)
index 0000000..9509e38
--- /dev/null
@@ -0,0 +1,328 @@
+/*
+ *  Handling of internal CCW device requests.
+ *
+ *    Copyright IBM Corp. 2009
+ *    Author(s): Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
+ */
+
+#include <linux/types.h>
+#include <linux/err.h>
+#include <asm/ccwdev.h>
+#include <asm/cio.h>
+
+#include "io_sch.h"
+#include "cio.h"
+#include "device.h"
+#include "cio_debug.h"
+
+/**
+ * lpm_adjust - adjust path mask
+ * @lpm: path mask to adjust
+ * @mask: mask of available paths
+ *
+ * Shift @lpm right until @lpm and @mask have at least one bit in common or
+ * until @lpm is zero. Return the resulting lpm.
+ */
+int lpm_adjust(int lpm, int mask)
+{
+       while (lpm && ((lpm & mask) == 0))
+               lpm >>= 1;
+       return lpm;
+}
+
+/*
+ * Adjust path mask to use next path and reset retry count. Return resulting
+ * path mask.
+ */
+static u16 ccwreq_next_path(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+
+       req->retries    = req->maxretries;
+       req->mask       = lpm_adjust(req->mask >>= 1, req->lpm);
+
+       return req->mask;
+}
+
+/*
+ * Clean up device state and report to callback.
+ */
+static void ccwreq_stop(struct ccw_device *cdev, int rc)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+
+       if (req->done)
+               return;
+       req->done = 1;
+       ccw_device_set_timeout(cdev, 0);
+       memset(&cdev->private->irb, 0, sizeof(struct irb));
+       sch->lpm = sch->schib.pmcw.pam;
+       if (rc && rc != -ENODEV && req->drc)
+               rc = req->drc;
+       req->callback(cdev, req->data, rc);
+}
+
+/*
+ * (Re-)Start the operation until retries and paths are exhausted.
+ */
+static void ccwreq_do(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw1 *cp = req->cp;
+       int rc = -EACCES;
+
+       while (req->mask) {
+               if (req->retries-- == 0) {
+                       /* Retries exhausted, try next path. */
+                       ccwreq_next_path(cdev);
+                       continue;
+               }
+               /* Perform start function. */
+               sch->lpm = 0xff;
+               memset(&cdev->private->irb, 0, sizeof(struct irb));
+               rc = cio_start(sch, cp, (u8) req->mask);
+               if (rc == 0) {
+                       /* I/O started successfully. */
+                       ccw_device_set_timeout(cdev, req->timeout);
+                       return;
+               }
+               if (rc == -ENODEV) {
+                       /* Permanent device error. */
+                       break;
+               }
+               if (rc == -EACCES) {
+                       /* Permant path error. */
+                       ccwreq_next_path(cdev);
+                       continue;
+               }
+               /* Temporary improper status. */
+               rc = cio_clear(sch);
+               if (rc)
+                       break;
+               return;
+       }
+       ccwreq_stop(cdev, rc);
+}
+
+/**
+ * ccw_request_start - perform I/O request
+ * @cdev: ccw device
+ *
+ * Perform the I/O request specified by cdev->req.
+ */
+void ccw_request_start(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+
+       /* Try all paths twice to counter link flapping. */
+       req->mask       = 0x8080;
+       req->retries    = req->maxretries;
+       req->mask       = lpm_adjust(req->mask, req->lpm);
+       req->drc        = 0;
+       req->done       = 0;
+       req->cancel     = 0;
+       if (!req->mask)
+               goto out_nopath;
+       ccwreq_do(cdev);
+       return;
+
+out_nopath:
+       ccwreq_stop(cdev, -EACCES);
+}
+
+/**
+ * ccw_request_cancel - cancel running I/O request
+ * @cdev: ccw device
+ *
+ * Cancel the I/O request specified by cdev->req. Return non-zero if request
+ * has already finished, zero otherwise.
+ */
+int ccw_request_cancel(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+       int rc;
+
+       if (req->done)
+               return 1;
+       req->cancel = 1;
+       rc = cio_clear(sch);
+       if (rc)
+               ccwreq_stop(cdev, rc);
+       return 0;
+}
+
+/*
+ * Return the status of the internal I/O started on the specified ccw device.
+ * Perform BASIC SENSE if required.
+ */
+static enum io_status ccwreq_status(struct ccw_device *cdev, struct irb *lcirb)
+{
+       struct irb *irb = &cdev->private->irb;
+       struct cmd_scsw *scsw = &irb->scsw.cmd;
+
+       /* Perform BASIC SENSE if needed. */
+       if (ccw_device_accumulate_and_sense(cdev, lcirb))
+               return IO_RUNNING;
+       /* Check for halt/clear interrupt. */
+       if (scsw->fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
+               return IO_KILLED;
+       /* Check for path error. */
+       if (scsw->cc == 3 || scsw->pno)
+               return IO_PATH_ERROR;
+       /* Handle BASIC SENSE data. */
+       if (irb->esw.esw0.erw.cons) {
+               CIO_TRACE_EVENT(2, "sensedata");
+               CIO_HEX_EVENT(2, &cdev->private->dev_id,
+                             sizeof(struct ccw_dev_id));
+               CIO_HEX_EVENT(2, &cdev->private->irb.ecw, SENSE_MAX_COUNT);
+               /* Check for command reject. */
+               if (irb->ecw[0] & SNS0_CMD_REJECT)
+                       return IO_REJECTED;
+               /* Assume that unexpected SENSE data implies an error. */
+               return IO_STATUS_ERROR;
+       }
+       /* Check for channel errors. */
+       if (scsw->cstat != 0)
+               return IO_STATUS_ERROR;
+       /* Check for device errors. */
+       if (scsw->dstat & ~(DEV_STAT_CHN_END | DEV_STAT_DEV_END))
+               return IO_STATUS_ERROR;
+       /* Check for final state. */
+       if (!(scsw->dstat & DEV_STAT_DEV_END))
+               return IO_RUNNING;
+       /* Check for other improper status. */
+       if (scsw->cc == 1 && (scsw->stctl & SCSW_STCTL_ALERT_STATUS))
+               return IO_STATUS_ERROR;
+       return IO_DONE;
+}
+
+/*
+ * Log ccw request status.
+ */
+static void ccwreq_log_status(struct ccw_device *cdev, enum io_status status)
+{
+       struct ccw_request *req = &cdev->private->req;
+       struct {
+               struct ccw_dev_id dev_id;
+               u16 retries;
+               u8 lpm;
+               u8 status;
+       }  __attribute__ ((packed)) data;
+       data.dev_id     = cdev->private->dev_id;
+       data.retries    = req->retries;
+       data.lpm        = (u8) req->mask;
+       data.status     = (u8) status;
+       CIO_TRACE_EVENT(2, "reqstat");
+       CIO_HEX_EVENT(2, &data, sizeof(data));
+}
+
+/**
+ * ccw_request_handler - interrupt handler for I/O request procedure.
+ * @cdev: ccw device
+ *
+ * Handle interrupt during I/O request procedure.
+ */
+void ccw_request_handler(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+       struct irb *irb = (struct irb *) __LC_IRB;
+       enum io_status status;
+       int rc = -EOPNOTSUPP;
+
+       /* Check status of I/O request. */
+       status = ccwreq_status(cdev, irb);
+       if (req->filter)
+               status = req->filter(cdev, req->data, irb, status);
+       if (status != IO_RUNNING)
+               ccw_device_set_timeout(cdev, 0);
+       if (status != IO_DONE && status != IO_RUNNING)
+               ccwreq_log_status(cdev, status);
+       switch (status) {
+       case IO_DONE:
+               break;
+       case IO_RUNNING:
+               return;
+       case IO_REJECTED:
+               goto err;
+       case IO_PATH_ERROR:
+               goto out_next_path;
+       case IO_STATUS_ERROR:
+               goto out_restart;
+       case IO_KILLED:
+               /* Check if request was cancelled on purpose. */
+               if (req->cancel) {
+                       rc = -EIO;
+                       goto err;
+               }
+               goto out_restart;
+       }
+       /* Check back with request initiator. */
+       if (!req->check)
+               goto out;
+       switch (req->check(cdev, req->data)) {
+       case 0:
+               break;
+       case -EAGAIN:
+               goto out_restart;
+       case -EACCES:
+               goto out_next_path;
+       default:
+               goto err;
+       }
+out:
+       ccwreq_stop(cdev, 0);
+       return;
+
+out_next_path:
+       /* Try next path and restart I/O. */
+       if (!ccwreq_next_path(cdev)) {
+               rc = -EACCES;
+               goto err;
+       }
+out_restart:
+       /* Restart. */
+       ccwreq_do(cdev);
+       return;
+err:
+       ccwreq_stop(cdev, rc);
+}
+
+
+/**
+ * ccw_request_timeout - timeout handler for I/O request procedure
+ * @cdev: ccw device
+ *
+ * Handle timeout during I/O request procedure.
+ */
+void ccw_request_timeout(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+       int rc;
+
+       if (!ccwreq_next_path(cdev)) {
+               /* set the final return code for this request */
+               req->drc = -ETIME;
+       }
+       rc = cio_clear(sch);
+       if (rc)
+               goto err;
+       return;
+
+err:
+       ccwreq_stop(cdev, rc);
+}
+
+/**
+ * ccw_request_notoper - notoper handler for I/O request procedure
+ * @cdev: ccw device
+ *
+ * Handle timeout during I/O request procedure.
+ */
+void ccw_request_notoper(struct ccw_device *cdev)
+{
+       ccwreq_stop(cdev, -ENODEV);
+}
index 2e43558..bf7f80f 100644 (file)
@@ -68,6 +68,11 @@ struct schib {
        __u8 mda[4];             /* model dependent area */
 } __attribute__ ((packed,aligned(4)));
 
+enum sch_todo {
+       SCH_TODO_NOTHING,
+       SCH_TODO_UNREG,
+};
+
 /* subchannel data structure used by I/O subroutines */
 struct subchannel {
        struct subchannel_id schid;
@@ -95,7 +100,8 @@ struct subchannel {
        struct device dev;      /* entry in device tree */
        struct css_driver *driver;
        void *private; /* private per subchannel type data */
-       struct work_struct work;
+       enum sch_todo todo;
+       struct work_struct todo_work;
        struct schib_config config;
 } __attribute__ ((aligned(8)));
 
index 91c2570..92ff88a 100644 (file)
@@ -133,6 +133,8 @@ out:
        return rc;
 }
 
+static void css_sch_todo(struct work_struct *work);
+
 static struct subchannel *
 css_alloc_subchannel(struct subchannel_id schid)
 {
@@ -147,6 +149,7 @@ css_alloc_subchannel(struct subchannel_id schid)
                kfree(sch);
                return ERR_PTR(ret);
        }
+       INIT_WORK(&sch->todo_work, css_sch_todo);
        return sch;
 }
 
@@ -190,6 +193,51 @@ void css_sch_device_unregister(struct subchannel *sch)
 }
 EXPORT_SYMBOL_GPL(css_sch_device_unregister);
 
+static void css_sch_todo(struct work_struct *work)
+{
+       struct subchannel *sch;
+       enum sch_todo todo;
+
+       sch = container_of(work, struct subchannel, todo_work);
+       /* Find out todo. */
+       spin_lock_irq(sch->lock);
+       todo = sch->todo;
+       CIO_MSG_EVENT(4, "sch_todo: sch=0.%x.%04x, todo=%d\n", sch->schid.ssid,
+                     sch->schid.sch_no, todo);
+       sch->todo = SCH_TODO_NOTHING;
+       spin_unlock_irq(sch->lock);
+       /* Perform todo. */
+       if (todo == SCH_TODO_UNREG)
+               css_sch_device_unregister(sch);
+       /* Release workqueue ref. */
+       put_device(&sch->dev);
+}
+
+/**
+ * css_sched_sch_todo - schedule a subchannel operation
+ * @sch: subchannel
+ * @todo: todo
+ *
+ * Schedule the operation identified by @todo to be performed on the slow path
+ * workqueue. Do nothing if another operation with higher priority is already
+ * scheduled. Needs to be called with subchannel lock held.
+ */
+void css_sched_sch_todo(struct subchannel *sch, enum sch_todo todo)
+{
+       CIO_MSG_EVENT(4, "sch_todo: sched sch=0.%x.%04x todo=%d\n",
+                     sch->schid.ssid, sch->schid.sch_no, todo);
+       if (sch->todo >= todo)
+               return;
+       /* Get workqueue ref. */
+       if (!get_device(&sch->dev))
+               return;
+       sch->todo = todo;
+       if (!queue_work(slow_path_wq, &sch->todo_work)) {
+               /* Already queued, release workqueue ref. */
+               put_device(&sch->dev);
+       }
+}
+
 static void ssd_from_pmcw(struct chsc_ssd_info *ssd, struct pmcw *pmcw)
 {
        int i;
@@ -376,8 +424,8 @@ static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
                /* Unusable - ignore. */
                return 0;
        }
-       CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, unknown, "
-                        "slow path.\n", schid.ssid, schid.sch_no, CIO_OPER);
+       CIO_MSG_EVENT(4, "event: sch 0.%x.%04x, new\n", schid.ssid,
+                     schid.sch_no);
 
        return css_probe_device(schid);
 }
@@ -394,6 +442,10 @@ static int css_evaluate_known_subchannel(struct subchannel *sch, int slow)
                                "Got subchannel machine check but "
                                "no sch_event handler provided.\n");
        }
+       if (ret != 0 && ret != -EAGAIN) {
+               CIO_MSG_EVENT(2, "eval: sch 0.%x.%04x, rc=%d\n",
+                             sch->schid.ssid, sch->schid.sch_no, ret);
+       }
        return ret;
 }
 
@@ -684,6 +736,7 @@ static int __init setup_css(int nr)
        css->pseudo_subchannel->dev.parent = &css->device;
        css->pseudo_subchannel->dev.release = css_subchannel_release;
        dev_set_name(&css->pseudo_subchannel->dev, "defunct");
+       mutex_init(&css->pseudo_subchannel->reg_mutex);
        ret = cio_create_sch_lock(css->pseudo_subchannel);
        if (ret) {
                kfree(css->pseudo_subchannel);
index 68d6b0b..fe84b92 100644 (file)
@@ -11,6 +11,8 @@
 #include <asm/chpid.h>
 #include <asm/schid.h>
 
+#include "cio.h"
+
 /*
  * path grouping stuff
  */
@@ -151,4 +153,5 @@ int css_sch_is_valid(struct schib *);
 
 extern struct workqueue_struct *slow_path_wq;
 void css_wait_for_slow_path(void);
+void css_sched_sch_todo(struct subchannel *sch, enum sch_todo todo);
 #endif
index 2490b74..a49aaea 100644 (file)
@@ -7,6 +7,10 @@
  *              Cornelia Huck (cornelia.huck@de.ibm.com)
  *              Martin Schwidefsky (schwidefsky@de.ibm.com)
  */
+
+#define KMSG_COMPONENT "cio"
+#define pr_fmt(fmt) KMSG_COMPONENT ": " fmt
+
 #include <linux/module.h>
 #include <linux/init.h>
 #include <linux/spinlock.h>
@@ -299,53 +303,18 @@ int ccw_device_is_orphan(struct ccw_device *cdev)
 
 static void ccw_device_unregister(struct ccw_device *cdev)
 {
-       if (test_and_clear_bit(1, &cdev->private->registered)) {
+       if (device_is_registered(&cdev->dev)) {
+               /* Undo device_add(). */
                device_del(&cdev->dev);
+       }
+       if (cdev->private->flags.initialized) {
+               cdev->private->flags.initialized = 0;
                /* Release reference from device_initialize(). */
                put_device(&cdev->dev);
        }
 }
 
-static void ccw_device_remove_orphan_cb(struct work_struct *work)
-{
-       struct ccw_device_private *priv;
-       struct ccw_device *cdev;
-
-       priv = container_of(work, struct ccw_device_private, kick_work);
-       cdev = priv->cdev;
-       ccw_device_unregister(cdev);
-       /* Release cdev reference for workqueue processing. */
-       put_device(&cdev->dev);
-}
-
-static void
-ccw_device_remove_disconnected(struct ccw_device *cdev)
-{
-       unsigned long flags;
-
-       /*
-        * Forced offline in disconnected state means
-        * 'throw away device'.
-        */
-       if (ccw_device_is_orphan(cdev)) {
-               /*
-                * Deregister ccw device.
-                * Unfortunately, we cannot do this directly from the
-                * attribute method.
-                */
-               /* Get cdev reference for workqueue processing. */
-               if (!get_device(&cdev->dev))
-                       return;
-               spin_lock_irqsave(cdev->ccwlock, flags);
-               cdev->private->state = DEV_STATE_NOT_OPER;
-               spin_unlock_irqrestore(cdev->ccwlock, flags);
-               PREPARE_WORK(&cdev->private->kick_work,
-                               ccw_device_remove_orphan_cb);
-               queue_work(slow_path_wq, &cdev->private->kick_work);
-       } else
-               /* Deregister subchannel, which will kill the ccw device. */
-               ccw_device_schedule_sch_unregister(cdev);
-}
+static void io_subchannel_quiesce(struct subchannel *);
 
 /**
  * ccw_device_set_offline() - disable a ccw device for I/O
@@ -360,7 +329,8 @@ ccw_device_remove_disconnected(struct ccw_device *cdev)
  */
 int ccw_device_set_offline(struct ccw_device *cdev)
 {
-       int ret;
+       struct subchannel *sch;
+       int ret, state;
 
        if (!cdev)
                return -ENODEV;
@@ -374,6 +344,7 @@ int ccw_device_set_offline(struct ccw_device *cdev)
        }
        cdev->online = 0;
        spin_lock_irq(cdev->ccwlock);
+       sch = to_subchannel(cdev->dev.parent);
        /* Wait until a final state or DISCONNECTED is reached */
        while (!dev_fsm_final_state(cdev) &&
               cdev->private->state != DEV_STATE_DISCONNECTED) {
@@ -382,20 +353,37 @@ int ccw_device_set_offline(struct ccw_device *cdev)
                           cdev->private->state == DEV_STATE_DISCONNECTED));
                spin_lock_irq(cdev->ccwlock);
        }
-       ret = ccw_device_offline(cdev);
-       if (ret)
-               goto error;
+       do {
+               ret = ccw_device_offline(cdev);
+               if (!ret)
+                       break;
+               CIO_MSG_EVENT(0, "ccw_device_offline returned %d, device "
+                             "0.%x.%04x\n", ret, cdev->private->dev_id.ssid,
+                             cdev->private->dev_id.devno);
+               if (ret != -EBUSY)
+                       goto error;
+               state = cdev->private->state;
+               spin_unlock_irq(cdev->ccwlock);
+               io_subchannel_quiesce(sch);
+               spin_lock_irq(cdev->ccwlock);
+               cdev->private->state = state;
+       } while (ret == -EBUSY);
        spin_unlock_irq(cdev->ccwlock);
        wait_event(cdev->private->wait_q, (dev_fsm_final_state(cdev) ||
                   cdev->private->state == DEV_STATE_DISCONNECTED));
+       /* Inform the user if set offline failed. */
+       if (cdev->private->state == DEV_STATE_BOXED) {
+               pr_warning("%s: The device entered boxed state while "
+                          "being set offline\n", dev_name(&cdev->dev));
+       } else if (cdev->private->state == DEV_STATE_NOT_OPER) {
+               pr_warning("%s: The device stopped operating while "
+                          "being set offline\n", dev_name(&cdev->dev));
+       }
        /* Give up reference from ccw_device_set_online(). */
        put_device(&cdev->dev);
        return 0;
 
 error:
-       CIO_MSG_EVENT(0, "ccw_device_offline returned %d, device 0.%x.%04x\n",
-                     ret, cdev->private->dev_id.ssid,
-                     cdev->private->dev_id.devno);
        cdev->private->state = DEV_STATE_OFFLINE;
        dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
        spin_unlock_irq(cdev->ccwlock);
@@ -448,6 +436,16 @@ int ccw_device_set_online(struct ccw_device *cdev)
        if ((cdev->private->state != DEV_STATE_ONLINE) &&
            (cdev->private->state != DEV_STATE_W4SENSE)) {
                spin_unlock_irq(cdev->ccwlock);
+               /* Inform the user that set online failed. */
+               if (cdev->private->state == DEV_STATE_BOXED) {
+                       pr_warning("%s: Setting the device online failed "
+                                  "because it is boxed\n",
+                                  dev_name(&cdev->dev));
+               } else if (cdev->private->state == DEV_STATE_NOT_OPER) {
+                       pr_warning("%s: Setting the device online failed "
+                                  "because it is not operational\n",
+                                  dev_name(&cdev->dev));
+               }
                /* Give up online reference since onlining failed. */
                put_device(&cdev->dev);
                return -ENODEV;
@@ -494,27 +492,22 @@ error:
 
 static int online_store_handle_offline(struct ccw_device *cdev)
 {
-       if (cdev->private->state == DEV_STATE_DISCONNECTED)
-               ccw_device_remove_disconnected(cdev);
-       else if (cdev->online && cdev->drv && cdev->drv->set_offline)
+       if (cdev->private->state == DEV_STATE_DISCONNECTED) {
+               spin_lock_irq(cdev->ccwlock);
+               ccw_device_sched_todo(cdev, CDEV_TODO_UNREG_EVAL);
+               spin_unlock_irq(cdev->ccwlock);
+       } else if (cdev->online && cdev->drv && cdev->drv->set_offline)
                return ccw_device_set_offline(cdev);
        return 0;
 }
 
 static int online_store_recog_and_online(struct ccw_device *cdev)
 {
-       int ret;
-
        /* Do device recognition, if needed. */
        if (cdev->private->state == DEV_STATE_BOXED) {
-               ret = ccw_device_recognition(cdev);
-               if (ret) {
-                       CIO_MSG_EVENT(0, "Couldn't start recognition "
-                                     "for device 0.%x.%04x (ret=%d)\n",
-                                     cdev->private->dev_id.ssid,
-                                     cdev->private->dev_id.devno, ret);
-                       return ret;
-               }
+               spin_lock_irq(cdev->ccwlock);
+               ccw_device_recognition(cdev);
+               spin_unlock_irq(cdev->ccwlock);
                wait_event(cdev->private->wait_q,
                           cdev->private->flags.recog_done);
                if (cdev->private->state != DEV_STATE_OFFLINE)
@@ -553,11 +546,10 @@ static ssize_t online_store (struct device *dev, struct device_attribute *attr,
        int force, ret;
        unsigned long i;
 
-       if ((cdev->private->state != DEV_STATE_OFFLINE &&
-            cdev->private->state != DEV_STATE_ONLINE &&
-            cdev->private->state != DEV_STATE_BOXED &&
-            cdev->private->state != DEV_STATE_DISCONNECTED) ||
-           atomic_cmpxchg(&cdev->private->onoff, 0, 1) != 0)
+       if (!dev_fsm_final_state(cdev) &&
+           cdev->private->state != DEV_STATE_DISCONNECTED)
+               return -EAGAIN;
+       if (atomic_cmpxchg(&cdev->private->onoff, 0, 1) != 0)
                return -EAGAIN;
 
        if (cdev->drv && !try_module_get(cdev->drv->owner)) {
@@ -665,81 +657,31 @@ static int ccw_device_register(struct ccw_device *cdev)
                           cdev->private->dev_id.devno);
        if (ret)
                return ret;
-       ret = device_add(dev);
-       if (ret)
-               return ret;
-
-       set_bit(1, &cdev->private->registered);
-       return ret;
+       return device_add(dev);
 }
 
-struct match_data {
-       struct ccw_dev_id dev_id;
-       struct ccw_device * sibling;
-};
-
-static int
-match_devno(struct device * dev, void * data)
-{
-       struct match_data * d = data;
-       struct ccw_device * cdev;
-
-       cdev = to_ccwdev(dev);
-       if ((cdev->private->state == DEV_STATE_DISCONNECTED) &&
-           !ccw_device_is_orphan(cdev) &&
-           ccw_dev_id_is_equal(&cdev->private->dev_id, &d->dev_id) &&
-           (cdev != d->sibling))
-               return 1;
-       return 0;
-}
-
-static struct ccw_device * get_disc_ccwdev_by_dev_id(struct ccw_dev_id *dev_id,
-                                                    struct ccw_device *sibling)
-{
-       struct device *dev;
-       struct match_data data;
-
-       data.dev_id = *dev_id;
-       data.sibling = sibling;
-       dev = bus_find_device(&ccw_bus_type, NULL, &data, match_devno);
-
-       return dev ? to_ccwdev(dev) : NULL;
-}
-
-static int match_orphan(struct device *dev, void *data)
+static int match_dev_id(struct device *dev, void *data)
 {
-       struct ccw_dev_id *dev_id;
-       struct ccw_device *cdev;
+       struct ccw_device *cdev = to_ccwdev(dev);
+       struct ccw_dev_id *dev_id = data;
 
-       dev_id = data;
-       cdev = to_ccwdev(dev);
        return ccw_dev_id_is_equal(&cdev->private->dev_id, dev_id);
 }
 
-static struct ccw_device *
-get_orphaned_ccwdev_by_dev_id(struct channel_subsystem *css,
-                             struct ccw_dev_id *dev_id)
+static struct ccw_device *get_ccwdev_by_dev_id(struct ccw_dev_id *dev_id)
 {
        struct device *dev;
 
-       dev = device_find_child(&css->pseudo_subchannel->dev, dev_id,
-                               match_orphan);
+       dev = bus_find_device(&ccw_bus_type, NULL, dev_id, match_dev_id);
 
        return dev ? to_ccwdev(dev) : NULL;
 }
 
-void ccw_device_do_unbind_bind(struct work_struct *work)
+static void ccw_device_do_unbind_bind(struct ccw_device *cdev)
 {
-       struct ccw_device_private *priv;
-       struct ccw_device *cdev;
-       struct subchannel *sch;
        int ret;
 
-       priv = container_of(work, struct ccw_device_private, kick_work);
-       cdev = priv->cdev;
-       sch = to_subchannel(cdev->dev.parent);
-
-       if (test_bit(1, &cdev->private->registered)) {
+       if (device_is_registered(&cdev->dev)) {
                device_release_driver(&cdev->dev);
                ret = device_attach(&cdev->dev);
                WARN_ON(ret == -ENODEV);
@@ -773,6 +715,8 @@ static struct ccw_device * io_subchannel_allocate_dev(struct subchannel *sch)
        return ERR_PTR(-ENOMEM);
 }
 
+static void ccw_device_todo(struct work_struct *work);
+
 static int io_subchannel_initialize_dev(struct subchannel *sch,
                                        struct ccw_device *cdev)
 {
@@ -780,7 +724,7 @@ static int io_subchannel_initialize_dev(struct subchannel *sch,
        atomic_set(&cdev->private->onoff, 0);
        cdev->dev.parent = &sch->dev;
        cdev->dev.release = ccw_device_release;
-       INIT_WORK(&cdev->private->kick_work, NULL);
+       INIT_WORK(&cdev->private->todo_work, ccw_device_todo);
        cdev->dev.groups = ccwdev_attr_groups;
        /* Do first half of device_register. */
        device_initialize(&cdev->dev);
@@ -789,6 +733,7 @@ static int io_subchannel_initialize_dev(struct subchannel *sch,
                put_device(&cdev->dev);
                return -ENODEV;
        }
+       cdev->private->flags.initialized = 1;
        return 0;
 }
 
@@ -806,76 +751,7 @@ static struct ccw_device * io_subchannel_create_ccwdev(struct subchannel *sch)
        return cdev;
 }
 
-static int io_subchannel_recog(struct ccw_device *, struct subchannel *);
-
-static void sch_attach_device(struct subchannel *sch,
-                             struct ccw_device *cdev)
-{
-       css_update_ssd_info(sch);
-       spin_lock_irq(sch->lock);
-       sch_set_cdev(sch, cdev);
-       cdev->private->schid = sch->schid;
-       cdev->ccwlock = sch->lock;
-       ccw_device_trigger_reprobe(cdev);
-       spin_unlock_irq(sch->lock);
-}
-
-static void sch_attach_disconnected_device(struct subchannel *sch,
-                                          struct ccw_device *cdev)
-{
-       struct subchannel *other_sch;
-       int ret;
-
-       /* Get reference for new parent. */
-       if (!get_device(&sch->dev))
-               return;
-       other_sch = to_subchannel(cdev->dev.parent);
-       /* Note: device_move() changes cdev->dev.parent */
-       ret = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV);
-       if (ret) {
-               CIO_MSG_EVENT(0, "Moving disconnected device 0.%x.%04x failed "
-                             "(ret=%d)!\n", cdev->private->dev_id.ssid,
-                             cdev->private->dev_id.devno, ret);
-               /* Put reference for new parent. */
-               put_device(&sch->dev);
-               return;
-       }
-       sch_set_cdev(other_sch, NULL);
-       /* No need to keep a subchannel without ccw device around. */
-       css_sch_device_unregister(other_sch);
-       sch_attach_device(sch, cdev);
-       /* Put reference for old parent. */
-       put_device(&other_sch->dev);
-}
-
-static void sch_attach_orphaned_device(struct subchannel *sch,
-                                      struct ccw_device *cdev)
-{
-       int ret;
-       struct subchannel *pseudo_sch;
-
-       /* Get reference for new parent. */
-       if (!get_device(&sch->dev))
-               return;
-       pseudo_sch = to_subchannel(cdev->dev.parent);
-       /*
-        * Try to move the ccw device to its new subchannel.
-        * Note: device_move() changes cdev->dev.parent
-        */
-       ret = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV);
-       if (ret) {
-               CIO_MSG_EVENT(0, "Moving device 0.%x.%04x from orphanage "
-                             "failed (ret=%d)!\n",
-                             cdev->private->dev_id.ssid,
-                             cdev->private->dev_id.devno, ret);
-               /* Put reference for new parent. */
-               put_device(&sch->dev);
-               return;
-       }
-       sch_attach_device(sch, cdev);
-       /* Put reference on pseudo subchannel. */
-       put_device(&pseudo_sch->dev);
-}
+static void io_subchannel_recog(struct ccw_device *, struct subchannel *);
 
 static void sch_create_and_recog_new_device(struct subchannel *sch)
 {
@@ -888,100 +764,19 @@ static void sch_create_and_recog_new_device(struct subchannel *sch)
                css_sch_device_unregister(sch);
                return;
        }
-       spin_lock_irq(sch->lock);
-       sch_set_cdev(sch, cdev);
-       spin_unlock_irq(sch->lock);
        /* Start recognition for the new ccw device. */
-       if (io_subchannel_recog(cdev, sch)) {
-               spin_lock_irq(sch->lock);
-               sch_set_cdev(sch, NULL);
-               spin_unlock_irq(sch->lock);
-               css_sch_device_unregister(sch);
-               /* Put reference from io_subchannel_create_ccwdev(). */
-               put_device(&sch->dev);
-               /* Give up initial reference. */
-               put_device(&cdev->dev);
-       }
-}
-
-
-void ccw_device_move_to_orphanage(struct work_struct *work)
-{
-       struct ccw_device_private *priv;
-       struct ccw_device *cdev;
-       struct ccw_device *replacing_cdev;
-       struct subchannel *sch;
-       int ret;
-       struct channel_subsystem *css;
-       struct ccw_dev_id dev_id;
-
-       priv = container_of(work, struct ccw_device_private, kick_work);
-       cdev = priv->cdev;
-       sch = to_subchannel(cdev->dev.parent);
-       css = to_css(sch->dev.parent);
-       dev_id.devno = sch->schib.pmcw.dev;
-       dev_id.ssid = sch->schid.ssid;
-
-       /* Increase refcount for pseudo subchannel. */
-       get_device(&css->pseudo_subchannel->dev);
-       /*
-        * Move the orphaned ccw device to the orphanage so the replacing
-        * ccw device can take its place on the subchannel.
-        * Note: device_move() changes cdev->dev.parent
-        */
-       ret = device_move(&cdev->dev, &css->pseudo_subchannel->dev,
-               DPM_ORDER_NONE);
-       if (ret) {
-               CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to orphanage failed "
-                             "(ret=%d)!\n", cdev->private->dev_id.ssid,
-                             cdev->private->dev_id.devno, ret);
-               /* Decrease refcount for pseudo subchannel again. */
-               put_device(&css->pseudo_subchannel->dev);
-               return;
-       }
-       cdev->ccwlock = css->pseudo_subchannel->lock;
-       /*
-        * Search for the replacing ccw device
-        * - among the disconnected devices
-        * - in the orphanage
-        */
-       replacing_cdev = get_disc_ccwdev_by_dev_id(&dev_id, cdev);
-       if (replacing_cdev) {
-               sch_attach_disconnected_device(sch, replacing_cdev);
-               /* Release reference from get_disc_ccwdev_by_dev_id() */
-               put_device(&replacing_cdev->dev);
-               /* Release reference of subchannel from old cdev. */
-               put_device(&sch->dev);
-               return;
-       }
-       replacing_cdev = get_orphaned_ccwdev_by_dev_id(css, &dev_id);
-       if (replacing_cdev) {
-               sch_attach_orphaned_device(sch, replacing_cdev);
-               /* Release reference from get_orphaned_ccwdev_by_dev_id() */
-               put_device(&replacing_cdev->dev);
-               /* Release reference of subchannel from old cdev. */
-               put_device(&sch->dev);
-               return;
-       }
-       sch_create_and_recog_new_device(sch);
-       /* Release reference of subchannel from old cdev. */
-       put_device(&sch->dev);
+       io_subchannel_recog(cdev, sch);
 }
 
 /*
  * Register recognized device.
  */
-static void
-io_subchannel_register(struct work_struct *work)
+static void io_subchannel_register(struct ccw_device *cdev)
 {
-       struct ccw_device_private *priv;
-       struct ccw_device *cdev;
        struct subchannel *sch;
        int ret;
        unsigned long flags;
 
-       priv = container_of(work, struct ccw_device_private, kick_work);
-       cdev = priv->cdev;
        sch = to_subchannel(cdev->dev.parent);
        /*
         * Check if subchannel is still registered. It may have become
@@ -1033,41 +828,23 @@ out:
        cdev->private->flags.recog_done = 1;
        wake_up(&cdev->private->wait_q);
 out_err:
-       /* Release reference for workqueue processing. */
-       put_device(&cdev->dev);
        if (atomic_dec_and_test(&ccw_device_init_count))
                wake_up(&ccw_device_init_wq);
 }
 
-static void ccw_device_call_sch_unregister(struct work_struct *work)
+static void ccw_device_call_sch_unregister(struct ccw_device *cdev)
 {
-       struct ccw_device_private *priv;
-       struct ccw_device *cdev;
        struct subchannel *sch;
 
-       priv = container_of(work, struct ccw_device_private, kick_work);
-       cdev = priv->cdev;
        /* Get subchannel reference for local processing. */
        if (!get_device(cdev->dev.parent))
                return;
        sch = to_subchannel(cdev->dev.parent);
        css_sch_device_unregister(sch);
-       /* Release cdev reference for workqueue processing.*/
-       put_device(&cdev->dev);
        /* Release subchannel reference for local processing. */
        put_device(&sch->dev);
 }
 
-void ccw_device_schedule_sch_unregister(struct ccw_device *cdev)
-{
-       /* Get cdev reference for workqueue processing. */
-       if (!get_device(&cdev->dev))
-               return;
-       PREPARE_WORK(&cdev->private->kick_work,
-                    ccw_device_call_sch_unregister);
-       queue_work(slow_path_wq, &cdev->private->kick_work);
-}
-
 /*
  * subchannel recognition done. Called from the state machine.
  */
@@ -1083,7 +860,8 @@ io_subchannel_recog_done(struct ccw_device *cdev)
                /* Device did not respond in time. */
        case DEV_STATE_NOT_OPER:
                cdev->private->flags.recog_done = 1;
-               ccw_device_schedule_sch_unregister(cdev);
+               /* Remove device found not operational. */
+               ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
                if (atomic_dec_and_test(&ccw_device_init_count))
                        wake_up(&ccw_device_init_wq);
                break;
@@ -1092,22 +870,15 @@ io_subchannel_recog_done(struct ccw_device *cdev)
                 * We can't register the device in interrupt context so
                 * we schedule a work item.
                 */
-               if (!get_device(&cdev->dev))
-                       break;
-               PREPARE_WORK(&cdev->private->kick_work,
-                            io_subchannel_register);
-               queue_work(slow_path_wq, &cdev->private->kick_work);
+               ccw_device_sched_todo(cdev, CDEV_TODO_REGISTER);
                break;
        }
 }
 
-static int
-io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch)
+static void io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch)
 {
-       int rc;
        struct ccw_device_private *priv;
 
-       sch_set_cdev(sch, cdev);
        cdev->ccwlock = sch->lock;
 
        /* Init private data. */
@@ -1125,62 +896,81 @@ io_subchannel_recog(struct ccw_device *cdev, struct subchannel *sch)
 
        /* Start async. device sensing. */
        spin_lock_irq(sch->lock);
-       rc = ccw_device_recognition(cdev);
+       sch_set_cdev(sch, cdev);
+       ccw_device_recognition(cdev);
        spin_unlock_irq(sch->lock);
-       if (rc) {
-               if (atomic_dec_and_test(&ccw_device_init_count))
-                       wake_up(&ccw_device_init_wq);
-       }
-       return rc;
 }
 
-static void ccw_device_move_to_sch(struct work_struct *work)
+static int ccw_device_move_to_sch(struct ccw_device *cdev,
+                                 struct subchannel *sch)
 {
-       struct ccw_device_private *priv;
-       int rc;
-       struct subchannel *sch;
-       struct ccw_device *cdev;
-       struct subchannel *former_parent;
+       struct subchannel *old_sch;
+       int rc, old_enabled = 0;
 
-       priv = container_of(work, struct ccw_device_private, kick_work);
-       sch = priv->sch;
-       cdev = priv->cdev;
-       former_parent = to_subchannel(cdev->dev.parent);
-       /* Get reference for new parent. */
+       old_sch = to_subchannel(cdev->dev.parent);
+       /* Obtain child reference for new parent. */
        if (!get_device(&sch->dev))
-               return;
+               return -ENODEV;
+
+       if (!sch_is_pseudo_sch(old_sch)) {
+               spin_lock_irq(old_sch->lock);
+               old_enabled = old_sch->schib.pmcw.ena;
+               rc = 0;
+               if (old_enabled)
+                       rc = cio_disable_subchannel(old_sch);
+               spin_unlock_irq(old_sch->lock);
+               if (rc == -EBUSY) {
+                       /* Release child reference for new parent. */
+                       put_device(&sch->dev);
+                       return rc;
+               }
+       }
+
        mutex_lock(&sch->reg_mutex);
-       /*
-        * Try to move the ccw device to its new subchannel.
-        * Note: device_move() changes cdev->dev.parent
-        */
        rc = device_move(&cdev->dev, &sch->dev, DPM_ORDER_PARENT_BEFORE_DEV);
        mutex_unlock(&sch->reg_mutex);
        if (rc) {
-               CIO_MSG_EVENT(0, "Moving device 0.%x.%04x to subchannel "
-                             "0.%x.%04x failed (ret=%d)!\n",
+               CIO_MSG_EVENT(0, "device_move(0.%x.%04x,0.%x.%04x)=%d\n",
                              cdev->private->dev_id.ssid,
                              cdev->private->dev_id.devno, sch->schid.ssid,
-                             sch->schid.sch_no, rc);
-               css_sch_device_unregister(sch);
-               /* Put reference for new parent again. */
+                             sch->schib.pmcw.dev, rc);
+               if (old_enabled) {
+                       /* Try to reenable the old subchannel. */
+                       spin_lock_irq(old_sch->lock);
+                       cio_enable_subchannel(old_sch, (u32)(addr_t)old_sch);
+                       spin_unlock_irq(old_sch->lock);
+               }
+               /* Release child reference for new parent. */
                put_device(&sch->dev);
-               goto out;
+               return rc;
        }
-       if (!sch_is_pseudo_sch(former_parent)) {
-               spin_lock_irq(former_parent->lock);
-               sch_set_cdev(former_parent, NULL);
-               spin_unlock_irq(former_parent->lock);
-               css_sch_device_unregister(former_parent);
-               /* Reset intparm to zeroes. */
-               former_parent->config.intparm = 0;
-               cio_commit_config(former_parent);
+       /* Clean up old subchannel. */
+       if (!sch_is_pseudo_sch(old_sch)) {
+               spin_lock_irq(old_sch->lock);
+               sch_set_cdev(old_sch, NULL);
+               spin_unlock_irq(old_sch->lock);
+               css_schedule_eval(old_sch->schid);
        }
-       sch_attach_device(sch, cdev);
-out:
-       /* Put reference for old parent. */
-       put_device(&former_parent->dev);
-       put_device(&cdev->dev);
+       /* Release child reference for old parent. */
+       put_device(&old_sch->dev);
+       /* Initialize new subchannel. */
+       spin_lock_irq(sch->lock);
+       cdev->private->schid = sch->schid;
+       cdev->ccwlock = sch->lock;
+       if (!sch_is_pseudo_sch(sch))
+               sch_set_cdev(sch, cdev);
+       spin_unlock_irq(sch->lock);
+       if (!sch_is_pseudo_sch(sch))
+               css_update_ssd_info(sch);
+       return 0;
+}
+
+static int ccw_device_move_to_orph(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct channel_subsystem *css = to_css(sch->dev.parent);
+
+       return ccw_device_move_to_sch(cdev, css->pseudo_subchannel);
 }
 
 static void io_subchannel_irq(struct subchannel *sch)
@@ -1199,9 +989,6 @@ void io_subchannel_init_config(struct subchannel *sch)
 {
        memset(&sch->config, 0, sizeof(sch->config));
        sch->config.csense = 1;
-       /* Use subchannel mp mode when there is more than 1 installed CHPID. */
-       if ((sch->schib.pmcw.pim & (sch->schib.pmcw.pim - 1)) != 0)
-               sch->config.mp = 1;
 }
 
 static void io_subchannel_init_fields(struct subchannel *sch)
@@ -1222,23 +1009,6 @@ static void io_subchannel_init_fields(struct subchannel *sch)
        io_subchannel_init_config(sch);
 }
 
-static void io_subchannel_do_unreg(struct work_struct *work)
-{
-       struct subchannel *sch;
-
-       sch = container_of(work, struct subchannel, work);
-       css_sch_device_unregister(sch);
-       put_device(&sch->dev);
-}
-
-/* Schedule unregister if we have no cdev. */
-static void io_subchannel_schedule_removal(struct subchannel *sch)
-{
-       get_device(&sch->dev);
-       INIT_WORK(&sch->work, io_subchannel_do_unreg);
-       queue_work(slow_path_wq, &sch->work);
-}
-
 /*
  * Note: We always return 0 so that we bind to the device even on error.
  * This is needed so that our remove function is called on unregister.
@@ -1247,8 +1017,6 @@ static int io_subchannel_probe(struct subchannel *sch)
 {
        struct ccw_device *cdev;
        int rc;
-       unsigned long flags;
-       struct ccw_dev_id dev_id;
 
        if (cio_is_console(sch->schid)) {
                rc = sysfs_create_group(&sch->dev.kobj,
@@ -1268,6 +1036,7 @@ static int io_subchannel_probe(struct subchannel *sch)
                cdev = sch_get_cdev(sch);
                cdev->dev.groups = ccwdev_attr_groups;
                device_initialize(&cdev->dev);
+               cdev->private->flags.initialized = 1;
                ccw_device_register(cdev);
                /*
                 * Check if the device is already online. If it is
@@ -1292,44 +1061,14 @@ static int io_subchannel_probe(struct subchannel *sch)
        sch->private = kzalloc(sizeof(struct io_subchannel_private),
                               GFP_KERNEL | GFP_DMA);
        if (!sch->private)
-               goto out_err;
-       /*
-        * First check if a fitting device may be found amongst the
-        * disconnected devices or in the orphanage.
-        */
-       dev_id.devno = sch->schib.pmcw.dev;
-       dev_id.ssid = sch->schid.ssid;
-       cdev = get_disc_ccwdev_by_dev_id(&dev_id, NULL);
-       if (!cdev)
-               cdev = get_orphaned_ccwdev_by_dev_id(to_css(sch->dev.parent),
-                                                    &dev_id);
-       if (cdev) {
-               /*
-                * Schedule moving the device until when we have a registered
-                * subchannel to move to and succeed the probe. We can
-                * unregister later again, when the probe is through.
-                */
-               cdev->private->sch = sch;
-               PREPARE_WORK(&cdev->private->kick_work,
-                            ccw_device_move_to_sch);
-               queue_work(slow_path_wq, &cdev->private->kick_work);
-               return 0;
-       }
-       cdev = io_subchannel_create_ccwdev(sch);
-       if (IS_ERR(cdev))
-               goto out_err;
-       rc = io_subchannel_recog(cdev, sch);
-       if (rc) {
-               spin_lock_irqsave(sch->lock, flags);
-               io_subchannel_recog_done(cdev);
-               spin_unlock_irqrestore(sch->lock, flags);
-       }
+               goto out_schedule;
+       css_schedule_eval(sch->schid);
        return 0;
-out_err:
-       kfree(sch->private);
-       sysfs_remove_group(&sch->dev.kobj, &io_subchannel_attr_group);
+
 out_schedule:
-       io_subchannel_schedule_removal(sch);
+       spin_lock_irq(sch->lock);
+       css_sched_sch_todo(sch, SCH_TODO_UNREG);
+       spin_unlock_irq(sch->lock);
        return 0;
 }
 
@@ -1341,28 +1080,20 @@ io_subchannel_remove (struct subchannel *sch)
 
        cdev = sch_get_cdev(sch);
        if (!cdev)
-               return 0;
+               goto out_free;
+       io_subchannel_quiesce(sch);
        /* Set ccw device to not operational and drop reference. */
        spin_lock_irqsave(cdev->ccwlock, flags);
        sch_set_cdev(sch, NULL);
        cdev->private->state = DEV_STATE_NOT_OPER;
        spin_unlock_irqrestore(cdev->ccwlock, flags);
        ccw_device_unregister(cdev);
+out_free:
        kfree(sch->private);
        sysfs_remove_group(&sch->dev.kobj, &io_subchannel_attr_group);
        return 0;
 }
 
-static int io_subchannel_notify(struct subchannel *sch, int event)
-{
-       struct ccw_device *cdev;
-
-       cdev = sch_get_cdev(sch);
-       if (!cdev)
-               return 0;
-       return ccw_device_notify(cdev, event);
-}
-
 static void io_subchannel_verify(struct subchannel *sch)
 {
        struct ccw_device *cdev;
@@ -1372,36 +1103,6 @@ static void io_subchannel_verify(struct subchannel *sch)
                dev_fsm_event(cdev, DEV_EVENT_VERIFY);
 }
 
-static int check_for_io_on_path(struct subchannel *sch, int mask)
-{
-       if (cio_update_schib(sch))
-               return 0;
-       if (scsw_actl(&sch->schib.scsw) && sch->schib.pmcw.lpum == mask)
-               return 1;
-       return 0;
-}
-
-static void terminate_internal_io(struct subchannel *sch,
-                                 struct ccw_device *cdev)
-{
-       if (cio_clear(sch)) {
-               /* Recheck device in case clear failed. */
-               sch->lpm = 0;
-               if (cdev->online)
-                       dev_fsm_event(cdev, DEV_EVENT_VERIFY);
-               else
-                       css_schedule_eval(sch->schid);
-               return;
-       }
-       cdev->private->state = DEV_STATE_CLEAR_VERIFY;
-       /* Request retry of internal operation. */
-       cdev->private->flags.intretry = 1;
-       /* Call handler. */
-       if (cdev->handler)
-               cdev->handler(cdev, cdev->private->intparm,
-                             ERR_PTR(-EIO));
-}
-
 static void io_subchannel_terminate_path(struct subchannel *sch, u8 mask)
 {
        struct ccw_device *cdev;
@@ -1409,18 +1110,24 @@ static void io_subchannel_terminate_path(struct subchannel *sch, u8 mask)
        cdev = sch_get_cdev(sch);
        if (!cdev)
                return;
-       if (check_for_io_on_path(sch, mask)) {
-               if (cdev->private->state == DEV_STATE_ONLINE)
-                       ccw_device_kill_io(cdev);
-               else {
-                       terminate_internal_io(sch, cdev);
-                       /* Re-start path verification. */
-                       dev_fsm_event(cdev, DEV_EVENT_VERIFY);
-               }
-       } else
-               /* trigger path verification. */
-               dev_fsm_event(cdev, DEV_EVENT_VERIFY);
+       if (cio_update_schib(sch))
+               goto err;
+       /* Check for I/O on path. */
+       if (scsw_actl(&sch->schib.scsw) == 0 || sch->schib.pmcw.lpum != mask)
+               goto out;
+       if (cdev->private->state == DEV_STATE_ONLINE) {
+               ccw_device_kill_io(cdev);
+               goto out;
+       }
+       if (cio_clear(sch))
+               goto err;
+out:
+       /* Trigger path verification. */
+       dev_fsm_event(cdev, DEV_EVENT_VERIFY);
+       return;
 
+err:
+       dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
 }
 
 static int io_subchannel_chp_event(struct subchannel *sch,
@@ -1457,46 +1164,41 @@ static int io_subchannel_chp_event(struct subchannel *sch,
        return 0;
 }
 
-static void
-io_subchannel_shutdown(struct subchannel *sch)
+static void io_subchannel_quiesce(struct subchannel *sch)
 {
        struct ccw_device *cdev;
        int ret;
 
+       spin_lock_irq(sch->lock);
        cdev = sch_get_cdev(sch);
-
        if (cio_is_console(sch->schid))
-               return;
+               goto out_unlock;
        if (!sch->schib.pmcw.ena)
-               /* Nothing to do. */
-               return;
+               goto out_unlock;
        ret = cio_disable_subchannel(sch);
        if (ret != -EBUSY)
-               /* Subchannel is disabled, we're done. */
-               return;
-       cdev->private->state = DEV_STATE_QUIESCE;
+               goto out_unlock;
        if (cdev->handler)
-               cdev->handler(cdev, cdev->private->intparm,
-                             ERR_PTR(-EIO));
-       ret = ccw_device_cancel_halt_clear(cdev);
-       if (ret == -EBUSY) {
-               ccw_device_set_timeout(cdev, HZ/10);
-               wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev));
+               cdev->handler(cdev, cdev->private->intparm, ERR_PTR(-EIO));
+       while (ret == -EBUSY) {
+               cdev->private->state = DEV_STATE_QUIESCE;
+               ret = ccw_device_cancel_halt_clear(cdev);
+               if (ret == -EBUSY) {
+                       ccw_device_set_timeout(cdev, HZ/10);
+                       spin_unlock_irq(sch->lock);
+                       wait_event(cdev->private->wait_q,
+                                  cdev->private->state != DEV_STATE_QUIESCE);
+                       spin_lock_irq(sch->lock);
+               }
+               ret = cio_disable_subchannel(sch);
        }
-       cio_disable_subchannel(sch);
+out_unlock:
+       spin_unlock_irq(sch->lock);
 }
 
-static int io_subchannel_get_status(struct subchannel *sch)
+static void io_subchannel_shutdown(struct subchannel *sch)
 {
-       struct schib schib;
-
-       if (stsch(sch->schid, &schib) || !schib.pmcw.dnv)
-               return CIO_GONE;
-       if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev))
-               return CIO_REVALIDATE;
-       if (!sch->lpm)
-               return CIO_NO_PATH;
-       return CIO_OPER;
+       io_subchannel_quiesce(sch);
 }
 
 static int device_is_disconnected(struct ccw_device *cdev)
@@ -1575,20 +1277,16 @@ static void ccw_device_schedule_recovery(void)
 static int purge_fn(struct device *dev, void *data)
 {
        struct ccw_device *cdev = to_ccwdev(dev);
-       struct ccw_device_private *priv = cdev->private;
-       int unreg;
+       struct ccw_dev_id *id = &cdev->private->dev_id;
 
        spin_lock_irq(cdev->ccwlock);
-       unreg = is_blacklisted(priv->dev_id.ssid, priv->dev_id.devno) &&
-               (priv->state == DEV_STATE_OFFLINE);
+       if (is_blacklisted(id->ssid, id->devno) &&
+           (cdev->private->state == DEV_STATE_OFFLINE)) {
+               CIO_MSG_EVENT(3, "ccw: purging 0.%x.%04x\n", id->ssid,
+                             id->devno);
+               ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
+       }
        spin_unlock_irq(cdev->ccwlock);
-       if (!unreg)
-               goto out;
-       CIO_MSG_EVENT(3, "ccw: purging 0.%x.%04x\n", priv->dev_id.ssid,
-                     priv->dev_id.devno);
-       ccw_device_schedule_sch_unregister(cdev);
-
-out:
        /* Abort loop in case of pending signal. */
        if (signal_pending(current))
                return -EINTR;
@@ -1630,91 +1328,169 @@ void ccw_device_set_notoper(struct ccw_device *cdev)
        cdev->private->state = DEV_STATE_NOT_OPER;
 }
 
-static int io_subchannel_sch_event(struct subchannel *sch, int slow)
+enum io_sch_action {
+       IO_SCH_UNREG,
+       IO_SCH_ORPH_UNREG,
+       IO_SCH_ATTACH,
+       IO_SCH_UNREG_ATTACH,
+       IO_SCH_ORPH_ATTACH,
+       IO_SCH_REPROBE,
+       IO_SCH_VERIFY,
+       IO_SCH_DISC,
+       IO_SCH_NOP,
+};
+
+static enum io_sch_action sch_get_action(struct subchannel *sch)
+{
+       struct ccw_device *cdev;
+
+       cdev = sch_get_cdev(sch);
+       if (cio_update_schib(sch)) {
+               /* Not operational. */
+               if (!cdev)
+                       return IO_SCH_UNREG;
+               if (!ccw_device_notify(cdev, CIO_GONE))
+                       return IO_SCH_UNREG;
+               return IO_SCH_ORPH_UNREG;
+       }
+       /* Operational. */
+       if (!cdev)
+               return IO_SCH_ATTACH;
+       if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) {
+               if (!ccw_device_notify(cdev, CIO_GONE))
+                       return IO_SCH_UNREG_ATTACH;
+               return IO_SCH_ORPH_ATTACH;
+       }
+       if ((sch->schib.pmcw.pam & sch->opm) == 0) {
+               if (!ccw_device_notify(cdev, CIO_NO_PATH))
+                       return IO_SCH_UNREG;
+               return IO_SCH_DISC;
+       }
+       if (device_is_disconnected(cdev))
+               return IO_SCH_REPROBE;
+       if (cdev->online)
+               return IO_SCH_VERIFY;
+       return IO_SCH_NOP;
+}
+
+/**
+ * io_subchannel_sch_event - process subchannel event
+ * @sch: subchannel
+ * @process: non-zero if function is called in process context
+ *
+ * An unspecified event occurred for this subchannel. Adjust data according
+ * to the current operational state of the subchannel and device. Return
+ * zero when the event has been handled sufficiently or -EAGAIN when this
+ * function should be called again in process context.
+ */
+static int io_subchannel_sch_event(struct subchannel *sch, int process)
 {
-       int event, ret, disc;
        unsigned long flags;
-       enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE, DISC } action;
        struct ccw_device *cdev;
+       struct ccw_dev_id dev_id;
+       enum io_sch_action action;
+       int rc = -EAGAIN;
 
        spin_lock_irqsave(sch->lock, flags);
+       if (!device_is_registered(&sch->dev))
+               goto out_unlock;
+       if (work_pending(&sch->todo_work))
+               goto out_unlock;
        cdev = sch_get_cdev(sch);
-       disc = device_is_disconnected(cdev);
-       if (disc && slow) {
-               /* Disconnected devices are evaluated directly only.*/
-               spin_unlock_irqrestore(sch->lock, flags);
-               return 0;
-       }
-       /* No interrupt after machine check - kill pending timers. */
-       if (cdev)
-               ccw_device_set_timeout(cdev, 0);
-       if (!disc && !slow) {
-               /* Non-disconnected devices are evaluated on the slow path. */
-               spin_unlock_irqrestore(sch->lock, flags);
-               return -EAGAIN;
+       if (cdev && work_pending(&cdev->private->todo_work))
+               goto out_unlock;
+       action = sch_get_action(sch);
+       CIO_MSG_EVENT(2, "event: sch 0.%x.%04x, process=%d, action=%d\n",
+                     sch->schid.ssid, sch->schid.sch_no, process,
+                     action);
+       /* Perform immediate actions while holding the lock. */
+       switch (action) {
+       case IO_SCH_REPROBE:
+               /* Trigger device recognition. */
+               ccw_device_trigger_reprobe(cdev);
+               rc = 0;
+               goto out_unlock;
+       case IO_SCH_VERIFY:
+               /* Trigger path verification. */
+               io_subchannel_verify(sch);
+               rc = 0;
+               goto out_unlock;
+       case IO_SCH_DISC:
+               ccw_device_set_disconnected(cdev);
+               rc = 0;
+               goto out_unlock;
+       case IO_SCH_ORPH_UNREG:
+       case IO_SCH_ORPH_ATTACH:
+               ccw_device_set_disconnected(cdev);
+               break;
+       case IO_SCH_UNREG_ATTACH:
+       case IO_SCH_UNREG:
+               if (cdev)
+                       ccw_device_set_notoper(cdev);
+               break;
+       case IO_SCH_NOP:
+               rc = 0;
+               goto out_unlock;
+       default:
+               break;
        }
-       event = io_subchannel_get_status(sch);
-       CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n",
-                     sch->schid.ssid, sch->schid.sch_no, event,
-                     disc ? "disconnected" : "normal",
-                     slow ? "slow" : "fast");
-       /* Analyze subchannel status. */
-       action = NONE;
-       switch (event) {
-       case CIO_NO_PATH:
-               if (disc) {
-                       /* Check if paths have become available. */
-                       action = REPROBE;
-                       break;
-               }
-               /* fall through */
-       case CIO_GONE:
-               /* Ask driver what to do with device. */
-               if (io_subchannel_notify(sch, event))
-                       action = DISC;
-               else
-                       action = UNREGISTER;
+       spin_unlock_irqrestore(sch->lock, flags);
+       /* All other actions require process context. */
+       if (!process)
+               goto out;
+       /* Handle attached ccw device. */
+       switch (action) {
+       case IO_SCH_ORPH_UNREG:
+       case IO_SCH_ORPH_ATTACH:
+               /* Move ccw device to orphanage. */
+               rc = ccw_device_move_to_orph(cdev);
+               if (rc)
+                       goto out;
                break;
-       case CIO_REVALIDATE:
-               /* Device will be removed, so no notify necessary. */
-               if (disc)
-                       /* Reprobe because immediate unregister might block. */
-                       action = REPROBE;
-               else
-                       action = UNREGISTER_PROBE;
+       case IO_SCH_UNREG_ATTACH:
+               /* Unregister ccw device. */
+               ccw_device_unregister(cdev);
                break;
-       case CIO_OPER:
-               if (disc)
-                       /* Get device operational again. */
-                       action = REPROBE;
+       default:
                break;
        }
-       /* Perform action. */
-       ret = 0;
+       /* Handle subchannel. */
        switch (action) {
-       case UNREGISTER:
-       case UNREGISTER_PROBE:
-               ccw_device_set_notoper(cdev);
-               /* Unregister device (will use subchannel lock). */
-               spin_unlock_irqrestore(sch->lock, flags);
+       case IO_SCH_ORPH_UNREG:
+       case IO_SCH_UNREG:
                css_sch_device_unregister(sch);
-               spin_lock_irqsave(sch->lock, flags);
                break;
-       case REPROBE:
+       case IO_SCH_ORPH_ATTACH:
+       case IO_SCH_UNREG_ATTACH:
+       case IO_SCH_ATTACH:
+               dev_id.ssid = sch->schid.ssid;
+               dev_id.devno = sch->schib.pmcw.dev;
+               cdev = get_ccwdev_by_dev_id(&dev_id);
+               if (!cdev) {
+                       sch_create_and_recog_new_device(sch);
+                       break;
+               }
+               rc = ccw_device_move_to_sch(cdev, sch);
+               if (rc) {
+                       /* Release reference from get_ccwdev_by_dev_id() */
+                       put_device(&cdev->dev);
+                       goto out;
+               }
+               spin_lock_irqsave(sch->lock, flags);
                ccw_device_trigger_reprobe(cdev);
-               break;
-       case DISC:
-               ccw_device_set_disconnected(cdev);
+               spin_unlock_irqrestore(sch->lock, flags);
+               /* Release reference from get_ccwdev_by_dev_id() */
+               put_device(&cdev->dev);
                break;
        default:
                break;
        }
-       spin_unlock_irqrestore(sch->lock, flags);
-       /* Probe if necessary. */
-       if (action == UNREGISTER_PROBE)
-               ret = css_probe_device(sch->schid);
+       return 0;
 
-       return ret;
+out_unlock:
+       spin_unlock_irqrestore(sch->lock, flags);
+out:
+       return rc;
 }
 
 #ifdef CONFIG_CCW_CONSOLE
@@ -1744,10 +1520,7 @@ static int ccw_device_console_enable(struct ccw_device *cdev,
        sch->driver = &io_subchannel_driver;
        /* Initialize the ccw_device structure. */
        cdev->dev.parent= &sch->dev;
-       rc = io_subchannel_recog(cdev, sch);
-       if (rc)
-               return rc;
-
+       io_subchannel_recog(cdev, sch);
        /* Now wait for the async. recognition to come to an end. */
        spin_lock_irq(cdev->ccwlock);
        while (!dev_fsm_final_state(cdev))
@@ -1763,7 +1536,7 @@ static int ccw_device_console_enable(struct ccw_device *cdev,
        rc = 0;
 out_unlock:
        spin_unlock_irq(cdev->ccwlock);
-       return 0;
+       return rc;
 }
 
 struct ccw_device *
@@ -1919,7 +1692,7 @@ static int ccw_device_pm_prepare(struct device *dev)
 {
        struct ccw_device *cdev = to_ccwdev(dev);
 
-       if (work_pending(&cdev->private->kick_work))
+       if (work_pending(&cdev->private->todo_work))
                return -EAGAIN;
        /* Fail while device is being set online/offline. */
        if (atomic_read(&cdev->private->onoff))
@@ -2005,7 +1778,6 @@ static int ccw_device_pm_thaw(struct device *dev)
 static void __ccw_device_pm_restore(struct ccw_device *cdev)
 {
        struct subchannel *sch = to_subchannel(cdev->dev.parent);
-       int ret;
 
        if (cio_is_console(sch->schid))
                goto out;
@@ -2015,22 +1787,10 @@ static void __ccw_device_pm_restore(struct ccw_device *cdev)
         */
        spin_lock_irq(sch->lock);
        cdev->private->flags.resuming = 1;
-       ret = ccw_device_recognition(cdev);
+       ccw_device_recognition(cdev);
        spin_unlock_irq(sch->lock);
-       if (ret) {
-               CIO_MSG_EVENT(0, "Couldn't start recognition for device "
-                             "0.%x.%04x (ret=%d)\n",
-                             cdev->private->dev_id.ssid,
-                             cdev->private->dev_id.devno, ret);
-               spin_lock_irq(sch->lock);
-               cdev->private->state = DEV_STATE_DISCONNECTED;
-               spin_unlock_irq(sch->lock);
-               /* notify driver after the resume cb */
-               goto out;
-       }
        wait_event(cdev->private->wait_q, dev_fsm_final_state(cdev) ||
                   cdev->private->state == DEV_STATE_DISCONNECTED);
-
 out:
        cdev->private->flags.resuming = 0;
 }
@@ -2040,7 +1800,7 @@ static int resume_handle_boxed(struct ccw_device *cdev)
        cdev->private->state = DEV_STATE_BOXED;
        if (ccw_device_notify(cdev, CIO_BOXED))
                return 0;
-       ccw_device_schedule_sch_unregister(cdev);
+       ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
        return -ENODEV;
 }
 
@@ -2049,7 +1809,7 @@ static int resume_handle_disc(struct ccw_device *cdev)
        cdev->private->state = DEV_STATE_DISCONNECTED;
        if (ccw_device_notify(cdev, CIO_GONE))
                return 0;
-       ccw_device_schedule_sch_unregister(cdev);
+       ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
        return -ENODEV;
 }
 
@@ -2094,9 +1854,7 @@ static int ccw_device_pm_restore(struct device *dev)
        /* check if the device type has changed */
        if (!ccw_device_test_sense_data(cdev)) {
                ccw_device_update_sense_data(cdev);
-               PREPARE_WORK(&cdev->private->kick_work,
-                            ccw_device_do_unbind_bind);
-               queue_work(ccw_device_work, &cdev->private->kick_work);
+               ccw_device_sched_todo(cdev, CDEV_TODO_REBIND);
                ret = -ENODEV;
                goto out_unlock;
        }
@@ -2140,7 +1898,7 @@ out_disc_unlock:
        goto out_restore;
 
 out_unreg_unlock:
-       ccw_device_schedule_sch_unregister(cdev);
+       ccw_device_sched_todo(cdev, CDEV_TODO_UNREG_EVAL);
        ret = -ENODEV;
 out_unlock:
        spin_unlock_irq(sch->lock);
@@ -2205,6 +1963,77 @@ ccw_device_get_subchannel_id(struct ccw_device *cdev)
        return sch->schid;
 }
 
+static void ccw_device_todo(struct work_struct *work)
+{
+       struct ccw_device_private *priv;
+       struct ccw_device *cdev;
+       struct subchannel *sch;
+       enum cdev_todo todo;
+
+       priv = container_of(work, struct ccw_device_private, todo_work);
+       cdev = priv->cdev;
+       sch = to_subchannel(cdev->dev.parent);
+       /* Find out todo. */
+       spin_lock_irq(cdev->ccwlock);
+       todo = priv->todo;
+       priv->todo = CDEV_TODO_NOTHING;
+       CIO_MSG_EVENT(4, "cdev_todo: cdev=0.%x.%04x todo=%d\n",
+                     priv->dev_id.ssid, priv->dev_id.devno, todo);
+       spin_unlock_irq(cdev->ccwlock);
+       /* Perform todo. */
+       switch (todo) {
+       case CDEV_TODO_ENABLE_CMF:
+               cmf_reenable(cdev);
+               break;
+       case CDEV_TODO_REBIND:
+               ccw_device_do_unbind_bind(cdev);
+               break;
+       case CDEV_TODO_REGISTER:
+               io_subchannel_register(cdev);
+               break;
+       case CDEV_TODO_UNREG_EVAL:
+               if (!sch_is_pseudo_sch(sch))
+                       css_schedule_eval(sch->schid);
+               /* fall-through */
+       case CDEV_TODO_UNREG:
+               if (sch_is_pseudo_sch(sch))
+                       ccw_device_unregister(cdev);
+               else
+                       ccw_device_call_sch_unregister(cdev);
+               break;
+       default:
+               break;
+       }
+       /* Release workqueue ref. */
+       put_device(&cdev->dev);
+}
+
+/**
+ * ccw_device_sched_todo - schedule ccw device operation
+ * @cdev: ccw device
+ * @todo: todo
+ *
+ * Schedule the operation identified by @todo to be performed on the slow path
+ * workqueue. Do nothing if another operation with higher priority is already
+ * scheduled. Needs to be called with ccwdev lock held.
+ */
+void ccw_device_sched_todo(struct ccw_device *cdev, enum cdev_todo todo)
+{
+       CIO_MSG_EVENT(4, "cdev_todo: sched cdev=0.%x.%04x todo=%d\n",
+                     cdev->private->dev_id.ssid, cdev->private->dev_id.devno,
+                     todo);
+       if (cdev->private->todo >= todo)
+               return;
+       cdev->private->todo = todo;
+       /* Get workqueue ref. */
+       if (!get_device(&cdev->dev))
+               return;
+       if (!queue_work(slow_path_wq, &cdev->private->todo_work)) {
+               /* Already queued, release workqueue ref. */
+               put_device(&cdev->dev);
+       }
+}
+
 MODULE_LICENSE("GPL");
 EXPORT_SYMBOL(ccw_device_set_online);
 EXPORT_SYMBOL(ccw_device_set_offline);
index 246c648..bcfe13e 100644 (file)
@@ -21,7 +21,6 @@ enum dev_state {
        DEV_STATE_DISBAND_PGID,
        DEV_STATE_BOXED,
        /* states to wait for i/o completion before doing something */
-       DEV_STATE_CLEAR_VERIFY,
        DEV_STATE_TIMEOUT_KILL,
        DEV_STATE_QUIESCE,
        /* special states for devices gone not operational */
@@ -29,6 +28,7 @@ enum dev_state {
        DEV_STATE_DISCONNECTED_SENSE_ID,
        DEV_STATE_CMFCHANGE,
        DEV_STATE_CMFUPDATE,
+       DEV_STATE_STEAL_LOCK,
        /* last element! */
        NR_DEV_STATES
 };
@@ -81,17 +81,16 @@ void io_subchannel_init_config(struct subchannel *sch);
 
 int ccw_device_cancel_halt_clear(struct ccw_device *);
 
-void ccw_device_do_unbind_bind(struct work_struct *);
-void ccw_device_move_to_orphanage(struct work_struct *);
 int ccw_device_is_orphan(struct ccw_device *);
 
-int ccw_device_recognition(struct ccw_device *);
+void ccw_device_recognition(struct ccw_device *);
 int ccw_device_online(struct ccw_device *);
 int ccw_device_offline(struct ccw_device *);
 void ccw_device_update_sense_data(struct ccw_device *);
 int ccw_device_test_sense_data(struct ccw_device *);
 void ccw_device_schedule_sch_unregister(struct ccw_device *);
 int ccw_purge_blacklisted(void);
+void ccw_device_sched_todo(struct ccw_device *cdev, enum cdev_todo todo);
 
 /* Function prototypes for device status and basic sense stuff. */
 void ccw_device_accumulate_irb(struct ccw_device *, struct irb *);
@@ -99,24 +98,28 @@ void ccw_device_accumulate_basic_sense(struct ccw_device *, struct irb *);
 int ccw_device_accumulate_and_sense(struct ccw_device *, struct irb *);
 int ccw_device_do_sense(struct ccw_device *, struct irb *);
 
+/* Function prototype for internal request handling. */
+int lpm_adjust(int lpm, int mask);
+void ccw_request_start(struct ccw_device *);
+int ccw_request_cancel(struct ccw_device *cdev);
+void ccw_request_handler(struct ccw_device *cdev);
+void ccw_request_timeout(struct ccw_device *cdev);
+void ccw_request_notoper(struct ccw_device *cdev);
+
 /* Function prototypes for sense id stuff. */
 void ccw_device_sense_id_start(struct ccw_device *);
-void ccw_device_sense_id_irq(struct ccw_device *, enum dev_event);
 void ccw_device_sense_id_done(struct ccw_device *, int);
 
 /* Function prototypes for path grouping stuff. */
-void ccw_device_sense_pgid_start(struct ccw_device *);
-void ccw_device_sense_pgid_irq(struct ccw_device *, enum dev_event);
-void ccw_device_sense_pgid_done(struct ccw_device *, int);
-
 void ccw_device_verify_start(struct ccw_device *);
-void ccw_device_verify_irq(struct ccw_device *, enum dev_event);
 void ccw_device_verify_done(struct ccw_device *, int);
 
 void ccw_device_disband_start(struct ccw_device *);
-void ccw_device_disband_irq(struct ccw_device *, enum dev_event);
 void ccw_device_disband_done(struct ccw_device *, int);
 
+void ccw_device_stlck_start(struct ccw_device *, void *, void *, void *);
+void ccw_device_stlck_done(struct ccw_device *, void *, int);
+
 int ccw_device_call_handler(struct ccw_device *);
 
 int ccw_device_stlck(struct ccw_device *);
index b9613d7..ae76065 100644 (file)
@@ -229,8 +229,8 @@ ccw_device_recog_done(struct ccw_device *cdev, int state)
 
        sch = to_subchannel(cdev->dev.parent);
 
-       ccw_device_set_timeout(cdev, 0);
-       cio_disable_subchannel(sch);
+       if (cio_disable_subchannel(sch))
+               state = DEV_STATE_NOT_OPER;
        /*
         * Now that we tried recognition, we have performed device selection
         * through ssch() and the path information is up to date.
@@ -263,22 +263,10 @@ ccw_device_recog_done(struct ccw_device *cdev, int state)
        }
        switch (state) {
        case DEV_STATE_NOT_OPER:
-               CIO_MSG_EVENT(2, "SenseID : unknown device %04x on "
-                             "subchannel 0.%x.%04x\n",
-                             cdev->private->dev_id.devno,
-                             sch->schid.ssid, sch->schid.sch_no);
                break;
        case DEV_STATE_OFFLINE:
                if (!cdev->online) {
                        ccw_device_update_sense_data(cdev);
-                       /* Issue device info message. */
-                       CIO_MSG_EVENT(4, "SenseID : device 0.%x.%04x reports: "
-                                     "CU  Type/Mod = %04X/%02X, Dev Type/Mod "
-                                     "= %04X/%02X\n",
-                                     cdev->private->dev_id.ssid,
-                                     cdev->private->dev_id.devno,
-                                     cdev->id.cu_type, cdev->id.cu_model,
-                                     cdev->id.dev_type, cdev->id.dev_model);
                        break;
                }
                cdev->private->state = DEV_STATE_OFFLINE;
@@ -289,16 +277,10 @@ ccw_device_recog_done(struct ccw_device *cdev, int state)
                        wake_up(&cdev->private->wait_q);
                } else {
                        ccw_device_update_sense_data(cdev);
-                       PREPARE_WORK(&cdev->private->kick_work,
-                                    ccw_device_do_unbind_bind);
-                       queue_work(ccw_device_work, &cdev->private->kick_work);
+                       ccw_device_sched_todo(cdev, CDEV_TODO_REBIND);
                }
                return;
        case DEV_STATE_BOXED:
-               CIO_MSG_EVENT(0, "SenseID : boxed device %04x on "
-                             " subchannel 0.%x.%04x\n",
-                             cdev->private->dev_id.devno,
-                             sch->schid.ssid, sch->schid.sch_no);
                if (cdev->id.cu_type != 0) { /* device was recognized before */
                        cdev->private->flags.recog_done = 1;
                        cdev->private->state = DEV_STATE_BOXED;
@@ -343,28 +325,16 @@ int ccw_device_notify(struct ccw_device *cdev, int event)
        return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0;
 }
 
-static void cmf_reenable_delayed(struct work_struct *work)
-{
-       struct ccw_device_private *priv;
-       struct ccw_device *cdev;
-
-       priv = container_of(work, struct ccw_device_private, kick_work);
-       cdev = priv->cdev;
-       cmf_reenable(cdev);
-}
-
 static void ccw_device_oper_notify(struct ccw_device *cdev)
 {
        if (ccw_device_notify(cdev, CIO_OPER)) {
                /* Reenable channel measurements, if needed. */
-               PREPARE_WORK(&cdev->private->kick_work, cmf_reenable_delayed);
-               queue_work(ccw_device_work, &cdev->private->kick_work);
+               ccw_device_sched_todo(cdev, CDEV_TODO_ENABLE_CMF);
                return;
        }
        /* Driver doesn't want device back. */
        ccw_device_set_notoper(cdev);
-       PREPARE_WORK(&cdev->private->kick_work, ccw_device_do_unbind_bind);
-       queue_work(ccw_device_work, &cdev->private->kick_work);
+       ccw_device_sched_todo(cdev, CDEV_TODO_REBIND);
 }
 
 /*
@@ -392,14 +362,14 @@ ccw_device_done(struct ccw_device *cdev, int state)
                CIO_MSG_EVENT(0, "Boxed device %04x on subchannel %04x\n",
                              cdev->private->dev_id.devno, sch->schid.sch_no);
                if (cdev->online && !ccw_device_notify(cdev, CIO_BOXED))
-                       ccw_device_schedule_sch_unregister(cdev);
+                       ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
                cdev->private->flags.donotify = 0;
                break;
        case DEV_STATE_NOT_OPER:
                CIO_MSG_EVENT(0, "Device %04x gone on subchannel %04x\n",
                              cdev->private->dev_id.devno, sch->schid.sch_no);
                if (!ccw_device_notify(cdev, CIO_GONE))
-                       ccw_device_schedule_sch_unregister(cdev);
+                       ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
                else
                        ccw_device_set_disconnected(cdev);
                cdev->private->flags.donotify = 0;
@@ -409,7 +379,7 @@ ccw_device_done(struct ccw_device *cdev, int state)
                              "%04x\n", cdev->private->dev_id.devno,
                              sch->schid.sch_no);
                if (!ccw_device_notify(cdev, CIO_NO_PATH))
-                       ccw_device_schedule_sch_unregister(cdev);
+                       ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
                else
                        ccw_device_set_disconnected(cdev);
                cdev->private->flags.donotify = 0;
@@ -425,107 +395,12 @@ ccw_device_done(struct ccw_device *cdev, int state)
        wake_up(&cdev->private->wait_q);
 }
 
-static int cmp_pgid(struct pgid *p1, struct pgid *p2)
-{
-       char *c1;
-       char *c2;
-
-       c1 = (char *)p1;
-       c2 = (char *)p2;
-
-       return memcmp(c1 + 1, c2 + 1, sizeof(struct pgid) - 1);
-}
-
-static void __ccw_device_get_common_pgid(struct ccw_device *cdev)
-{
-       int i;
-       int last;
-
-       last = 0;
-       for (i = 0; i < 8; i++) {
-               if (cdev->private->pgid[i].inf.ps.state1 == SNID_STATE1_RESET)
-                       /* No PGID yet */
-                       continue;
-               if (cdev->private->pgid[last].inf.ps.state1 ==
-                   SNID_STATE1_RESET) {
-                       /* First non-zero PGID */
-                       last = i;
-                       continue;
-               }
-               if (cmp_pgid(&cdev->private->pgid[i],
-                            &cdev->private->pgid[last]) == 0)
-                       /* Non-conflicting PGIDs */
-                       continue;
-
-               /* PGID mismatch, can't pathgroup. */
-               CIO_MSG_EVENT(0, "SNID - pgid mismatch for device "
-                             "0.%x.%04x, can't pathgroup\n",
-                             cdev->private->dev_id.ssid,
-                             cdev->private->dev_id.devno);
-               cdev->private->options.pgroup = 0;
-               return;
-       }
-       if (cdev->private->pgid[last].inf.ps.state1 ==
-           SNID_STATE1_RESET)
-               /* No previous pgid found */
-               memcpy(&cdev->private->pgid[0],
-                      &channel_subsystems[0]->global_pgid,
-                      sizeof(struct pgid));
-       else
-               /* Use existing pgid */
-               memcpy(&cdev->private->pgid[0], &cdev->private->pgid[last],
-                      sizeof(struct pgid));
-}
-
-/*
- * Function called from device_pgid.c after sense path ground has completed.
- */
-void
-ccw_device_sense_pgid_done(struct ccw_device *cdev, int err)
-{
-       struct subchannel *sch;
-
-       sch = to_subchannel(cdev->dev.parent);
-       switch (err) {
-       case -EOPNOTSUPP: /* path grouping not supported, use nop instead. */
-               cdev->private->options.pgroup = 0;
-               break;
-       case 0: /* success */
-       case -EACCES: /* partial success, some paths not operational */
-               /* Check if all pgids are equal or 0. */
-               __ccw_device_get_common_pgid(cdev);
-               break;
-       case -ETIME:            /* Sense path group id stopped by timeout. */
-       case -EUSERS:           /* device is reserved for someone else. */
-               ccw_device_done(cdev, DEV_STATE_BOXED);
-               return;
-       default:
-               ccw_device_done(cdev, DEV_STATE_NOT_OPER);
-               return;
-       }
-       /* Start Path Group verification. */
-       cdev->private->state = DEV_STATE_VERIFY;
-       cdev->private->flags.doverify = 0;
-       ccw_device_verify_start(cdev);
-}
-
 /*
  * Start device recognition.
  */
-int
-ccw_device_recognition(struct ccw_device *cdev)
+void ccw_device_recognition(struct ccw_device *cdev)
 {
-       struct subchannel *sch;
-       int ret;
-
-       sch = to_subchannel(cdev->dev.parent);
-       ret = cio_enable_subchannel(sch, (u32)(addr_t)sch);
-       if (ret != 0)
-               /* Couldn't enable the subchannel for i/o. Sick device. */
-               return ret;
-
-       /* After 60s the device recognition is considered to have failed. */
-       ccw_device_set_timeout(cdev, 60*HZ);
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
 
        /*
         * We used to start here with a sense pgid to find out whether a device
@@ -537,32 +412,33 @@ ccw_device_recognition(struct ccw_device *cdev)
         */
        cdev->private->flags.recog_done = 0;
        cdev->private->state = DEV_STATE_SENSE_ID;
+       if (cio_enable_subchannel(sch, (u32) (addr_t) sch)) {
+               ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER);
+               return;
+       }
        ccw_device_sense_id_start(cdev);
-       return 0;
 }
 
 /*
- * Handle timeout in device recognition.
+ * Handle events for states that use the ccw request infrastructure.
  */
-static void
-ccw_device_recog_timeout(struct ccw_device *cdev, enum dev_event dev_event)
+static void ccw_device_request_event(struct ccw_device *cdev, enum dev_event e)
 {
-       int ret;
-
-       ret = ccw_device_cancel_halt_clear(cdev);
-       switch (ret) {
-       case 0:
-               ccw_device_recog_done(cdev, DEV_STATE_BOXED);
+       switch (e) {
+       case DEV_EVENT_NOTOPER:
+               ccw_request_notoper(cdev);
                break;
-       case -ENODEV:
-               ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER);
+       case DEV_EVENT_INTERRUPT:
+               ccw_request_handler(cdev);
+               break;
+       case DEV_EVENT_TIMEOUT:
+               ccw_request_timeout(cdev);
                break;
        default:
-               ccw_device_set_timeout(cdev, 3*HZ);
+               break;
        }
 }
 
-
 void
 ccw_device_verify_done(struct ccw_device *cdev, int err)
 {
@@ -571,21 +447,18 @@ ccw_device_verify_done(struct ccw_device *cdev, int err)
        sch = to_subchannel(cdev->dev.parent);
        /* Update schib - pom may have changed. */
        if (cio_update_schib(sch)) {
-               cdev->private->flags.donotify = 0;
-               ccw_device_done(cdev, DEV_STATE_NOT_OPER);
-               return;
+               err = -ENODEV;
+               goto callback;
        }
        /* Update lpm with verified path mask. */
        sch->lpm = sch->vpm;
        /* Repeat path verification? */
        if (cdev->private->flags.doverify) {
-               cdev->private->flags.doverify = 0;
                ccw_device_verify_start(cdev);
                return;
        }
+callback:
        switch (err) {
-       case -EOPNOTSUPP: /* path grouping not supported, just set online. */
-               cdev->private->options.pgroup = 0;
        case 0:
                ccw_device_done(cdev, DEV_STATE_ONLINE);
                /* Deliver fake irb to device driver, if needed. */
@@ -604,18 +477,20 @@ ccw_device_verify_done(struct ccw_device *cdev, int err)
                }
                break;
        case -ETIME:
+       case -EUSERS:
                /* Reset oper notify indication after verify error. */
                cdev->private->flags.donotify = 0;
                ccw_device_done(cdev, DEV_STATE_BOXED);
                break;
+       case -EACCES:
+               /* Reset oper notify indication after verify error. */
+               cdev->private->flags.donotify = 0;
+               ccw_device_done(cdev, DEV_STATE_DISCONNECTED);
+               break;
        default:
                /* Reset oper notify indication after verify error. */
                cdev->private->flags.donotify = 0;
-               if (cdev->online) {
-                       ccw_device_set_timeout(cdev, 0);
-                       dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
-               } else
-                       ccw_device_done(cdev, DEV_STATE_NOT_OPER);
+               ccw_device_done(cdev, DEV_STATE_NOT_OPER);
                break;
        }
 }
@@ -640,17 +515,9 @@ ccw_device_online(struct ccw_device *cdev)
                        dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
                return ret;
        }
-       /* Do we want to do path grouping? */
-       if (!cdev->private->options.pgroup) {
-               /* Start initial path verification. */
-               cdev->private->state = DEV_STATE_VERIFY;
-               cdev->private->flags.doverify = 0;
-               ccw_device_verify_start(cdev);
-               return 0;
-       }
-       /* Do a SensePGID first. */
-       cdev->private->state = DEV_STATE_SENSE_PGID;
-       ccw_device_sense_pgid_start(cdev);
+       /* Start initial path verification. */
+       cdev->private->state = DEV_STATE_VERIFY;
+       ccw_device_verify_start(cdev);
        return 0;
 }
 
@@ -666,7 +533,6 @@ ccw_device_disband_done(struct ccw_device *cdev, int err)
                break;
        default:
                cdev->private->flags.donotify = 0;
-               dev_fsm_event(cdev, DEV_EVENT_NOTOPER);
                ccw_device_done(cdev, DEV_STATE_NOT_OPER);
                break;
        }
@@ -703,7 +569,7 @@ ccw_device_offline(struct ccw_device *cdev)
        if (cdev->private->state != DEV_STATE_ONLINE)
                return -EINVAL;
        /* Are we doing path grouping? */
-       if (!cdev->private->options.pgroup) {
+       if (!cdev->private->flags.pgroup) {
                /* No, set state offline immediately. */
                ccw_device_done(cdev, DEV_STATE_OFFLINE);
                return 0;
@@ -715,43 +581,13 @@ ccw_device_offline(struct ccw_device *cdev)
 }
 
 /*
- * Handle timeout in device online/offline process.
- */
-static void
-ccw_device_onoff_timeout(struct ccw_device *cdev, enum dev_event dev_event)
-{
-       int ret;
-
-       ret = ccw_device_cancel_halt_clear(cdev);
-       switch (ret) {
-       case 0:
-               ccw_device_done(cdev, DEV_STATE_BOXED);
-               break;
-       case -ENODEV:
-               ccw_device_done(cdev, DEV_STATE_NOT_OPER);
-               break;
-       default:
-               ccw_device_set_timeout(cdev, 3*HZ);
-       }
-}
-
-/*
- * Handle not oper event in device recognition.
- */
-static void
-ccw_device_recog_notoper(struct ccw_device *cdev, enum dev_event dev_event)
-{
-       ccw_device_recog_done(cdev, DEV_STATE_NOT_OPER);
-}
-
-/*
  * Handle not operational event in non-special state.
  */
 static void ccw_device_generic_notoper(struct ccw_device *cdev,
                                       enum dev_event dev_event)
 {
        if (!ccw_device_notify(cdev, CIO_GONE))
-               ccw_device_schedule_sch_unregister(cdev);
+               ccw_device_sched_todo(cdev, CDEV_TODO_UNREG);
        else
                ccw_device_set_disconnected(cdev);
 }
@@ -802,11 +638,27 @@ ccw_device_online_verify(struct ccw_device *cdev, enum dev_event dev_event)
        }
        /* Device is idle, we can do the path verification. */
        cdev->private->state = DEV_STATE_VERIFY;
-       cdev->private->flags.doverify = 0;
        ccw_device_verify_start(cdev);
 }
 
 /*
+ * Handle path verification event in boxed state.
+ */
+static void ccw_device_boxed_verify(struct ccw_device *cdev,
+                                   enum dev_event dev_event)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+
+       if (cdev->online) {
+               if (cio_enable_subchannel(sch, (u32) (addr_t) sch))
+                       ccw_device_done(cdev, DEV_STATE_NOT_OPER);
+               else
+                       ccw_device_online_verify(cdev, dev_event);
+       } else
+               css_schedule_eval(sch->schid);
+}
+
+/*
  * Got an interrupt for a normal io (state online).
  */
 static void
@@ -904,12 +756,6 @@ ccw_device_w4sense(struct ccw_device *cdev, enum dev_event dev_event)
         */
        if (scsw_fctl(&irb->scsw) &
            (SCSW_FCTL_CLEAR_FUNC | SCSW_FCTL_HALT_FUNC)) {
-               /* Retry Basic Sense if requested. */
-               if (cdev->private->flags.intretry) {
-                       cdev->private->flags.intretry = 0;
-                       ccw_device_do_sense(cdev, irb);
-                       return;
-               }
                cdev->private->flags.dosense = 0;
                memset(&cdev->private->irb, 0, sizeof(struct irb));
                ccw_device_accumulate_irb(cdev, irb);
@@ -933,21 +779,6 @@ call_handler:
 }
 
 static void
-ccw_device_clear_verify(struct ccw_device *cdev, enum dev_event dev_event)
-{
-       struct irb *irb;
-
-       irb = (struct irb *) __LC_IRB;
-       /* Accumulate status. We don't do basic sense. */
-       ccw_device_accumulate_irb(cdev, irb);
-       /* Remember to clear irb to avoid residuals. */
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-       /* Try to start delayed device verification. */
-       ccw_device_online_verify(cdev, 0);
-       /* Note: Don't call handler for cio initiated clear! */
-}
-
-static void
 ccw_device_killing_irq(struct ccw_device *cdev, enum dev_event dev_event)
 {
        struct subchannel *sch;
@@ -1004,32 +835,6 @@ ccw_device_delay_verify(struct ccw_device *cdev, enum dev_event dev_event)
 }
 
 static void
-ccw_device_stlck_done(struct ccw_device *cdev, enum dev_event dev_event)
-{
-       struct irb *irb;
-
-       switch (dev_event) {
-       case DEV_EVENT_INTERRUPT:
-               irb = (struct irb *) __LC_IRB;
-               /* Check for unsolicited interrupt. */
-               if ((scsw_stctl(&irb->scsw) ==
-                    (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) &&
-                   (!scsw_cc(&irb->scsw)))
-                       /* FIXME: we should restart stlck here, but this
-                        * is extremely unlikely ... */
-                       goto out_wakeup;
-
-               ccw_device_accumulate_irb(cdev, irb);
-               /* We don't care about basic sense etc. */
-               break;
-       default: /* timeout */
-               break;
-       }
-out_wakeup:
-       wake_up(&cdev->private->wait_q);
-}
-
-static void
 ccw_device_start_id(struct ccw_device *cdev, enum dev_event dev_event)
 {
        struct subchannel *sch;
@@ -1038,10 +843,6 @@ ccw_device_start_id(struct ccw_device *cdev, enum dev_event dev_event)
        if (cio_enable_subchannel(sch, (u32)(addr_t)sch) != 0)
                /* Couldn't enable the subchannel for i/o. Sick device. */
                return;
-
-       /* After 60s the device recognition is considered to have failed. */
-       ccw_device_set_timeout(cdev, 60*HZ);
-
        cdev->private->state = DEV_STATE_DISCONNECTED_SENSE_ID;
        ccw_device_sense_id_start(cdev);
 }
@@ -1072,22 +873,20 @@ void ccw_device_trigger_reprobe(struct ccw_device *cdev)
 
        /* We should also udate ssd info, but this has to wait. */
        /* Check if this is another device which appeared on the same sch. */
-       if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) {
-               PREPARE_WORK(&cdev->private->kick_work,
-                            ccw_device_move_to_orphanage);
-               queue_work(slow_path_wq, &cdev->private->kick_work);
-       } else
+       if (sch->schib.pmcw.dev != cdev->private->dev_id.devno)
+               css_schedule_eval(sch->schid);
+       else
                ccw_device_start_id(cdev, 0);
 }
 
-static void
-ccw_device_offline_irq(struct ccw_device *cdev, enum dev_event dev_event)
+static void ccw_device_disabled_irq(struct ccw_device *cdev,
+                                   enum dev_event dev_event)
 {
        struct subchannel *sch;
 
        sch = to_subchannel(cdev->dev.parent);
        /*
-        * An interrupt in state offline means a previous disable was not
+        * An interrupt in a disabled state means a previous disable was not
         * successful - should not happen, but we try to disable again.
         */
        cio_disable_subchannel(sch);
@@ -1113,10 +912,7 @@ static void
 ccw_device_quiesce_done(struct ccw_device *cdev, enum dev_event dev_event)
 {
        ccw_device_set_timeout(cdev, 0);
-       if (dev_event == DEV_EVENT_NOTOPER)
-               cdev->private->state = DEV_STATE_NOT_OPER;
-       else
-               cdev->private->state = DEV_STATE_OFFLINE;
+       cdev->private->state = DEV_STATE_NOT_OPER;
        wake_up(&cdev->private->wait_q);
 }
 
@@ -1126,17 +922,11 @@ ccw_device_quiesce_timeout(struct ccw_device *cdev, enum dev_event dev_event)
        int ret;
 
        ret = ccw_device_cancel_halt_clear(cdev);
-       switch (ret) {
-       case 0:
-               cdev->private->state = DEV_STATE_OFFLINE;
-               wake_up(&cdev->private->wait_q);
-               break;
-       case -ENODEV:
+       if (ret == -EBUSY) {
+               ccw_device_set_timeout(cdev, HZ/10);
+       } else {
                cdev->private->state = DEV_STATE_NOT_OPER;
                wake_up(&cdev->private->wait_q);
-               break;
-       default:
-               ccw_device_set_timeout(cdev, HZ/10);
        }
 }
 
@@ -1150,50 +940,37 @@ ccw_device_nop(struct ccw_device *cdev, enum dev_event dev_event)
 }
 
 /*
- * Bug operation action. 
- */
-static void
-ccw_device_bug(struct ccw_device *cdev, enum dev_event dev_event)
-{
-       CIO_MSG_EVENT(0, "Internal state [%i][%i] not handled for device "
-                     "0.%x.%04x\n", cdev->private->state, dev_event,
-                     cdev->private->dev_id.ssid,
-                     cdev->private->dev_id.devno);
-       BUG();
-}
-
-/*
  * device statemachine
  */
 fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = {
        [DEV_STATE_NOT_OPER] = {
                [DEV_EVENT_NOTOPER]     = ccw_device_nop,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_bug,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_disabled_irq,
                [DEV_EVENT_TIMEOUT]     = ccw_device_nop,
                [DEV_EVENT_VERIFY]      = ccw_device_nop,
        },
        [DEV_STATE_SENSE_PGID] = {
-               [DEV_EVENT_NOTOPER]     = ccw_device_generic_notoper,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_sense_pgid_irq,
-               [DEV_EVENT_TIMEOUT]     = ccw_device_onoff_timeout,
+               [DEV_EVENT_NOTOPER]     = ccw_device_request_event,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_request_event,
+               [DEV_EVENT_TIMEOUT]     = ccw_device_request_event,
                [DEV_EVENT_VERIFY]      = ccw_device_nop,
        },
        [DEV_STATE_SENSE_ID] = {
-               [DEV_EVENT_NOTOPER]     = ccw_device_recog_notoper,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_sense_id_irq,
-               [DEV_EVENT_TIMEOUT]     = ccw_device_recog_timeout,
+               [DEV_EVENT_NOTOPER]     = ccw_device_request_event,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_request_event,
+               [DEV_EVENT_TIMEOUT]     = ccw_device_request_event,
                [DEV_EVENT_VERIFY]      = ccw_device_nop,
        },
        [DEV_STATE_OFFLINE] = {
                [DEV_EVENT_NOTOPER]     = ccw_device_generic_notoper,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_offline_irq,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_disabled_irq,
                [DEV_EVENT_TIMEOUT]     = ccw_device_nop,
                [DEV_EVENT_VERIFY]      = ccw_device_offline_verify,
        },
        [DEV_STATE_VERIFY] = {
-               [DEV_EVENT_NOTOPER]     = ccw_device_generic_notoper,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_verify_irq,
-               [DEV_EVENT_TIMEOUT]     = ccw_device_onoff_timeout,
+               [DEV_EVENT_NOTOPER]     = ccw_device_request_event,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_request_event,
+               [DEV_EVENT_TIMEOUT]     = ccw_device_request_event,
                [DEV_EVENT_VERIFY]      = ccw_device_delay_verify,
        },
        [DEV_STATE_ONLINE] = {
@@ -1209,24 +986,18 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = {
                [DEV_EVENT_VERIFY]      = ccw_device_online_verify,
        },
        [DEV_STATE_DISBAND_PGID] = {
-               [DEV_EVENT_NOTOPER]     = ccw_device_generic_notoper,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_disband_irq,
-               [DEV_EVENT_TIMEOUT]     = ccw_device_onoff_timeout,
+               [DEV_EVENT_NOTOPER]     = ccw_device_request_event,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_request_event,
+               [DEV_EVENT_TIMEOUT]     = ccw_device_request_event,
                [DEV_EVENT_VERIFY]      = ccw_device_nop,
        },
        [DEV_STATE_BOXED] = {
                [DEV_EVENT_NOTOPER]     = ccw_device_generic_notoper,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_stlck_done,
-               [DEV_EVENT_TIMEOUT]     = ccw_device_stlck_done,
-               [DEV_EVENT_VERIFY]      = ccw_device_nop,
-       },
-       /* states to wait for i/o completion before doing something */
-       [DEV_STATE_CLEAR_VERIFY] = {
-               [DEV_EVENT_NOTOPER]     = ccw_device_generic_notoper,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_clear_verify,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_nop,
                [DEV_EVENT_TIMEOUT]     = ccw_device_nop,
-               [DEV_EVENT_VERIFY]      = ccw_device_nop,
+               [DEV_EVENT_VERIFY]      = ccw_device_boxed_verify,
        },
+       /* states to wait for i/o completion before doing something */
        [DEV_STATE_TIMEOUT_KILL] = {
                [DEV_EVENT_NOTOPER]     = ccw_device_generic_notoper,
                [DEV_EVENT_INTERRUPT]   = ccw_device_killing_irq,
@@ -1243,13 +1014,13 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = {
        [DEV_STATE_DISCONNECTED] = {
                [DEV_EVENT_NOTOPER]     = ccw_device_nop,
                [DEV_EVENT_INTERRUPT]   = ccw_device_start_id,
-               [DEV_EVENT_TIMEOUT]     = ccw_device_bug,
+               [DEV_EVENT_TIMEOUT]     = ccw_device_nop,
                [DEV_EVENT_VERIFY]      = ccw_device_start_id,
        },
        [DEV_STATE_DISCONNECTED_SENSE_ID] = {
-               [DEV_EVENT_NOTOPER]     = ccw_device_recog_notoper,
-               [DEV_EVENT_INTERRUPT]   = ccw_device_sense_id_irq,
-               [DEV_EVENT_TIMEOUT]     = ccw_device_recog_timeout,
+               [DEV_EVENT_NOTOPER]     = ccw_device_request_event,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_request_event,
+               [DEV_EVENT_TIMEOUT]     = ccw_device_request_event,
                [DEV_EVENT_VERIFY]      = ccw_device_nop,
        },
        [DEV_STATE_CMFCHANGE] = {
@@ -1264,6 +1035,12 @@ fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS] = {
                [DEV_EVENT_TIMEOUT]     = ccw_device_update_cmfblock,
                [DEV_EVENT_VERIFY]      = ccw_device_update_cmfblock,
        },
+       [DEV_STATE_STEAL_LOCK] = {
+               [DEV_EVENT_NOTOPER]     = ccw_device_request_event,
+               [DEV_EVENT_INTERRUPT]   = ccw_device_request_event,
+               [DEV_EVENT_TIMEOUT]     = ccw_device_request_event,
+               [DEV_EVENT_VERIFY]      = ccw_device_nop,
+       },
 };
 
 EXPORT_SYMBOL_GPL(ccw_device_set_timeout);
index 1bdaa61..78a0b43 100644 (file)
@@ -1,40 +1,39 @@
 /*
- * drivers/s390/cio/device_id.c
+ *  CCW device SENSE ID I/O handling.
  *
- *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
- *                      IBM Corporation
- *    Author(s): Cornelia Huck (cornelia.huck@de.ibm.com)
- *              Martin Schwidefsky (schwidefsky@de.ibm.com)
- *
- * Sense ID functions.
+ *    Copyright IBM Corp. 2002,2009
+ *    Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *              Martin Schwidefsky <schwidefsky@de.ibm.com>
+ *              Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
  */
 
-#include <linux/module.h>
-#include <linux/init.h>
 #include <linux/kernel.h>
-
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/errno.h>
 #include <asm/ccwdev.h>
-#include <asm/delay.h>
+#include <asm/setup.h>
 #include <asm/cio.h>
-#include <asm/lowcore.h>
 #include <asm/diag.h>
 
 #include "cio.h"
 #include "cio_debug.h"
-#include "css.h"
 #include "device.h"
-#include "ioasm.h"
 #include "io_sch.h"
 
+#define SENSE_ID_RETRIES       256
+#define SENSE_ID_TIMEOUT       (10 * HZ)
+#define SENSE_ID_MIN_LEN       4
+#define SENSE_ID_BASIC_LEN     7
+
 /**
- * vm_vdev_to_cu_type - Convert vm virtual device into control unit type
- *                     for certain devices.
- * @class: virtual device class
- * @type: virtual device type
+ * diag210_to_senseid - convert diag 0x210 data to sense id information
+ * @senseid: sense id
+ * @diag: diag 0x210 data
  *
- * Returns control unit type if a match was made or %0xffff otherwise.
+ * Return 0 on success, non-zero otherwise.
  */
-static int vm_vdev_to_cu_type(int class, int type)
+static int diag210_to_senseid(struct senseid *senseid, struct diag210 *diag)
 {
        static struct {
                int class, type, cu_type;
@@ -71,253 +70,153 @@ static int vm_vdev_to_cu_type(int class, int type)
        };
        int i;
 
-       for (i = 0; i < ARRAY_SIZE(vm_devices); i++)
-               if (class == vm_devices[i].class && type == vm_devices[i].type)
-                       return vm_devices[i].cu_type;
+       /* Special case for osa devices. */
+       if (diag->vrdcvcla == 0x02 && diag->vrdcvtyp == 0x20) {
+               senseid->cu_type = 0x3088;
+               senseid->cu_model = 0x60;
+               senseid->reserved = 0xff;
+               return 0;
+       }
+       for (i = 0; i < ARRAY_SIZE(vm_devices); i++) {
+               if (diag->vrdcvcla == vm_devices[i].class &&
+                   diag->vrdcvtyp == vm_devices[i].type) {
+                       senseid->cu_type = vm_devices[i].cu_type;
+                       senseid->reserved = 0xff;
+                       return 0;
+               }
+       }
 
-       return 0xffff;
+       return -ENODEV;
 }
 
 /**
- * diag_get_dev_info - retrieve device information via DIAG X'210'
- * @devno: device number
- * @ps: pointer to sense ID data area
+ * diag_get_dev_info - retrieve device information via diag 0x210
+ * @cdev: ccw device
  *
  * Returns zero on success, non-zero otherwise.
  */
-static int diag_get_dev_info(u16 devno, struct senseid *ps)
+static int diag210_get_dev_info(struct ccw_device *cdev)
 {
+       struct ccw_dev_id *dev_id = &cdev->private->dev_id;
+       struct senseid *senseid = &cdev->private->senseid;
        struct diag210 diag_data;
-       int ccode;
-
-       CIO_TRACE_EVENT (4, "VMvdinf");
-
-       diag_data = (struct diag210) {
-               .vrdcdvno = devno,
-               .vrdclen = sizeof (diag_data),
-       };
-
-       ccode = diag210 (&diag_data);
-       if ((ccode == 0) || (ccode == 2)) {
-               ps->reserved = 0xff;
-
-               /* Special case for osa devices. */
-               if (diag_data.vrdcvcla == 0x02 && diag_data.vrdcvtyp == 0x20) {
-                       ps->cu_type = 0x3088;
-                       ps->cu_model = 0x60;
-                       return 0;
-               }
-               ps->cu_type = vm_vdev_to_cu_type(diag_data.vrdcvcla,
-                                               diag_data.vrdcvtyp);
-               if (ps->cu_type != 0xffff)
-                       return 0;
-       }
-
-       CIO_MSG_EVENT(0, "DIAG X'210' for device %04X returned (cc = %d):"
-                     "vdev class : %02X, vdev type : %04X \n ...  "
-                     "rdev class : %02X, rdev type : %04X, "
-                     "rdev model: %02X\n",
-                     devno, ccode,
-                     diag_data.vrdcvcla, diag_data.vrdcvtyp,
-                     diag_data.vrdcrccl, diag_data.vrdccrty,
-                     diag_data.vrdccrmd);
-
+       int rc;
+
+       if (dev_id->ssid != 0)
+               return -ENODEV;
+       memset(&diag_data, 0, sizeof(diag_data));
+       diag_data.vrdcdvno      = dev_id->devno;
+       diag_data.vrdclen       = sizeof(diag_data);
+       rc = diag210(&diag_data);
+       CIO_TRACE_EVENT(4, "diag210");
+       CIO_HEX_EVENT(4, &rc, sizeof(rc));
+       CIO_HEX_EVENT(4, &diag_data, sizeof(diag_data));
+       if (rc != 0 && rc != 2)
+               goto err_failed;
+       if (diag210_to_senseid(senseid, &diag_data))
+               goto err_unknown;
+       return 0;
+
+err_unknown:
+       CIO_MSG_EVENT(0, "snsid: device 0.%x.%04x: unknown diag210 data\n",
+                     dev_id->ssid, dev_id->devno);
+       return -ENODEV;
+err_failed:
+       CIO_MSG_EVENT(0, "snsid: device 0.%x.%04x: diag210 failed (rc=%d)\n",
+                     dev_id->ssid, dev_id->devno, rc);
        return -ENODEV;
 }
 
 /*
- * Start Sense ID helper function.
- * Try to obtain the 'control unit'/'device type' information
- *  associated with the subchannel.
+ * Initialize SENSE ID data.
  */
-static int
-__ccw_device_sense_id_start(struct ccw_device *cdev)
-{
-       struct subchannel *sch;
-       struct ccw1 *ccw;
-       int ret;
-
-       sch = to_subchannel(cdev->dev.parent);
-       /* Setup sense channel program. */
-       ccw = cdev->private->iccws;
-       ccw->cmd_code = CCW_CMD_SENSE_ID;
-       ccw->cda = (__u32) __pa (&cdev->private->senseid);
-       ccw->count = sizeof (struct senseid);
-       ccw->flags = CCW_FLAG_SLI;
-
-       /* Reset device status. */
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-
-       /* Try on every path. */
-       ret = -ENODEV;
-       while (cdev->private->imask != 0) {
-               cdev->private->senseid.cu_type = 0xFFFF;
-               if ((sch->opm & cdev->private->imask) != 0 &&
-                   cdev->private->iretry > 0) {
-                       cdev->private->iretry--;
-                       /* Reset internal retry indication. */
-                       cdev->private->flags.intretry = 0;
-                       ret = cio_start (sch, cdev->private->iccws,
-                                        cdev->private->imask);
-                       /* ret is 0, -EBUSY, -EACCES or -ENODEV */
-                       if (ret != -EACCES)
-                               return ret;
-               }
-               cdev->private->imask >>= 1;
-               cdev->private->iretry = 5;
-       }
-       return ret;
-}
-
-void
-ccw_device_sense_id_start(struct ccw_device *cdev)
+static void snsid_init(struct ccw_device *cdev)
 {
-       int ret;
-
-       memset (&cdev->private->senseid, 0, sizeof (struct senseid));
-       cdev->private->imask = 0x80;
-       cdev->private->iretry = 5;
-       ret = __ccw_device_sense_id_start(cdev);
-       if (ret && ret != -EBUSY)
-               ccw_device_sense_id_done(cdev, ret);
+       cdev->private->flags.esid = 0;
+       memset(&cdev->private->senseid, 0, sizeof(cdev->private->senseid));
+       cdev->private->senseid.cu_type = 0xffff;
 }
 
 /*
- * Called from interrupt context to check if a valid answer
- * to Sense ID was received.
+ * Check for complete SENSE ID data.
  */
-static int
-ccw_device_check_sense_id(struct ccw_device *cdev)
+static int snsid_check(struct ccw_device *cdev, void *data)
 {
-       struct subchannel *sch;
-       struct irb *irb;
-
-       sch = to_subchannel(cdev->dev.parent);
-       irb = &cdev->private->irb;
-
-       /* Check the error cases. */
-       if (irb->scsw.cmd.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
-               /* Retry Sense ID if requested. */
-               if (cdev->private->flags.intretry) {
-                       cdev->private->flags.intretry = 0;
-                       return -EAGAIN;
-               }
-               return -ETIME;
-       }
-       if (irb->esw.esw0.erw.cons && (irb->ecw[0] & SNS0_CMD_REJECT)) {
-               /*
-                * if the device doesn't support the SenseID
-                *  command further retries wouldn't help ...
-                * NB: We don't check here for intervention required like we
-                *     did before, because tape devices with no tape inserted
-                *     may present this status *in conjunction with* the
-                *     sense id information. So, for intervention required,
-                *     we use the "whack it until it talks" strategy...
-                */
-               CIO_MSG_EVENT(0, "SenseID : device %04x on Subchannel "
-                             "0.%x.%04x reports cmd reject\n",
-                             cdev->private->dev_id.devno, sch->schid.ssid,
-                             sch->schid.sch_no);
+       struct cmd_scsw *scsw = &cdev->private->irb.scsw.cmd;
+       int len = sizeof(struct senseid) - scsw->count;
+
+       /* Check for incomplete SENSE ID data. */
+       if (len < SENSE_ID_MIN_LEN)
+               goto out_restart;
+       if (cdev->private->senseid.cu_type == 0xffff)
+               goto out_restart;
+       /* Check for incompatible SENSE ID data. */
+       if (cdev->private->senseid.reserved != 0xff)
                return -EOPNOTSUPP;
-       }
-       if (irb->esw.esw0.erw.cons) {
-               CIO_MSG_EVENT(2, "SenseID : UC on dev 0.%x.%04x, "
-                             "lpum %02X, cnt %02d, sns :"
-                             " %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
-                             cdev->private->dev_id.ssid,
-                             cdev->private->dev_id.devno,
-                             irb->esw.esw0.sublog.lpum,
-                             irb->esw.esw0.erw.scnt,
-                             irb->ecw[0], irb->ecw[1],
-                             irb->ecw[2], irb->ecw[3],
-                             irb->ecw[4], irb->ecw[5],
-                             irb->ecw[6], irb->ecw[7]);
-               return -EAGAIN;
-       }
-       if (irb->scsw.cmd.cc == 3) {
-               u8 lpm;
+       /* Check for extended-identification information. */
+       if (len > SENSE_ID_BASIC_LEN)
+               cdev->private->flags.esid = 1;
+       return 0;
 
-               lpm = to_io_private(sch)->orb.cmd.lpm;
-               if ((lpm & sch->schib.pmcw.pim & sch->schib.pmcw.pam) != 0)
-                       CIO_MSG_EVENT(4, "SenseID : path %02X for device %04x "
-                                     "on subchannel 0.%x.%04x is "
-                                     "'not operational'\n", lpm,
-                                     cdev->private->dev_id.devno,
-                                     sch->schid.ssid, sch->schid.sch_no);
-               return -EACCES;
-       }
-
-       /* Did we get a proper answer ? */
-       if (irb->scsw.cmd.cc == 0 && cdev->private->senseid.cu_type != 0xFFFF &&
-           cdev->private->senseid.reserved == 0xFF) {
-               if (irb->scsw.cmd.count < sizeof(struct senseid) - 8)
-                       cdev->private->flags.esid = 1;
-               return 0; /* Success */
-       }
-
-       /* Hmm, whatever happened, try again. */
-       CIO_MSG_EVENT(2, "SenseID : start_IO() for device %04x on "
-                     "subchannel 0.%x.%04x returns status %02X%02X\n",
-                     cdev->private->dev_id.devno, sch->schid.ssid,
-                     sch->schid.sch_no,
-                     irb->scsw.cmd.dstat, irb->scsw.cmd.cstat);
+out_restart:
+       snsid_init(cdev);
        return -EAGAIN;
 }
 
 /*
- * Got interrupt for Sense ID.
+ * Process SENSE ID request result.
  */
-void
-ccw_device_sense_id_irq(struct ccw_device *cdev, enum dev_event dev_event)
+static void snsid_callback(struct ccw_device *cdev, void *data, int rc)
 {
-       struct subchannel *sch;
-       struct irb *irb;
-       int ret;
-
-       sch = to_subchannel(cdev->dev.parent);
-       irb = (struct irb *) __LC_IRB;
-       /* Retry sense id, if needed. */
-       if (irb->scsw.cmd.stctl ==
-           (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
-               if ((irb->scsw.cmd.cc == 1) || !irb->scsw.cmd.actl) {
-                       ret = __ccw_device_sense_id_start(cdev);
-                       if (ret && ret != -EBUSY)
-                               ccw_device_sense_id_done(cdev, ret);
+       struct ccw_dev_id *id = &cdev->private->dev_id;
+       struct senseid *senseid = &cdev->private->senseid;
+       int vm = 0;
+
+       if (rc && MACHINE_IS_VM) {
+               /* Try diag 0x210 fallback on z/VM. */
+               snsid_init(cdev);
+               if (diag210_get_dev_info(cdev) == 0) {
+                       rc = 0;
+                       vm = 1;
                }
-               return;
        }
-       if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
-               return;
-       ret = ccw_device_check_sense_id(cdev);
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-       switch (ret) {
-       /* 0, -ETIME, -EOPNOTSUPP, -EAGAIN or -EACCES */
-       case 0:                 /* Sense id succeeded. */
-       case -ETIME:            /* Sense id stopped by timeout. */
-               ccw_device_sense_id_done(cdev, ret);
-               break;
-       case -EACCES:           /* channel is not operational. */
-               sch->lpm &= ~cdev->private->imask;
-               cdev->private->imask >>= 1;
-               cdev->private->iretry = 5;
-               /* fall through. */
-       case -EAGAIN:           /* try again. */
-               ret = __ccw_device_sense_id_start(cdev);
-               if (ret == 0 || ret == -EBUSY)
-                       break;
-               /* fall through. */
-       default:                /* Sense ID failed. Try asking VM. */
-               if (MACHINE_IS_VM)
-                       ret = diag_get_dev_info(cdev->private->dev_id.devno,
-                                               &cdev->private->senseid);
-               else
-                       /*
-                        * If we can't couldn't identify the device type we
-                        *  consider the device "not operational".
-                        */
-                       ret = -ENODEV;
+       CIO_MSG_EVENT(2, "snsid: device 0.%x.%04x: rc=%d %04x/%02x "
+                     "%04x/%02x%s\n", id->ssid, id->devno, rc,
+                     senseid->cu_type, senseid->cu_model, senseid->dev_type,
+                     senseid->dev_model, vm ? " (diag210)" : "");
+       ccw_device_sense_id_done(cdev, rc);
+}
 
-               ccw_device_sense_id_done(cdev, ret);
-               break;
-       }
+/**
+ * ccw_device_sense_id_start - perform SENSE ID
+ * @cdev: ccw device
+ *
+ * Execute a SENSE ID channel program on @cdev to update its sense id
+ * information. When finished, call ccw_device_sense_id_done with a
+ * return code specifying the result.
+ */
+void ccw_device_sense_id_start(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+       struct ccw1 *cp = cdev->private->iccws;
+
+       CIO_TRACE_EVENT(4, "snsid");
+       CIO_HEX_EVENT(4, &cdev->private->dev_id, sizeof(cdev->private->dev_id));
+       /* Data setup. */
+       snsid_init(cdev);
+       /* Channel program setup. */
+       cp->cmd_code    = CCW_CMD_SENSE_ID;
+       cp->cda         = (u32) (addr_t) &cdev->private->senseid;
+       cp->count       = sizeof(struct senseid);
+       cp->flags       = CCW_FLAG_SLI;
+       /* Request setup. */
+       memset(req, 0, sizeof(*req));
+       req->cp         = cp;
+       req->timeout    = SENSE_ID_TIMEOUT;
+       req->maxretries = SENSE_ID_RETRIES;
+       req->lpm        = sch->schib.pmcw.pam & sch->opm;
+       req->check      = snsid_check;
+       req->callback   = snsid_callback;
+       ccw_request_start(cdev);
 }
index 2d0efee..6da8454 100644 (file)
@@ -11,6 +11,7 @@
 #include <linux/list.h>
 #include <linux/device.h>
 #include <linux/delay.h>
+#include <linux/completion.h>
 
 #include <asm/ccwdev.h>
 #include <asm/idals.h>
@@ -46,6 +47,7 @@ int ccw_device_set_options_mask(struct ccw_device *cdev, unsigned long flags)
        cdev->private->options.repall = (flags & CCWDEV_REPORT_ALL) != 0;
        cdev->private->options.pgroup = (flags & CCWDEV_DO_PATHGROUP) != 0;
        cdev->private->options.force = (flags & CCWDEV_ALLOW_FORCE) != 0;
+       cdev->private->options.mpath = (flags & CCWDEV_DO_MULTIPATH) != 0;
        return 0;
 }
 
@@ -74,6 +76,7 @@ int ccw_device_set_options(struct ccw_device *cdev, unsigned long flags)
        cdev->private->options.repall |= (flags & CCWDEV_REPORT_ALL) != 0;
        cdev->private->options.pgroup |= (flags & CCWDEV_DO_PATHGROUP) != 0;
        cdev->private->options.force |= (flags & CCWDEV_ALLOW_FORCE) != 0;
+       cdev->private->options.mpath |= (flags & CCWDEV_DO_MULTIPATH) != 0;
        return 0;
 }
 
@@ -90,9 +93,34 @@ void ccw_device_clear_options(struct ccw_device *cdev, unsigned long flags)
        cdev->private->options.repall &= (flags & CCWDEV_REPORT_ALL) == 0;
        cdev->private->options.pgroup &= (flags & CCWDEV_DO_PATHGROUP) == 0;
        cdev->private->options.force &= (flags & CCWDEV_ALLOW_FORCE) == 0;
+       cdev->private->options.mpath &= (flags & CCWDEV_DO_MULTIPATH) == 0;
 }
 
 /**
+ * ccw_device_is_pathgroup - determine if paths to this device are grouped
+ * @cdev: ccw device
+ *
+ * Return non-zero if there is a path group, zero otherwise.
+ */
+int ccw_device_is_pathgroup(struct ccw_device *cdev)
+{
+       return cdev->private->flags.pgroup;
+}
+EXPORT_SYMBOL(ccw_device_is_pathgroup);
+
+/**
+ * ccw_device_is_multipath - determine if device is operating in multipath mode
+ * @cdev: ccw device
+ *
+ * Return non-zero if device is operating in multipath mode, zero otherwise.
+ */
+int ccw_device_is_multipath(struct ccw_device *cdev)
+{
+       return cdev->private->flags.mpath;
+}
+EXPORT_SYMBOL(ccw_device_is_multipath);
+
+/**
  * ccw_device_clear() - terminate I/O request processing
  * @cdev: target ccw device
  * @intparm: interruption parameter; value is only used if no I/O is
@@ -167,8 +195,7 @@ int ccw_device_start_key(struct ccw_device *cdev, struct ccw1 *cpa,
                return -EINVAL;
        if (cdev->private->state == DEV_STATE_NOT_OPER)
                return -ENODEV;
-       if (cdev->private->state == DEV_STATE_VERIFY ||
-           cdev->private->state == DEV_STATE_CLEAR_VERIFY) {
+       if (cdev->private->state == DEV_STATE_VERIFY) {
                /* Remember to fake irb when finished. */
                if (!cdev->private->flags.fake_irb) {
                        cdev->private->flags.fake_irb = 1;
@@ -478,74 +505,65 @@ __u8 ccw_device_get_path_mask(struct ccw_device *cdev)
        return sch->lpm;
 }
 
-/*
- * Try to break the lock on a boxed device.
- */
-int
-ccw_device_stlck(struct ccw_device *cdev)
-{
-       void *buf, *buf2;
-       unsigned long flags;
-       struct subchannel *sch;
-       int ret;
+struct stlck_data {
+       struct completion done;
+       int rc;
+};
 
-       if (!cdev)
-               return -ENODEV;
+void ccw_device_stlck_done(struct ccw_device *cdev, void *data, int rc)
+{
+       struct stlck_data *sdata = data;
 
-       if (cdev->drv && !cdev->private->options.force)
-               return -EINVAL;
+       sdata->rc = rc;
+       complete(&sdata->done);
+}
 
-       sch = to_subchannel(cdev->dev.parent);
-       
-       CIO_TRACE_EVENT(2, "stl lock");
-       CIO_TRACE_EVENT(2, dev_name(&cdev->dev));
+/*
+ * Perform unconditional reserve + release.
+ */
+int ccw_device_stlck(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct stlck_data data;
+       u8 *buffer;
+       int rc;
 
-       buf = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL);
-       if (!buf)
-               return -ENOMEM;
-       buf2 = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL);
-       if (!buf2) {
-               kfree(buf);
-               return -ENOMEM;
+       /* Check if steal lock operation is valid for this device. */
+       if (cdev->drv) {
+               if (!cdev->private->options.force)
+                       return -EINVAL;
        }
-       spin_lock_irqsave(sch->lock, flags);
-       ret = cio_enable_subchannel(sch, (u32)(addr_t)sch);
-       if (ret)
-               goto out_unlock;
-       /*
-        * Setup ccw. We chain an unconditional reserve and a release so we
-        * only break the lock.
-        */
-       cdev->private->iccws[0].cmd_code = CCW_CMD_STLCK;
-       cdev->private->iccws[0].cda = (__u32) __pa(buf);
-       cdev->private->iccws[0].count = 32;
-       cdev->private->iccws[0].flags = CCW_FLAG_CC;
-       cdev->private->iccws[1].cmd_code = CCW_CMD_RELEASE;
-       cdev->private->iccws[1].cda = (__u32) __pa(buf2);
-       cdev->private->iccws[1].count = 32;
-       cdev->private->iccws[1].flags = 0;
-       ret = cio_start(sch, cdev->private->iccws, 0);
-       if (ret) {
-               cio_disable_subchannel(sch); //FIXME: return code?
+       buffer = kzalloc(64, GFP_DMA | GFP_KERNEL);
+       if (!buffer)
+               return -ENOMEM;
+       init_completion(&data.done);
+       data.rc = -EIO;
+       spin_lock_irq(sch->lock);
+       rc = cio_enable_subchannel(sch, (u32) (addr_t) sch);
+       if (rc)
                goto out_unlock;
+       /* Perform operation. */
+       cdev->private->state = DEV_STATE_STEAL_LOCK,
+       ccw_device_stlck_start(cdev, &data, &buffer[0], &buffer[32]);
+       spin_unlock_irq(sch->lock);
+       /* Wait for operation to finish. */
+       if (wait_for_completion_interruptible(&data.done)) {
+               /* Got a signal. */
+               spin_lock_irq(sch->lock);
+               ccw_request_cancel(cdev);
+               spin_unlock_irq(sch->lock);
+               wait_for_completion(&data.done);
        }
-       cdev->private->irb.scsw.cmd.actl |= SCSW_ACTL_START_PEND;
-       spin_unlock_irqrestore(sch->lock, flags);
-       wait_event(cdev->private->wait_q,
-                  cdev->private->irb.scsw.cmd.actl == 0);
-       spin_lock_irqsave(sch->lock, flags);
-       cio_disable_subchannel(sch); //FIXME: return code?
-       if ((cdev->private->irb.scsw.cmd.dstat !=
-            (DEV_STAT_CHN_END|DEV_STAT_DEV_END)) ||
-           (cdev->private->irb.scsw.cmd.cstat != 0))
-               ret = -EIO;
-       /* Clear irb. */
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
+       rc = data.rc;
+       /* Check results. */
+       spin_lock_irq(sch->lock);
+       cio_disable_subchannel(sch);
+       cdev->private->state = DEV_STATE_BOXED;
 out_unlock:
-       kfree(buf);
-       kfree(buf2);
-       spin_unlock_irqrestore(sch->lock, flags);
-       return ret;
+       spin_unlock_irq(sch->lock);
+       kfree(buffer);
+
+       return rc;
 }
 
 void *ccw_device_get_chp_desc(struct ccw_device *cdev, int chp_no)
index fc5ca1d..aad188e 100644 (file)
 /*
- * drivers/s390/cio/device_pgid.c
+ *  CCW device PGID and path verification I/O handling.
  *
- *    Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
- *                      IBM Corporation
- *    Author(s): Cornelia Huck (cornelia.huck@de.ibm.com)
- *              Martin Schwidefsky (schwidefsky@de.ibm.com)
- *
- * Path Group ID functions.
+ *    Copyright IBM Corp. 2002,2009
+ *    Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ *              Martin Schwidefsky <schwidefsky@de.ibm.com>
+ *              Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
  */
 
-#include <linux/module.h>
-#include <linux/init.h>
-
+#include <linux/kernel.h>
+#include <linux/string.h>
+#include <linux/types.h>
+#include <linux/errno.h>
+#include <linux/bitops.h>
 #include <asm/ccwdev.h>
 #include <asm/cio.h>
-#include <asm/delay.h>
-#include <asm/lowcore.h>
 
 #include "cio.h"
 #include "cio_debug.h"
-#include "css.h"
 #include "device.h"
-#include "ioasm.h"
 #include "io_sch.h"
 
+#define PGID_RETRIES   256
+#define PGID_TIMEOUT   (10 * HZ)
+
 /*
- * Helper function called from interrupt context to decide whether an
- * operation should be tried again.
+ * Process path verification data and report result.
  */
-static int __ccw_device_should_retry(union scsw *scsw)
+static void verify_done(struct ccw_device *cdev, int rc)
 {
-       /* CC is only valid if start function bit is set. */
-       if ((scsw->cmd.fctl & SCSW_FCTL_START_FUNC) && scsw->cmd.cc == 1)
-               return 1;
-       /* No more activity. For sense and set PGID we stubbornly try again. */
-       if (!scsw->cmd.actl)
-               return 1;
-       return 0;
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_dev_id *id = &cdev->private->dev_id;
+       int mpath = cdev->private->flags.mpath;
+       int pgroup = cdev->private->flags.pgroup;
+
+       if (rc)
+               goto out;
+       /* Ensure consistent multipathing state at device and channel. */
+       if (sch->config.mp != mpath) {
+               sch->config.mp = mpath;
+               rc = cio_commit_config(sch);
+       }
+out:
+       CIO_MSG_EVENT(2, "vrfy: device 0.%x.%04x: rc=%d pgroup=%d mpath=%d "
+                        "vpm=%02x\n", id->ssid, id->devno, rc, pgroup, mpath,
+                        sch->vpm);
+       ccw_device_verify_done(cdev, rc);
 }
 
 /*
- * Start Sense Path Group ID helper function. Used in ccw_device_recog
- * and ccw_device_sense_pgid.
+ * Create channel program to perform a NOOP.
  */
-static int
-__ccw_device_sense_pgid_start(struct ccw_device *cdev)
+static void nop_build_cp(struct ccw_device *cdev)
 {
-       struct subchannel *sch;
-       struct ccw1 *ccw;
-       int ret;
-       int i;
-
-       sch = to_subchannel(cdev->dev.parent);
-       /* Return if we already checked on all paths. */
-       if (cdev->private->imask == 0)
-               return (sch->lpm == 0) ? -ENODEV : -EACCES;
-       i = 8 - ffs(cdev->private->imask);
-
-       /* Setup sense path group id channel program. */
-       ccw = cdev->private->iccws;
-       ccw->cmd_code = CCW_CMD_SENSE_PGID;
-       ccw->count = sizeof (struct pgid);
-       ccw->flags = CCW_FLAG_SLI;
-
-       /* Reset device status. */
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-       /* Try on every path. */
-       ret = -ENODEV;
-       while (cdev->private->imask != 0) {
-               /* Try every path multiple times. */
-               ccw->cda = (__u32) __pa (&cdev->private->pgid[i]);
-               if (cdev->private->iretry > 0) {
-                       cdev->private->iretry--;
-                       /* Reset internal retry indication. */
-                       cdev->private->flags.intretry = 0;
-                       ret = cio_start (sch, cdev->private->iccws, 
-                                        cdev->private->imask);
-                       /* ret is 0, -EBUSY, -EACCES or -ENODEV */
-                       if (ret != -EACCES)
-                               return ret;
-                       CIO_MSG_EVENT(3, "SNID - Device %04x on Subchannel "
-                                     "0.%x.%04x, lpm %02X, became 'not "
-                                     "operational'\n",
-                                     cdev->private->dev_id.devno,
-                                     sch->schid.ssid,
-                                     sch->schid.sch_no, cdev->private->imask);
-
-               }
-               cdev->private->imask >>= 1;
-               cdev->private->iretry = 5;
-               i++;
-       }
-
-       return ret;
+       struct ccw_request *req = &cdev->private->req;
+       struct ccw1 *cp = cdev->private->iccws;
+
+       cp->cmd_code    = CCW_CMD_NOOP;
+       cp->cda         = 0;
+       cp->count       = 0;
+       cp->flags       = CCW_FLAG_SLI;
+       req->cp         = cp;
 }
 
-void
-ccw_device_sense_pgid_start(struct ccw_device *cdev)
+/*
+ * Perform NOOP on a single path.
+ */
+static void nop_do(struct ccw_device *cdev)
 {
-       int ret;
-
-       /* Set a timeout of 60s */
-       ccw_device_set_timeout(cdev, 60*HZ);
-
-       cdev->private->state = DEV_STATE_SENSE_PGID;
-       cdev->private->imask = 0x80;
-       cdev->private->iretry = 5;
-       memset (&cdev->private->pgid, 0, sizeof (cdev->private->pgid));
-       ret = __ccw_device_sense_pgid_start(cdev);
-       if (ret && ret != -EBUSY)
-               ccw_device_sense_pgid_done(cdev, ret);
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+
+       /* Adjust lpm. */
+       req->lpm = lpm_adjust(req->lpm, sch->schib.pmcw.pam & sch->opm);
+       if (!req->lpm)
+               goto out_nopath;
+       nop_build_cp(cdev);
+       ccw_request_start(cdev);
+       return;
+
+out_nopath:
+       verify_done(cdev, sch->vpm ? 0 : -EACCES);
 }
 
 /*
- * Called from interrupt context to check if a valid answer
- * to Sense Path Group ID was received.
+ * Adjust NOOP I/O status.
  */
-static int
-__ccw_device_check_sense_pgid(struct ccw_device *cdev)
+static enum io_status nop_filter(struct ccw_device *cdev, void *data,
+                                struct irb *irb, enum io_status status)
 {
-       struct subchannel *sch;
-       struct irb *irb;
-       int i;
-
-       sch = to_subchannel(cdev->dev.parent);
-       irb = &cdev->private->irb;
-       if (irb->scsw.cmd.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
-               /* Retry Sense PGID if requested. */
-               if (cdev->private->flags.intretry) {
-                       cdev->private->flags.intretry = 0;
-                       return -EAGAIN;
-               }
-               return -ETIME;
-       }
-       if (irb->esw.esw0.erw.cons &&
-           (irb->ecw[0]&(SNS0_CMD_REJECT|SNS0_INTERVENTION_REQ))) {
-               /*
-                * If the device doesn't support the Sense Path Group ID
-                *  command further retries wouldn't help ...
-                */
-               return -EOPNOTSUPP;
-       }
-       if (irb->esw.esw0.erw.cons) {
-               CIO_MSG_EVENT(2, "SNID - device 0.%x.%04x, unit check, "
-                             "lpum %02X, cnt %02d, sns : "
-                             "%02X%02X%02X%02X %02X%02X%02X%02X ...\n",
-                             cdev->private->dev_id.ssid,
-                             cdev->private->dev_id.devno,
-                             irb->esw.esw0.sublog.lpum,
-                             irb->esw.esw0.erw.scnt,
-                             irb->ecw[0], irb->ecw[1],
-                             irb->ecw[2], irb->ecw[3],
-                             irb->ecw[4], irb->ecw[5],
-                             irb->ecw[6], irb->ecw[7]);
-               return -EAGAIN;
-       }
-       if (irb->scsw.cmd.cc == 3) {
-               u8 lpm;
-
-               lpm = to_io_private(sch)->orb.cmd.lpm;
-               CIO_MSG_EVENT(3, "SNID - Device %04x on Subchannel 0.%x.%04x,"
-                             " lpm %02X, became 'not operational'\n",
-                             cdev->private->dev_id.devno, sch->schid.ssid,
-                             sch->schid.sch_no, lpm);
-               return -EACCES;
-       }
-       i = 8 - ffs(cdev->private->imask);
-       if (cdev->private->pgid[i].inf.ps.state2 == SNID_STATE2_RESVD_ELSE) {
-               CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel 0.%x.%04x "
-                             "is reserved by someone else\n",
-                             cdev->private->dev_id.devno, sch->schid.ssid,
-                             sch->schid.sch_no);
-               return -EUSERS;
-       }
-       return 0;
+       /* Only subchannel status might indicate a path error. */
+       if (status == IO_STATUS_ERROR && irb->scsw.cmd.cstat == 0)
+               return IO_DONE;
+       return status;
 }
 
 /*
- * Got interrupt for Sense Path Group ID.
+ * Process NOOP request result for a single path.
  */
-void
-ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event)
+static void nop_callback(struct ccw_device *cdev, void *data, int rc)
 {
-       struct subchannel *sch;
-       struct irb *irb;
-       int ret;
-
-       irb = (struct irb *) __LC_IRB;
-
-       if (irb->scsw.cmd.stctl ==
-           (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
-               if (__ccw_device_should_retry(&irb->scsw)) {
-                       ret = __ccw_device_sense_pgid_start(cdev);
-                       if (ret && ret != -EBUSY)
-                               ccw_device_sense_pgid_done(cdev, ret);
-               }
-               return;
-       }
-       if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
-               return;
-       sch = to_subchannel(cdev->dev.parent);
-       ret = __ccw_device_check_sense_pgid(cdev);
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-       switch (ret) {
-       /* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */
-       case -EOPNOTSUPP:       /* Sense Path Group ID not supported */
-               ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP);
-               break;
-       case -ETIME:            /* Sense path group id stopped by timeout. */
-               ccw_device_sense_pgid_done(cdev, -ETIME);
-               break;
-       case -EACCES:           /* channel is not operational. */
-               sch->lpm &= ~cdev->private->imask;
-               /* Fall through. */
-       case 0:                 /* Sense Path Group ID successful. */
-               cdev->private->imask >>= 1;
-               cdev->private->iretry = 5;
-               /* Fall through. */
-       case -EAGAIN:           /* Try again. */
-               ret = __ccw_device_sense_pgid_start(cdev);
-               if (ret != 0 && ret != -EBUSY)
-                       ccw_device_sense_pgid_done(cdev, ret);
-               break;
-       case -EUSERS:           /* device is reserved for someone else. */
-               ccw_device_sense_pgid_done(cdev, -EUSERS);
-               break;
-       }
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+
+       if (rc == 0)
+               sch->vpm |= req->lpm;
+       else if (rc != -EACCES)
+               goto err;
+       req->lpm >>= 1;
+       nop_do(cdev);
+       return;
+
+err:
+       verify_done(cdev, rc);
 }
 
 /*
- * Path Group ID helper function.
+ * Create channel program to perform SET PGID on a single path.
  */
-static int
-__ccw_device_do_pgid(struct ccw_device *cdev, __u8 func)
+static void spid_build_cp(struct ccw_device *cdev, u8 fn)
 {
-       struct subchannel *sch;
-       struct ccw1 *ccw;
-       int ret;
-
-       sch = to_subchannel(cdev->dev.parent);
-
-       /* Setup sense path group id channel program. */
-       cdev->private->pgid[0].inf.fc = func;
-       ccw = cdev->private->iccws;
-       if (cdev->private->flags.pgid_single)
-               cdev->private->pgid[0].inf.fc |= SPID_FUNC_SINGLE_PATH;
-       else
-               cdev->private->pgid[0].inf.fc |= SPID_FUNC_MULTI_PATH;
-       ccw->cmd_code = CCW_CMD_SET_PGID;
-       ccw->cda = (__u32) __pa (&cdev->private->pgid[0]);
-       ccw->count = sizeof (struct pgid);
-       ccw->flags = CCW_FLAG_SLI;
-
-       /* Reset device status. */
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-
-       /* Try multiple times. */
-       ret = -EACCES;
-       if (cdev->private->iretry > 0) {
-               cdev->private->iretry--;
-               /* Reset internal retry indication. */
-               cdev->private->flags.intretry = 0;
-               ret = cio_start (sch, cdev->private->iccws,
-                                cdev->private->imask);
-               /* We expect an interrupt in case of success or busy
-                * indication. */
-               if ((ret == 0) || (ret == -EBUSY))
-                       return ret;
-       }
-       /* PGID command failed on this path. */
-       CIO_MSG_EVENT(3, "SPID - Device %04x on Subchannel "
-                     "0.%x.%04x, lpm %02X, became 'not operational'\n",
-                     cdev->private->dev_id.devno, sch->schid.ssid,
-                     sch->schid.sch_no, cdev->private->imask);
-       return ret;
+       struct ccw_request *req = &cdev->private->req;
+       struct ccw1 *cp = cdev->private->iccws;
+       int i = 8 - ffs(req->lpm);
+       struct pgid *pgid = &cdev->private->pgid[i];
+
+       pgid->inf.fc    = fn;
+       cp->cmd_code    = CCW_CMD_SET_PGID;
+       cp->cda         = (u32) (addr_t) pgid;
+       cp->count       = sizeof(*pgid);
+       cp->flags       = CCW_FLAG_SLI;
+       req->cp         = cp;
 }
 
 /*
- * Helper function to send a nop ccw down a path.
+ * Perform establish/resign SET PGID on a single path.
  */
-static int __ccw_device_do_nop(struct ccw_device *cdev)
+static void spid_do(struct ccw_device *cdev)
 {
-       struct subchannel *sch;
-       struct ccw1 *ccw;
-       int ret;
-
-       sch = to_subchannel(cdev->dev.parent);
-
-       /* Setup nop channel program. */
-       ccw = cdev->private->iccws;
-       ccw->cmd_code = CCW_CMD_NOOP;
-       ccw->cda = 0;
-       ccw->count = 0;
-       ccw->flags = CCW_FLAG_SLI;
-
-       /* Reset device status. */
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-
-       /* Try multiple times. */
-       ret = -EACCES;
-       if (cdev->private->iretry > 0) {
-               cdev->private->iretry--;
-               /* Reset internal retry indication. */
-               cdev->private->flags.intretry = 0;
-               ret = cio_start (sch, cdev->private->iccws,
-                                cdev->private->imask);
-               /* We expect an interrupt in case of success or busy
-                * indication. */
-               if ((ret == 0) || (ret == -EBUSY))
-                       return ret;
-       }
-       /* nop command failed on this path. */
-       CIO_MSG_EVENT(3, "NOP - Device %04x on Subchannel "
-                     "0.%x.%04x, lpm %02X, became 'not operational'\n",
-                     cdev->private->dev_id.devno, sch->schid.ssid,
-                     sch->schid.sch_no, cdev->private->imask);
-       return ret;
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+       u8 fn;
+
+       /* Use next available path that is not already in correct state. */
+       req->lpm = lpm_adjust(req->lpm, sch->schib.pmcw.pam & ~sch->vpm);
+       if (!req->lpm)
+               goto out_nopath;
+       /* Channel program setup. */
+       if (req->lpm & sch->opm)
+               fn = SPID_FUNC_ESTABLISH;
+       else
+               fn = SPID_FUNC_RESIGN;
+       if (cdev->private->flags.mpath)
+               fn |= SPID_FUNC_MULTI_PATH;
+       spid_build_cp(cdev, fn);
+       ccw_request_start(cdev);
+       return;
+
+out_nopath:
+       verify_done(cdev, sch->vpm ? 0 : -EACCES);
 }
 
+static void verify_start(struct ccw_device *cdev);
 
 /*
- * Called from interrupt context to check if a valid answer
- * to Set Path Group ID was received.
+ * Process SET PGID request result for a single path.
  */
-static int
-__ccw_device_check_pgid(struct ccw_device *cdev)
+static void spid_callback(struct ccw_device *cdev, void *data, int rc)
 {
-       struct subchannel *sch;
-       struct irb *irb;
-
-       sch = to_subchannel(cdev->dev.parent);
-       irb = &cdev->private->irb;
-       if (irb->scsw.cmd.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
-               /* Retry Set PGID if requested. */
-               if (cdev->private->flags.intretry) {
-                       cdev->private->flags.intretry = 0;
-                       return -EAGAIN;
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+
+       switch (rc) {
+       case 0:
+               sch->vpm |= req->lpm & sch->opm;
+               break;
+       case -EACCES:
+               break;
+       case -EOPNOTSUPP:
+               if (cdev->private->flags.mpath) {
+                       /* Try without multipathing. */
+                       cdev->private->flags.mpath = 0;
+                       goto out_restart;
                }
-               return -ETIME;
+               /* Try without pathgrouping. */
+               cdev->private->flags.pgroup = 0;
+               goto out_restart;
+       default:
+               goto err;
        }
-       if (irb->esw.esw0.erw.cons) {
-               if (irb->ecw[0] & SNS0_CMD_REJECT)
-                       return -EOPNOTSUPP;
-               /* Hmm, whatever happened, try again. */
-               CIO_MSG_EVENT(2, "SPID - device 0.%x.%04x, unit check, "
-                             "cnt %02d, "
-                             "sns : %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
-                             cdev->private->dev_id.ssid,
-                             cdev->private->dev_id.devno,
-                             irb->esw.esw0.erw.scnt,
-                             irb->ecw[0], irb->ecw[1],
-                             irb->ecw[2], irb->ecw[3],
-                             irb->ecw[4], irb->ecw[5],
-                             irb->ecw[6], irb->ecw[7]);
-               return -EAGAIN;
-       }
-       if (irb->scsw.cmd.cc == 3) {
-               CIO_MSG_EVENT(3, "SPID - Device %04x on Subchannel 0.%x.%04x,"
-                             " lpm %02X, became 'not operational'\n",
-                             cdev->private->dev_id.devno, sch->schid.ssid,
-                             sch->schid.sch_no, cdev->private->imask);
-               return -EACCES;
-       }
-       return 0;
+       req->lpm >>= 1;
+       spid_do(cdev);
+       return;
+
+out_restart:
+       verify_start(cdev);
+       return;
+err:
+       verify_done(cdev, rc);
+}
+
+static void spid_start(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+
+       /* Initialize request data. */
+       memset(req, 0, sizeof(*req));
+       req->timeout    = PGID_TIMEOUT;
+       req->maxretries = PGID_RETRIES;
+       req->lpm        = 0x80;
+       req->callback   = spid_callback;
+       spid_do(cdev);
+}
+
+static int pgid_cmp(struct pgid *p1, struct pgid *p2)
+{
+       return memcmp((char *) p1 + 1, (char *) p2 + 1,
+                     sizeof(struct pgid) - 1);
 }
 
 /*
- * Called from interrupt context to check the path status after a nop has
- * been send.
+ * Determine pathgroup state from PGID data.
  */
-static int __ccw_device_check_nop(struct ccw_device *cdev)
+static void pgid_analyze(struct ccw_device *cdev, struct pgid **p,
+                        int *mismatch, int *reserved, int *reset)
 {
-       struct subchannel *sch;
-       struct irb *irb;
-
-       sch = to_subchannel(cdev->dev.parent);
-       irb = &cdev->private->irb;
-       if (irb->scsw.cmd.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC)) {
-               /* Retry NOP if requested. */
-               if (cdev->private->flags.intretry) {
-                       cdev->private->flags.intretry = 0;
-                       return -EAGAIN;
+       struct pgid *pgid = &cdev->private->pgid[0];
+       struct pgid *first = NULL;
+       int lpm;
+       int i;
+
+       *mismatch = 0;
+       *reserved = 0;
+       *reset = 0;
+       for (i = 0, lpm = 0x80; i < 8; i++, pgid++, lpm >>= 1) {
+               if ((cdev->private->pgid_valid_mask & lpm) == 0)
+                       continue;
+               if (pgid->inf.ps.state2 == SNID_STATE2_RESVD_ELSE)
+                       *reserved = 1;
+               if (pgid->inf.ps.state1 == SNID_STATE1_RESET) {
+                       /* A PGID was reset. */
+                       *reset = 1;
+                       continue;
                }
-               return -ETIME;
-       }
-       if (irb->scsw.cmd.cc == 3) {
-               CIO_MSG_EVENT(3, "NOP - Device %04x on Subchannel 0.%x.%04x,"
-                             " lpm %02X, became 'not operational'\n",
-                             cdev->private->dev_id.devno, sch->schid.ssid,
-                             sch->schid.sch_no, cdev->private->imask);
-               return -EACCES;
+               if (!first) {
+                       first = pgid;
+                       continue;
+               }
+               if (pgid_cmp(pgid, first) != 0)
+                       *mismatch = 1;
        }
-       return 0;
+       if (!first)
+               first = &channel_subsystems[0]->global_pgid;
+       *p = first;
 }
 
-static void
-__ccw_device_verify_start(struct ccw_device *cdev)
+static u8 pgid_to_vpm(struct ccw_device *cdev)
 {
-       struct subchannel *sch;
-       __u8 func;
-       int ret;
-
-       sch = to_subchannel(cdev->dev.parent);
-       /* Repeat for all paths. */
-       for (; cdev->private->imask; cdev->private->imask >>= 1,
-                                    cdev->private->iretry = 5) {
-               if ((cdev->private->imask & sch->schib.pmcw.pam) == 0)
-                       /* Path not available, try next. */
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct pgid *pgid;
+       int i;
+       int lpm;
+       u8 vpm = 0;
+
+       /* Set VPM bits for paths which are already in the target state. */
+       for (i = 0; i < 8; i++) {
+               lpm = 0x80 >> i;
+               if ((cdev->private->pgid_valid_mask & lpm) == 0)
                        continue;
-               if (cdev->private->options.pgroup) {
-                       if (sch->opm & cdev->private->imask)
-                               func = SPID_FUNC_ESTABLISH;
-                       else
-                               func = SPID_FUNC_RESIGN;
-                       ret = __ccw_device_do_pgid(cdev, func);
-               } else
-                       ret = __ccw_device_do_nop(cdev);
-               /* We expect an interrupt in case of success or busy
-                * indication. */
-               if (ret == 0 || ret == -EBUSY)
-                       return;
-               /* Permanent path failure, try next. */
+               pgid = &cdev->private->pgid[i];
+               if (sch->opm & lpm) {
+                       if (pgid->inf.ps.state1 != SNID_STATE1_GROUPED)
+                               continue;
+               } else {
+                       if (pgid->inf.ps.state1 != SNID_STATE1_UNGROUPED)
+                               continue;
+               }
+               if (cdev->private->flags.mpath) {
+                       if (pgid->inf.ps.state3 != SNID_STATE3_MULTI_PATH)
+                               continue;
+               } else {
+                       if (pgid->inf.ps.state3 != SNID_STATE3_SINGLE_PATH)
+                               continue;
+               }
+               vpm |= lpm;
        }
-       /* Done with all paths. */
-       ccw_device_verify_done(cdev, (sch->vpm != 0) ? 0 : -ENODEV);
+
+       return vpm;
 }
-               
-/*
- * Got interrupt for Set Path Group ID.
- */
-void
-ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event)
+
+static void pgid_fill(struct ccw_device *cdev, struct pgid *pgid)
 {
-       struct subchannel *sch;
-       struct irb *irb;
-       int ret;
+       int i;
 
-       irb = (struct irb *) __LC_IRB;
+       for (i = 0; i < 8; i++)
+               memcpy(&cdev->private->pgid[i], pgid, sizeof(struct pgid));
+}
 
-       if (irb->scsw.cmd.stctl ==
-           (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
-               if (__ccw_device_should_retry(&irb->scsw))
-                       __ccw_device_verify_start(cdev);
-               return;
+/*
+ * Process SENSE PGID data and report result.
+ */
+static void snid_done(struct ccw_device *cdev, int rc)
+{
+       struct ccw_dev_id *id = &cdev->private->dev_id;
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct pgid *pgid;
+       int mismatch = 0;
+       int reserved = 0;
+       int reset = 0;
+
+       if (rc)
+               goto out;
+       pgid_analyze(cdev, &pgid, &mismatch, &reserved, &reset);
+       if (reserved)
+               rc = -EUSERS;
+       else if (mismatch)
+               rc = -EOPNOTSUPP;
+       else {
+               sch->vpm = pgid_to_vpm(cdev);
+               pgid_fill(cdev, pgid);
        }
-       if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
-               return;
-       sch = to_subchannel(cdev->dev.parent);
-       if (cdev->private->options.pgroup)
-               ret = __ccw_device_check_pgid(cdev);
-       else
-               ret = __ccw_device_check_nop(cdev);
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-
-       switch (ret) {
-       /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
+out:
+       CIO_MSG_EVENT(2, "snid: device 0.%x.%04x: rc=%d pvm=%02x vpm=%02x "
+                     "mism=%d rsvd=%d reset=%d\n", id->ssid, id->devno, rc,
+                     cdev->private->pgid_valid_mask, sch->vpm, mismatch,
+                     reserved, reset);
+       switch (rc) {
        case 0:
-               /* Path verification ccw finished successfully, update lpm. */
-               sch->vpm |= sch->opm & cdev->private->imask;
-               /* Go on with next path. */
-               cdev->private->imask >>= 1;
-               cdev->private->iretry = 5;
-               __ccw_device_verify_start(cdev);
+               /* Anything left to do? */
+               if (sch->vpm == sch->schib.pmcw.pam) {
+                       verify_done(cdev, sch->vpm == 0 ? -EACCES : 0);
+                       return;
+               }
+               /* Perform path-grouping. */
+               spid_start(cdev);
                break;
        case -EOPNOTSUPP:
-               /*
-                * One of those strange devices which claim to be able
-                * to do multipathing but not for Set Path Group ID.
-                */
-               if (cdev->private->flags.pgid_single)
-                       cdev->private->options.pgroup = 0;
-               else
-                       cdev->private->flags.pgid_single = 1;
-               /* Retry */
-               sch->vpm = 0;
-               cdev->private->imask = 0x80;
-               cdev->private->iretry = 5;
-               /* fall through. */
-       case -EAGAIN:           /* Try again. */
-               __ccw_device_verify_start(cdev);
-               break;
-       case -ETIME:            /* Set path group id stopped by timeout. */
-               ccw_device_verify_done(cdev, -ETIME);
-               break;
-       case -EACCES:           /* channel is not operational. */
-               cdev->private->imask >>= 1;
-               cdev->private->iretry = 5;
-               __ccw_device_verify_start(cdev);
+               /* Path-grouping not supported. */
+               cdev->private->flags.pgroup = 0;
+               cdev->private->flags.mpath = 0;
+               verify_start(cdev);
                break;
+       default:
+               verify_done(cdev, rc);
        }
 }
 
-void
-ccw_device_verify_start(struct ccw_device *cdev)
+/*
+ * Create channel program to perform a SENSE PGID on a single path.
+ */
+static void snid_build_cp(struct ccw_device *cdev)
+{
+       struct ccw_request *req = &cdev->private->req;
+       struct ccw1 *cp = cdev->private->iccws;
+       int i = 8 - ffs(req->lpm);
+
+       /* Channel program setup. */
+       cp->cmd_code    = CCW_CMD_SENSE_PGID;
+       cp->cda         = (u32) (addr_t) &cdev->private->pgid[i];
+       cp->count       = sizeof(struct pgid);
+       cp->flags       = CCW_FLAG_SLI;
+       req->cp         = cp;
+}
+
+/*
+ * Perform SENSE PGID on a single path.
+ */
+static void snid_do(struct ccw_device *cdev)
 {
        struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+
+       /* Adjust lpm if paths are not set in pam. */
+       req->lpm = lpm_adjust(req->lpm, sch->schib.pmcw.pam);
+       if (!req->lpm)
+               goto out_nopath;
+       snid_build_cp(cdev);
+       ccw_request_start(cdev);
+       return;
+
+out_nopath:
+       snid_done(cdev, cdev->private->pgid_valid_mask ? 0 : -EACCES);
+}
 
-       cdev->private->flags.pgid_single = 0;
-       cdev->private->imask = 0x80;
-       cdev->private->iretry = 5;
+/*
+ * Process SENSE PGID request result for single path.
+ */
+static void snid_callback(struct ccw_device *cdev, void *data, int rc)
+{
+       struct ccw_request *req = &cdev->private->req;
+
+       if (rc == 0)
+               cdev->private->pgid_valid_mask |= req->lpm;
+       else if (rc != -EACCES)
+               goto err;
+       req->lpm >>= 1;
+       snid_do(cdev);
+       return;
+
+err:
+       snid_done(cdev, rc);
+}
 
-       /* Start with empty vpm. */
-       sch->vpm = 0;
+/*
+ * Perform path verification.
+ */
+static void verify_start(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+       struct ccw_dev_id *devid = &cdev->private->dev_id;
 
-       /* Get current pam. */
-       if (cio_update_schib(sch)) {
-               ccw_device_verify_done(cdev, -ENODEV);
-               return;
+       sch->vpm = 0;
+       /* Initialize request data. */
+       memset(req, 0, sizeof(*req));
+       req->timeout    = PGID_TIMEOUT;
+       req->maxretries = PGID_RETRIES;
+       req->lpm        = 0x80;
+       if (cdev->private->flags.pgroup) {
+               CIO_TRACE_EVENT(4, "snid");
+               CIO_HEX_EVENT(4, devid, sizeof(*devid));
+               req->callback   = snid_callback;
+               snid_do(cdev);
+       } else {
+               CIO_TRACE_EVENT(4, "nop");
+               CIO_HEX_EVENT(4, devid, sizeof(*devid));
+               req->filter     = nop_filter;
+               req->callback   = nop_callback;
+               nop_do(cdev);
        }
-       /* After 60s path verification is considered to have failed. */
-       ccw_device_set_timeout(cdev, 60*HZ);
-       __ccw_device_verify_start(cdev);
 }
 
-static void
-__ccw_device_disband_start(struct ccw_device *cdev)
+/**
+ * ccw_device_verify_start - perform path verification
+ * @cdev: ccw device
+ *
+ * Perform an I/O on each available channel path to @cdev to determine which
+ * paths are operational. The resulting path mask is stored in sch->vpm.
+ * If device options specify pathgrouping, establish a pathgroup for the
+ * operational paths. When finished, call ccw_device_verify_done with a
+ * return code specifying the result.
+ */
+void ccw_device_verify_start(struct ccw_device *cdev)
 {
-       struct subchannel *sch;
-       int ret;
-
-       sch = to_subchannel(cdev->dev.parent);
-       while (cdev->private->imask != 0) {
-               if (sch->lpm & cdev->private->imask) {
-                       ret = __ccw_device_do_pgid(cdev, SPID_FUNC_DISBAND);
-                       if (ret == 0)
-                               return;
-               }
-               cdev->private->iretry = 5;
-               cdev->private->imask >>= 1;
-       }
-       ccw_device_disband_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
+       CIO_TRACE_EVENT(4, "vrfy");
+       CIO_HEX_EVENT(4, &cdev->private->dev_id, sizeof(cdev->private->dev_id));
+       /* Initialize PGID data. */
+       memset(cdev->private->pgid, 0, sizeof(cdev->private->pgid));
+       cdev->private->pgid_valid_mask = 0;
+       /*
+        * Initialize pathgroup and multipath state with target values.
+        * They may change in the course of path verification.
+        */
+       cdev->private->flags.pgroup = cdev->private->options.pgroup;
+       cdev->private->flags.mpath = cdev->private->options.mpath;
+       cdev->private->flags.doverify = 0;
+       verify_start(cdev);
 }
 
 /*
- * Got interrupt for Unset Path Group ID.
+ * Process disband SET PGID request result.
  */
-void
-ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event)
+static void disband_callback(struct ccw_device *cdev, void *data, int rc)
 {
-       struct subchannel *sch;
-       struct irb *irb;
-       int ret;
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_dev_id *id = &cdev->private->dev_id;
+
+       if (rc)
+               goto out;
+       /* Ensure consistent multipathing state at device and channel. */
+       cdev->private->flags.mpath = 0;
+       if (sch->config.mp) {
+               sch->config.mp = 0;
+               rc = cio_commit_config(sch);
+       }
+out:
+       CIO_MSG_EVENT(0, "disb: device 0.%x.%04x: rc=%d\n", id->ssid, id->devno,
+                     rc);
+       ccw_device_disband_done(cdev, rc);
+}
 
-       irb = (struct irb *) __LC_IRB;
+/**
+ * ccw_device_disband_start - disband pathgroup
+ * @cdev: ccw device
+ *
+ * Execute a SET PGID channel program on @cdev to disband a previously
+ * established pathgroup. When finished, call ccw_device_disband_done with
+ * a return code specifying the result.
+ */
+void ccw_device_disband_start(struct ccw_device *cdev)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+       u8 fn;
+
+       CIO_TRACE_EVENT(4, "disb");
+       CIO_HEX_EVENT(4, &cdev->private->dev_id, sizeof(cdev->private->dev_id));
+       /* Request setup. */
+       memset(req, 0, sizeof(*req));
+       req->timeout    = PGID_TIMEOUT;
+       req->maxretries = PGID_RETRIES;
+       req->lpm        = sch->schib.pmcw.pam & sch->opm;
+       req->callback   = disband_callback;
+       fn = SPID_FUNC_DISBAND;
+       if (cdev->private->flags.mpath)
+               fn |= SPID_FUNC_MULTI_PATH;
+       spid_build_cp(cdev, fn);
+       ccw_request_start(cdev);
+}
 
-       if (irb->scsw.cmd.stctl ==
-           (SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
-               if (__ccw_device_should_retry(&irb->scsw))
-                       __ccw_device_disband_start(cdev);
-               return;
-       }
-       if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
-               return;
-       sch = to_subchannel(cdev->dev.parent);
-       ret = __ccw_device_check_pgid(cdev);
-       memset(&cdev->private->irb, 0, sizeof(struct irb));
-       switch (ret) {
-       /* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
-       case 0:                 /* disband successful. */
-               ccw_device_disband_done(cdev, ret);
-               break;
-       case -EOPNOTSUPP:
-               /*
-                * One of those strange devices which claim to be able
-                * to do multipathing but not for Unset Path Group ID.
-                */
-               cdev->private->flags.pgid_single = 1;
-               /* fall through. */
-       case -EAGAIN:           /* Try again. */
-               __ccw_device_disband_start(cdev);
-               break;
-       case -ETIME:            /* Set path group id stopped by timeout. */
-               ccw_device_disband_done(cdev, -ETIME);
-               break;
-       case -EACCES:           /* channel is not operational. */
-               cdev->private->imask >>= 1;
-               cdev->private->iretry = 5;
-               __ccw_device_disband_start(cdev);
-               break;
-       }
+static void stlck_build_cp(struct ccw_device *cdev, void *buf1, void *buf2)
+{
+       struct ccw_request *req = &cdev->private->req;
+       struct ccw1 *cp = cdev->private->iccws;
+
+       cp[0].cmd_code = CCW_CMD_STLCK;
+       cp[0].cda = (u32) (addr_t) buf1;
+       cp[0].count = 32;
+       cp[0].flags = CCW_FLAG_CC;
+       cp[1].cmd_code = CCW_CMD_RELEASE;
+       cp[1].cda = (u32) (addr_t) buf2;
+       cp[1].count = 32;
+       cp[1].flags = 0;
+       req->cp = cp;
 }
 
-void
-ccw_device_disband_start(struct ccw_device *cdev)
+static void stlck_callback(struct ccw_device *cdev, void *data, int rc)
 {
-       /* After 60s disbanding is considered to have failed. */
-       ccw_device_set_timeout(cdev, 60*HZ);
+       ccw_device_stlck_done(cdev, data, rc);
+}
 
-       cdev->private->flags.pgid_single = 0;
-       cdev->private->iretry = 5;
-       cdev->private->imask = 0x80;
-       __ccw_device_disband_start(cdev);
+/**
+ * ccw_device_stlck_start - perform unconditional release
+ * @cdev: ccw device
+ * @data: data pointer to be passed to ccw_device_stlck_done
+ * @buf1: data pointer used in channel program
+ * @buf2: data pointer used in channel program
+ *
+ * Execute a channel program on @cdev to release an existing PGID reservation.
+ * When finished, call ccw_device_stlck_done with a return code specifying the
+ * result.
+ */
+void ccw_device_stlck_start(struct ccw_device *cdev, void *data, void *buf1,
+                           void *buf2)
+{
+       struct subchannel *sch = to_subchannel(cdev->dev.parent);
+       struct ccw_request *req = &cdev->private->req;
+
+       CIO_TRACE_EVENT(4, "stlck");
+       CIO_HEX_EVENT(4, &cdev->private->dev_id, sizeof(cdev->private->dev_id));
+       /* Request setup. */
+       memset(req, 0, sizeof(*req));
+       req->timeout    = PGID_TIMEOUT;
+       req->maxretries = PGID_RETRIES;
+       req->lpm        = sch->schib.pmcw.pam & sch->opm;
+       req->data       = data;
+       req->callback   = stlck_callback;
+       stlck_build_cp(cdev, buf1, buf2);
+       ccw_request_start(cdev);
 }
+
index 5814dbe..66d8066 100644 (file)
@@ -336,9 +336,6 @@ ccw_device_do_sense(struct ccw_device *cdev, struct irb *irb)
        sense_ccw->count = SENSE_MAX_COUNT;
        sense_ccw->flags = CCW_FLAG_SLI;
 
-       /* Reset internal retry indication. */
-       cdev->private->flags.intretry = 0;
-
        rc = cio_start(sch, sense_ccw, 0xff);
        if (rc == -ENODEV || rc == -EACCES)
                dev_fsm_event(cdev, DEV_EVENT_VERIFY);
index 0b8f381..d72ae4c 100644 (file)
@@ -1,7 +1,10 @@
 #ifndef S390_IO_SCH_H
 #define S390_IO_SCH_H
 
+#include <linux/types.h>
 #include <asm/schid.h>
+#include <asm/ccwdev.h>
+#include "css.h"
 
 /*
  * command-mode operation request block
@@ -68,6 +71,52 @@ struct io_subchannel_private {
 #define MAX_CIWS 8
 
 /*
+ * Possible status values for a CCW request's I/O.
+ */
+enum io_status {
+       IO_DONE,
+       IO_RUNNING,
+       IO_STATUS_ERROR,
+       IO_PATH_ERROR,
+       IO_REJECTED,
+       IO_KILLED
+};
+
+/**
+ * ccw_request - Internal CCW request.
+ * @cp: channel program to start
+ * @timeout: maximum allowable time in jiffies between start I/O and interrupt
+ * @maxretries: number of retries per I/O operation and path
+ * @lpm: mask of paths to use
+ * @check: optional callback that determines if results are final
+ * @filter: optional callback to adjust request status based on IRB data
+ * @callback: final callback
+ * @data: user-defined pointer passed to all callbacks
+ * @mask: current path mask
+ * @retries: current number of retries
+ * @drc: delayed return code
+ * @cancel: non-zero if request was cancelled
+ * @done: non-zero if request was finished
+ */
+struct ccw_request {
+       struct ccw1 *cp;
+       unsigned long timeout;
+       u16 maxretries;
+       u8 lpm;
+       int (*check)(struct ccw_device *, void *);
+       enum io_status (*filter)(struct ccw_device *, void *, struct irb *,
+                                enum io_status);
+       void (*callback)(struct ccw_device *, void *, int);
+       void *data;
+       /* These fields are used internally. */
+       u16 mask;
+       u16 retries;
+       int drc;
+       int cancel:1;
+       int done:1;
+} __attribute__((packed));
+
+/*
  * sense-id response buffer layout
  */
 struct senseid {
@@ -82,32 +131,43 @@ struct senseid {
        struct ciw ciw[MAX_CIWS];       /* variable # of CIWs */
 }  __attribute__ ((packed, aligned(4)));
 
+enum cdev_todo {
+       CDEV_TODO_NOTHING,
+       CDEV_TODO_ENABLE_CMF,
+       CDEV_TODO_REBIND,
+       CDEV_TODO_REGISTER,
+       CDEV_TODO_UNREG,
+       CDEV_TODO_UNREG_EVAL,
+};
+
 struct ccw_device_private {
        struct ccw_device *cdev;
        struct subchannel *sch;
        int state;              /* device state */
        atomic_t onoff;
-       unsigned long registered;
        struct ccw_dev_id dev_id;       /* device id */
        struct subchannel_id schid;     /* subchannel number */
-       u8 imask;               /* lpm mask for SNID/SID/SPGID */
-       int iretry;             /* retry counter SNID/SID/SPGID */
+       struct ccw_request req;         /* internal I/O request */
+       int iretry;
+       u8 pgid_valid_mask;             /* mask of valid PGIDs */
        struct {
                unsigned int fast:1;    /* post with "channel end" */
                unsigned int repall:1;  /* report every interrupt status */
                unsigned int pgroup:1;  /* do path grouping */
                unsigned int force:1;   /* allow forced online */
+               unsigned int mpath:1;   /* do multipathing */
        } __attribute__ ((packed)) options;
        struct {
-               unsigned int pgid_single:1; /* use single path for Set PGID */
                unsigned int esid:1;        /* Ext. SenseID supported by HW */
                unsigned int dosense:1;     /* delayed SENSE required */
                unsigned int doverify:1;    /* delayed path verification */
                unsigned int donotify:1;    /* call notify function */
                unsigned int recog_done:1;  /* dev. recog. complete */
                unsigned int fake_irb:1;    /* deliver faked irb */
-               unsigned int intretry:1;    /* retry internal operation */
                unsigned int resuming:1;    /* recognition while resume */
+               unsigned int pgroup:1;      /* pathgroup is set up */
+               unsigned int mpath:1;       /* multipathing is set up */
+               unsigned int initialized:1; /* set if initial reference held */
        } __attribute__((packed)) flags;
        unsigned long intparm;  /* user interruption parameter */
        struct qdio_irq *qdio_data;
@@ -115,7 +175,8 @@ struct ccw_device_private {
        struct senseid senseid; /* SenseID info */
        struct pgid pgid[8];    /* path group IDs per chpid*/
        struct ccw1 iccws[2];   /* ccws for SNID/SID/SPGID commands */
-       struct work_struct kick_work;
+       struct work_struct todo_work;
+       enum cdev_todo todo;
        wait_queue_head_t wait_q;
        struct timer_list timer;
        void *cmb;                      /* measurement information */
index 21077f4..20836ef 100644 (file)
@@ -102,6 +102,7 @@ static atomic_t ap_poll_requests = ATOMIC_INIT(0);
 static DECLARE_WAIT_QUEUE_HEAD(ap_poll_wait);
 static struct task_struct *ap_poll_kthread = NULL;
 static DEFINE_MUTEX(ap_poll_thread_mutex);
+static DEFINE_SPINLOCK(ap_poll_timer_lock);
 static void *ap_interrupt_indicator;
 static struct hrtimer ap_poll_timer;
 /* In LPAR poll with 4kHz frequency. Poll every 250000 nanoseconds.
@@ -1170,16 +1171,19 @@ ap_config_timeout(unsigned long ptr)
 static inline void ap_schedule_poll_timer(void)
 {
        ktime_t hr_time;
+
+       spin_lock_bh(&ap_poll_timer_lock);
        if (ap_using_interrupts() || ap_suspend_flag)
-               return;
+               goto out;
        if (hrtimer_is_queued(&ap_poll_timer))
-               return;
+               goto out;
        if (ktime_to_ns(hrtimer_expires_remaining(&ap_poll_timer)) <= 0) {
                hr_time = ktime_set(0, poll_timeout);
                hrtimer_forward_now(&ap_poll_timer, hr_time);
                hrtimer_restart(&ap_poll_timer);
        }
-       return;
+out:
+       spin_unlock_bh(&ap_poll_timer_lock);
 }
 
 /**
@@ -1668,6 +1672,7 @@ int __init ap_module_init(void)
         */
        if (MACHINE_IS_VM)
                poll_timeout = 1500000;
+       spin_lock_init(&ap_poll_timer_lock);
        hrtimer_init(&ap_poll_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
        ap_poll_timer.function = ap_poll_timeout;