S390
P: Martin Schwidefsky
M: schwidefsky@de.ibm.com
+P: Heiko Carstens
+M: heiko.carstens@de.ibm.com
M: linux390@de.ibm.com
L: linux-390@vm.marist.edu
W: http://www.ibm.com/developerworks/linux/linux390/
information in an s390 hypervisor environment.
config KEXEC
- bool "kexec system call (EXPERIMENTAL)"
- depends on EXPERIMENTAL
+ bool "kexec system call"
help
kexec is a system call that implements the ability to shutdown your
current kernel, and to start another kernel. It is like a reboot
source "fs/Kconfig"
+menu "Instrumentation Support"
+
source "arch/s390/oprofile/Kconfig"
+config KPROBES
+ bool "Kprobes (EXPERIMENTAL)"
+ depends on EXPERIMENTAL && MODULES
+ help
+ Kprobes allows you to trap at almost any kernel address and
+ execute a callback function. register_kprobe() establishes
+ a probepoint and specifies the callback. Kprobes is useful
+ for kernel debugging, non-intrusive instrumentation and testing.
+ If in doubt, say "N".
+
+endmenu
+
source "arch/s390/Kconfig.debug"
source "security/Kconfig"
#define CTL_APPLDATA_NET_SUM 2125
#define CTL_APPLDATA_PROC 2126
-#ifndef CONFIG_64BIT
-
-#define APPLDATA_START_INTERVAL_REC 0x00 /* Function codes for */
-#define APPLDATA_STOP_REC 0x01 /* DIAG 0xDC */
-#define APPLDATA_GEN_EVENT_RECORD 0x02
-#define APPLDATA_START_CONFIG_REC 0x03
-
-#else
-
-#define APPLDATA_START_INTERVAL_REC 0x80
-#define APPLDATA_STOP_REC 0x81
-#define APPLDATA_GEN_EVENT_RECORD 0x82
-#define APPLDATA_START_CONFIG_REC 0x83
-
-#endif /* CONFIG_64BIT */
-
#define P_INFO(x...) printk(KERN_INFO MY_PRINT_NAME " info: " x)
#define P_ERROR(x...) printk(KERN_ERR MY_PRINT_NAME " error: " x)
#define P_WARNING(x...) printk(KERN_WARNING MY_PRINT_NAME " status: " x)
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/errno.h>
-#include <asm/uaccess.h>
-#include <asm/io.h>
-#include <asm/smp.h>
#include <linux/interrupt.h>
#include <linux/proc_fs.h>
#include <linux/page-flags.h>
#include <linux/swap.h>
#include <linux/pagemap.h>
#include <linux/sysctl.h>
-#include <asm/timer.h>
-//#include <linux/kernel_stat.h>
#include <linux/notifier.h>
#include <linux/cpu.h>
#include <linux/workqueue.h>
+#include <asm/appldata.h>
+#include <asm/timer.h>
+#include <asm/uaccess.h>
+#include <asm/io.h>
+#include <asm/smp.h>
#include "appldata.h"
#define TOD_MICRO 0x01000 /* nr. of TOD clock units
for 1 microsecond */
-
-/*
- * Parameter list for DIAGNOSE X'DC'
- */
-#ifndef CONFIG_64BIT
-struct appldata_parameter_list {
- u16 diag; /* The DIAGNOSE code X'00DC' */
- u8 function; /* The function code for the DIAGNOSE */
- u8 parlist_length; /* Length of the parameter list */
- u32 product_id_addr; /* Address of the 16-byte product ID */
- u16 reserved;
- u16 buffer_length; /* Length of the application data buffer */
- u32 buffer_addr; /* Address of the application data buffer */
-};
-#else
-struct appldata_parameter_list {
- u16 diag;
- u8 function;
- u8 parlist_length;
- u32 unused01;
- u16 reserved;
- u16 buffer_length;
- u32 unused02;
- u64 product_id_addr;
- u64 buffer_addr;
-};
-#endif /* CONFIG_64BIT */
-
/*
* /proc entries (sysctl)
*/
int appldata_diag(char record_nr, u16 function, unsigned long buffer,
u16 length, char *mod_lvl)
{
- unsigned long ry;
- struct appldata_product_id {
- char prod_nr[7]; /* product nr. */
- char prod_fn[2]; /* product function */
- char record_nr; /* record nr. */
- char version_nr[2]; /* version */
- char release_nr[2]; /* release */
- char mod_lvl[2]; /* modification lvl. */
- } appldata_product_id = {
- /* all strings are EBCDIC, record_nr is byte */
+ struct appldata_product_id id = {
.prod_nr = {0xD3, 0xC9, 0xD5, 0xE4,
- 0xE7, 0xD2, 0xD9}, /* "LINUXKR" */
- .prod_fn = {0xD5, 0xD3}, /* "NL" */
+ 0xE7, 0xD2, 0xD9}, /* "LINUXKR" */
+ .prod_fn = 0xD5D3, /* "NL" */
.record_nr = record_nr,
- .version_nr = {0xF2, 0xF6}, /* "26" */
- .release_nr = {0xF0, 0xF1}, /* "01" */
- .mod_lvl = {mod_lvl[0], mod_lvl[1]},
- };
- struct appldata_parameter_list appldata_parameter_list = {
- .diag = 0xDC,
- .function = function,
- .parlist_length =
- sizeof(appldata_parameter_list),
- .buffer_length = length,
- .product_id_addr =
- (unsigned long) &appldata_product_id,
- .buffer_addr = virt_to_phys((void *) buffer)
+ .version_nr = 0xF2F6, /* "26" */
+ .release_nr = 0xF0F1, /* "01" */
+ .mod_lvl = (mod_lvl[0]) << 8 | mod_lvl[1],
};
- if (!MACHINE_IS_VM)
- return -ENOSYS;
- ry = -1;
- asm volatile(
- "diag %1,%0,0xDC\n\t"
- : "=d" (ry)
- : "d" (&appldata_parameter_list),
- "m" (appldata_parameter_list),
- "m" (appldata_product_id)
- : "cc");
- return (int) ry;
+ return appldata_asm(&id, function, (void *) buffer, length);
}
/************************ timer, work, DIAG <END> ****************************/
#include <linux/kernel_stat.h>
#include <linux/netdevice.h>
#include <linux/sched.h>
+#include <asm/appldata.h>
#include <asm/smp.h>
#include "appldata.h"
# CONFIG_VMLOGRDR is not set
# CONFIG_VMCP is not set
# CONFIG_MONREADER is not set
+CONFIG_MONWRITER=m
#
# Cryptographic devices
/*
- * fs/hypfs/hypfs.h
+ * arch/s390/hypfs/hypfs.h
* Hypervisor filesystem for Linux on s390.
*
* Copyright (C) IBM Corp. 2006
/*
- * fs/hypfs/hypfs_diag.c
+ * arch/s390/hypfs/hypfs_diag.c
* Hypervisor filesystem for Linux on s390. Diag 204 and 224
* implementation.
*
buf = diag204_get_buffer(INFO_EXT, &pages);
if (!IS_ERR(buf)) {
- if (diag204(SUBC_STIB7 | INFO_EXT, pages, buf) >= 0) {
+ if (diag204((unsigned long)SUBC_STIB7 |
+ (unsigned long)INFO_EXT, pages, buf) >= 0) {
diag204_store_sc = SUBC_STIB7;
diag204_info_type = INFO_EXT;
goto out;
}
- if (diag204(SUBC_STIB6 | INFO_EXT, pages, buf) >= 0) {
+ if (diag204((unsigned long)SUBC_STIB6 |
+ (unsigned long)INFO_EXT, pages, buf) >= 0) {
diag204_store_sc = SUBC_STIB7;
diag204_info_type = INFO_EXT;
goto out;
rc = PTR_ERR(buf);
goto fail_alloc;
}
- if (diag204(SUBC_STIB4 | INFO_SIMPLE, pages, buf) >= 0) {
+ if (diag204((unsigned long)SUBC_STIB4 |
+ (unsigned long)INFO_SIMPLE, pages, buf) >= 0) {
diag204_store_sc = SUBC_STIB4;
diag204_info_type = INFO_SIMPLE;
goto out;
buf = diag204_get_buffer(diag204_info_type, &pages);
if (IS_ERR(buf))
goto out;
- if (diag204(diag204_store_sc | diag204_info_type, pages, buf) < 0)
+ if (diag204((unsigned long)diag204_store_sc |
+ (unsigned long)diag204_info_type, pages, buf) < 0)
return ERR_PTR(-ENOSYS);
out:
return buf;
return rc;
}
-__exit void hypfs_diag_exit(void)
+void hypfs_diag_exit(void)
{
diag224_delete_name_table();
diag204_free_buffer();
/*
- * fs/hypfs/hypfs_diag.h
+ * arch/s390/hypfs_diag.h
* Hypervisor filesystem for Linux on s390.
*
* Copyright (C) IBM Corp. 2006
/*
- * fs/hypfs/inode.c
+ * arch/s390/hypfs/inode.c
* Hypervisor filesystem for Linux on s390.
*
* Copyright (C) IBM Corp. 2006
{
struct hypfs_sb_info *sb_info = sb->s_fs_info;
- hypfs_delete_tree(sb->s_root);
- hypfs_remove(sb_info->update_file);
- kfree(sb->s_fs_info);
- sb->s_fs_info = NULL;
+ if (sb->s_root) {
+ hypfs_delete_tree(sb->s_root);
+ hypfs_remove(sb_info->update_file);
+ kfree(sb->s_fs_info);
+ sb->s_fs_info = NULL;
+ }
kill_litter_super(sb);
}
obj-y := bitmap.o traps.o time.o process.o \
setup.o sys_s390.o ptrace.o signal.o cpcmd.o ebcdic.o \
- semaphore.o s390_ext.o debug.o profile.o irq.o reipl_diag.o
+ semaphore.o s390_ext.o debug.o profile.o irq.o ipl.o
obj-y += $(if $(CONFIG_64BIT),entry64.o,entry.o)
obj-y += $(if $(CONFIG_64BIT),reipl64.o,reipl.o)
obj-$(CONFIG_VIRT_TIMER) += vtime.o
obj-$(CONFIG_STACKTRACE) += stacktrace.o
+obj-$(CONFIG_KPROBES) += kprobes.o
# Kexec part
S390_KEXEC_OBJS := machine_kexec.o crash.o
mvc __THREAD_per+__PER_address(4,%r1),__LC_PER_ADDRESS
mvc __THREAD_per+__PER_access_id(1,%r1),__LC_PER_ACCESS_ID
oi __TI_flags+3(%r9),_TIF_SINGLE_STEP # set TIF_SINGLE_STEP
+ tm SP_PSW+1(%r15),0x01 # kernel per event ?
+ bz BASED(kernel_per)
l %r3,__LC_PGM_ILC # load program interruption code
la %r8,0x7f
nr %r8,%r3 # clear per-event-bit and ilc
stosm __SF_EMPTY(%r15),0x03 # reenable interrupts
b BASED(sysc_do_svc)
+#
+# per was called from kernel, must be kprobes
+#
+kernel_per:
+ mvi SP_TRAP+1(%r15),0x28 # set trap indication to pgm check
+ la %r2,SP_PTREGS(%r15) # address of register-save area
+ l %r1,BASED(.Lhandle_per) # load adr. of per handler
+ la %r14,BASED(sysc_leave) # load adr. of system return
+ br %r1 # branch to do_single_step
+
/*
* IO interrupt handler routine
*/
#endif
lg %r9,__LC_THREAD_INFO # load pointer to thread_info struct
lg %r1,__TI_task(%r9)
+ tm SP_PSW+1(%r15),0x01 # kernel per event ?
+ jz kernel_per
mvc __THREAD_per+__PER_atmid(2,%r1),__LC_PER_ATMID
mvc __THREAD_per+__PER_address(8,%r1),__LC_PER_ADDRESS
mvc __THREAD_per+__PER_access_id(1,%r1),__LC_PER_ACCESS_ID
stosm __SF_EMPTY(%r15),0x03 # reenable interrupts
j sysc_do_svc
+#
+# per was called from kernel, must be kprobes
+#
+kernel_per:
+ lhi %r0,__LC_PGM_OLD_PSW
+ sth %r0,SP_TRAP(%r15) # set trap indication to pgm check
+ la %r2,SP_PTREGS(%r15) # address of register-save area
+ larl %r14,sysc_leave # load adr. of system ret, no work
+ jg do_single_step # branch to do_single_step
+
/*
* IO interrupt handler routine
*/
*/
stack_overflow:
lg %r15,__LC_PANIC_STACK # change to panic stack
- aghi %r1,-SP_SIZE
+ aghi %r15,-SP_SIZE
mvc SP_PSW(16,%r15),0(%r12) # move user PSW to stack
stmg %r0,%r11,SP_R0(%r15) # store gprs %r0-%r11 to kernel stack
la %r1,__LC_SAVE_AREA
je 0f
chi %r12,__LC_PGM_OLD_PSW
je 0f
- la %r1,__LC_SAVE_AREA+16
+ la %r1,__LC_SAVE_AREA+32
0: mvc SP_R12(32,%r15),0(%r1) # move %r12-%r15 to stack
xc __SF_BACKCHAIN(8,%r15),__SF_BACKCHAIN(%r15) # clear back chain
la %r2,SP_PTREGS(%r15) # load pt_regs
# load parameter file from ipl device
#
.Lagain1:
- l %r2,INITRD_START+ARCH_OFFSET-PARMAREA(%r12) # ramdisk loc. is temp
+ l %r2,.Linitrd # ramdisk loc. is temp
bas %r14,.Lloader # load parameter file
ltr %r2,%r2 # got anything ?
bz .Lnopf
bnh .Lnotrunc
la %r2,895
.Lnotrunc:
- l %r4,INITRD_START+ARCH_OFFSET-PARMAREA(%r12)
+ l %r4,.Linitrd
clc 0(3,%r4),.L_hdr # if it is HDRx
bz .Lagain1 # skip dataset header
clc 0(3,%r4),.L_eof # if it is EOFx
# load ramdisk from ipl device
#
.Lagain2:
- l %r2,INITRD_START+ARCH_OFFSET-PARMAREA(%r12) # addr of ramdisk
+ l %r2,.Linitrd # addr of ramdisk
+ st %r2,INITRD_START+ARCH_OFFSET-PARMAREA(%r12)
bas %r14,.Lloader # load ramdisk
st %r2,INITRD_SIZE+ARCH_OFFSET-PARMAREA(%r12) # store size of ramdisk
ltr %r2,%r2
bnz .Lrdcont
st %r2,INITRD_START+ARCH_OFFSET-PARMAREA(%r12) # no ramdisk found
.Lrdcont:
- l %r2,INITRD_START+ARCH_OFFSET-PARMAREA(%r12)
+ l %r2,.Linitrd
clc 0(3,%r2),.L_hdr # skip HDRx and EOFx
bz .Lagain2
l %r1,.Lstartup
br %r1
+.Linitrd:.long _end + 0x400000 # default address of initrd
.Lparm: .long PARMAREA
.Lstartup: .long startup
.Lcvtab:.long _ebcasc # ebcdic to ascii table
.byte 0xf0,0xf1,0xf2,0xf3,0xf4,0xf5,0xf6,0xf7
.byte 0xf8,0xf9,0xfa,0xfb,0xfc,0xfd,0xfe,0xff
-.macro GET_IPL_DEVICE
-.Lget_ipl_device:
- l %r1,0xb8 # get sid
- sll %r1,15 # test if subchannel is enabled
- srl %r1,31
- ltr %r1,%r1
- bz 2f-.LPG1(%r13) # subchannel disabled
- l %r1,0xb8
- la %r5,.Lipl_schib-.LPG1(%r13)
- stsch 0(%r5) # get schib of subchannel
- bnz 2f-.LPG1(%r13) # schib not available
- tm 5(%r5),0x01 # devno valid?
- bno 2f-.LPG1(%r13)
- la %r6,ipl_parameter_flags-.LPG1(%r13)
- oi 3(%r6),0x01 # set flag
- la %r2,ipl_devno-.LPG1(%r13)
- mvc 0(2,%r2),6(%r5) # store devno
- tm 4(%r5),0x80 # qdio capable device?
- bno 2f-.LPG1(%r13)
- oi 3(%r6),0x02 # set flag
-
- # copy ipl parameters
-
- lhi %r0,4096
- l %r2,20(%r0) # get address of parameter list
- lhi %r3,IPL_PARMBLOCK_ORIGIN
- st %r3,20(%r0)
- lhi %r4,1
- cr %r2,%r3 # start parameters < destination ?
- jl 0f
- lhi %r1,1 # copy direction is upwards
- j 1f
-0: lhi %r1,-1 # copy direction is downwards
- ar %r2,%r0
- ar %r3,%r0
- ar %r2,%r1
- ar %r3,%r1
-1: mvc 0(1,%r3),0(%r2) # finally copy ipl parameters
- ar %r3,%r1
- ar %r2,%r1
- sr %r0,%r4
- jne 1b
- b 2f-.LPG1(%r13)
-
- .align 4
-.Lipl_schib:
- .rept 13
- .long 0
- .endr
-
- .globl ipl_parameter_flags
-ipl_parameter_flags:
- .long 0
- .globl ipl_devno
-ipl_devno:
- .word 0
-2:
-.endm
-
#ifdef CONFIG_64BIT
#include "head64.S"
#else
#
.org PARMAREA
.long 0,0 # IPL_DEVICE
- .long 0,RAMDISK_ORIGIN # INITRD_START
- .long 0,RAMDISK_SIZE # INITRD_SIZE
+ .long 0,0 # INITRD_START
+ .long 0,0 # INITRD_SIZE
.org COMMAND_LINE
.byte "root=/dev/ram0 ro"
startup_continue:
basr %r13,0 # get base
-.LPG1: GET_IPL_DEVICE
+.LPG1: mvi __LC_AR_MODE_ID,0 # set ESA flag (mode 0)
lctl %c0,%c15,.Lctl-.LPG1(%r13) # load control registers
l %r12,.Lparmaddr-.LPG1(%r13) # pointer to parameter area
# move IPL device to lowcore
mvc __LC_IPLDEV(4),IPL_DEVICE-PARMAREA(%r12)
+#
+# Setup stack
+#
+ l %r15,.Linittu-.LPG1(%r13)
+ mvc __LC_CURRENT(4),__TI_task(%r15)
+ ahi %r15,1<<(PAGE_SHIFT+THREAD_ORDER) # init_task_union+THREAD_SIZE
+ st %r15,__LC_KERNEL_STACK # set end of kernel stack
+ ahi %r15,-96
+ xc __SF_BACKCHAIN(4,%r15),__SF_BACKCHAIN(%r15) # clear backchain
+ l %r14,.Lipl_save_parameters-.LPG1(%r13)
+ basr %r14,%r14
#
# clear bss memory
#
b .Lfchunk-.LPG1(%r13)
.align 4
+.Lipl_save_parameters:
+ .long ipl_save_parameters
+.Linittu:
+ .long init_thread_union
.Lpmask:
.byte 0
.align 8
.Lbss_end: .long _end
.Lparmaddr: .long PARMAREA
.Lsccbaddr: .long .Lsccb
+
+ .globl ipl_schib
+ipl_schib:
+ .rept 13
+ .long 0
+ .endr
+
+ .globl ipl_flags
+ipl_flags:
+ .long 0
+ .globl ipl_devno
+ipl_devno:
+ .word 0
+
.org 0x12000
+.globl s390_readinfo_sccb
+s390_readinfo_sccb:
.Lsccb:
.hword 0x1000 # length, one page
.byte 0x00,0x00,0x00
.globl _stext
_stext: basr %r13,0 # get base
.LPG3:
-#
-# Setup stack
-#
- l %r15,.Linittu-.LPG3(%r13)
- mvc __LC_CURRENT(4),__TI_task(%r15)
- ahi %r15,1<<(PAGE_SHIFT+THREAD_ORDER) # init_task_union+THREAD_SIZE
- st %r15,__LC_KERNEL_STACK # set end of kernel stack
- ahi %r15,-96
- xc __SF_BACKCHAIN(4,%r15),__SF_BACKCHAIN(%r15) # clear backchain
-
# check control registers
stctl %c0,%c15,0(%r15)
oi 2(%r15),0x40 # enable sigp emergency signal
#
.align 8
.Ldw: .long 0x000a0000,0x00000000
-.Linittu:.long init_thread_union
.Lstart:.long start_kernel
.Laregs:.long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
#
.org PARMAREA
.quad 0 # IPL_DEVICE
- .quad RAMDISK_ORIGIN # INITRD_START
- .quad RAMDISK_SIZE # INITRD_SIZE
+ .quad 0 # INITRD_START
+ .quad 0 # INITRD_SIZE
.org COMMAND_LINE
.byte "root=/dev/ram0 ro"
basr %r13,0 # get base
.LPG1: sll %r13,1 # remove high order bit
srl %r13,1
- GET_IPL_DEVICE
lhi %r1,1 # mode 1 = esame
+ mvi __LC_AR_MODE_ID,1 # set esame flag
slr %r0,%r0 # set cpuid to zero
sigp %r1,%r0,0x12 # switch to esame mode
sam64 # switch to 64 bit mode
lg %r12,.Lparmaddr-.LPG1(%r13)# pointer to parameter area
# move IPL device to lowcore
mvc __LC_IPLDEV(4),IPL_DEVICE+4-PARMAREA(%r12)
+#
+# Setup stack
+#
+ larl %r15,init_thread_union
+ lg %r14,__TI_task(%r15) # cache current in lowcore
+ stg %r14,__LC_CURRENT
+ aghi %r15,1<<(PAGE_SHIFT+THREAD_ORDER) # init_task_union + THREAD_SIZE
+ stg %r15,__LC_KERNEL_STACK # set end of kernel stack
+ aghi %r15,-160
+ xc __SF_BACKCHAIN(4,%r15),__SF_BACKCHAIN(%r15) # clear backchain
+ brasl %r14,ipl_save_parameters
#
# clear bss memory
#
oi 7(%r12),0x80 # set IDTE flag
0:
+#
+# find out if we have the MVCOS instruction
+#
+ la %r1,0f-.LPG1(%r13) # set program check address
+ stg %r1,__LC_PGM_NEW_PSW+8
+ .short 0xc800 # mvcos 0(%r0),0(%r0),%r0
+ .short 0x0000
+ .short 0x0000
+0: tm 0x8f,0x13 # special-operation exception?
+ bno 1f-.LPG1(%r13) # if yes, MVCOS is present
+ oi 6(%r12),2 # set MVCOS flag
+1:
+
lpswe .Lentry-.LPG1(13) # jump to _stext in primary-space,
# virtual and never return ...
.align 16
.Lparmaddr:
.quad PARMAREA
+ .globl ipl_schib
+ipl_schib:
+ .rept 13
+ .long 0
+ .endr
+
+ .globl ipl_flags
+ipl_flags:
+ .long 0
+ .globl ipl_devno
+ipl_devno:
+ .word 0
+
.org 0x12000
+.globl s390_readinfo_sccb
+s390_readinfo_sccb:
.Lsccb:
.hword 0x1000 # length, one page
.byte 0x00,0x00,0x00
.globl _stext
_stext: basr %r13,0 # get base
.LPG3:
-#
-# Setup stack
-#
- larl %r15,init_thread_union
- lg %r14,__TI_task(%r15) # cache current in lowcore
- stg %r14,__LC_CURRENT
- aghi %r15,1<<(PAGE_SHIFT+THREAD_ORDER) # init_task_union + THREAD_SIZE
- stg %r15,__LC_KERNEL_STACK # set end of kernel stack
- aghi %r15,-160
- xc __SF_BACKCHAIN(4,%r15),__SF_BACKCHAIN(%r15) # clear backchain
-
# check control registers
stctg %c0,%c15,0(%r15)
oi 6(%r15),0x40 # enable sigp emergency signal
oi 4(%r15),0x10 # switch on low address proctection
lctlg %c0,%c15,0(%r15)
-#
lam 0,15,.Laregs-.LPG3(%r13) # load access regs needed by uaccess
brasl %r14,start_kernel # go to C code
#
#
basr %r13,0
lpswe .Ldw-.(%r13) # load disabled wait psw
-#
+
.align 8
.Ldw: .quad 0x0002000180000000,0x0000000000000000
.Laregs: .long 0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
--- /dev/null
+/*
+ * arch/s390/kernel/ipl.c
+ * ipl/reipl/dump support for Linux on s390.
+ *
+ * Copyright (C) IBM Corp. 2005,2006
+ * Author(s): Michael Holzheu <holzheu@de.ibm.com>
+ * Heiko Carstens <heiko.carstens@de.ibm.com>
+ * Volker Sameske <sameske@de.ibm.com>
+ */
+
+#include <linux/types.h>
+#include <linux/module.h>
+#include <linux/device.h>
+#include <linux/delay.h>
+#include <linux/reboot.h>
+#include <asm/smp.h>
+#include <asm/setup.h>
+#include <asm/cpcmd.h>
+#include <asm/cio.h>
+
+#define IPL_PARM_BLOCK_VERSION 0
+
+enum ipl_type {
+ IPL_TYPE_NONE = 1,
+ IPL_TYPE_UNKNOWN = 2,
+ IPL_TYPE_CCW = 4,
+ IPL_TYPE_FCP = 8,
+};
+
+#define IPL_NONE_STR "none"
+#define IPL_UNKNOWN_STR "unknown"
+#define IPL_CCW_STR "ccw"
+#define IPL_FCP_STR "fcp"
+
+static char *ipl_type_str(enum ipl_type type)
+{
+ switch (type) {
+ case IPL_TYPE_NONE:
+ return IPL_NONE_STR;
+ case IPL_TYPE_CCW:
+ return IPL_CCW_STR;
+ case IPL_TYPE_FCP:
+ return IPL_FCP_STR;
+ case IPL_TYPE_UNKNOWN:
+ default:
+ return IPL_UNKNOWN_STR;
+ }
+}
+
+enum ipl_method {
+ IPL_METHOD_NONE,
+ IPL_METHOD_CCW_CIO,
+ IPL_METHOD_CCW_DIAG,
+ IPL_METHOD_CCW_VM,
+ IPL_METHOD_FCP_RO_DIAG,
+ IPL_METHOD_FCP_RW_DIAG,
+ IPL_METHOD_FCP_RO_VM,
+};
+
+enum shutdown_action {
+ SHUTDOWN_REIPL,
+ SHUTDOWN_DUMP,
+ SHUTDOWN_STOP,
+};
+
+#define SHUTDOWN_REIPL_STR "reipl"
+#define SHUTDOWN_DUMP_STR "dump"
+#define SHUTDOWN_STOP_STR "stop"
+
+static char *shutdown_action_str(enum shutdown_action action)
+{
+ switch (action) {
+ case SHUTDOWN_REIPL:
+ return SHUTDOWN_REIPL_STR;
+ case SHUTDOWN_DUMP:
+ return SHUTDOWN_DUMP_STR;
+ case SHUTDOWN_STOP:
+ return SHUTDOWN_STOP_STR;
+ default:
+ BUG();
+ }
+}
+
+enum diag308_subcode {
+ DIAG308_IPL = 3,
+ DIAG308_DUMP = 4,
+ DIAG308_SET = 5,
+ DIAG308_STORE = 6,
+};
+
+enum diag308_ipl_type {
+ DIAG308_IPL_TYPE_FCP = 0,
+ DIAG308_IPL_TYPE_CCW = 2,
+};
+
+enum diag308_opt {
+ DIAG308_IPL_OPT_IPL = 0x10,
+ DIAG308_IPL_OPT_DUMP = 0x20,
+};
+
+enum diag308_rc {
+ DIAG308_RC_OK = 1,
+};
+
+static int diag308_set_works = 0;
+
+static int reipl_capabilities = IPL_TYPE_UNKNOWN;
+static enum ipl_type reipl_type = IPL_TYPE_UNKNOWN;
+static enum ipl_method reipl_method = IPL_METHOD_NONE;
+static struct ipl_parameter_block *reipl_block_fcp;
+static struct ipl_parameter_block *reipl_block_ccw;
+
+static int dump_capabilities = IPL_TYPE_NONE;
+static enum ipl_type dump_type = IPL_TYPE_NONE;
+static enum ipl_method dump_method = IPL_METHOD_NONE;
+static struct ipl_parameter_block *dump_block_fcp;
+static struct ipl_parameter_block *dump_block_ccw;
+
+static enum shutdown_action on_panic_action = SHUTDOWN_STOP;
+
+static int diag308(unsigned long subcode, void *addr)
+{
+ register unsigned long _addr asm("0") = (unsigned long)addr;
+ register unsigned long _rc asm("1") = 0;
+
+ asm volatile (
+ " diag %0,%2,0x308\n"
+ "0: \n"
+ ".section __ex_table,\"a\"\n"
+#ifdef CONFIG_64BIT
+ " .align 8\n"
+ " .quad 0b, 0b\n"
+#else
+ " .align 4\n"
+ " .long 0b, 0b\n"
+#endif
+ ".previous\n"
+ : "+d" (_addr), "+d" (_rc)
+ : "d" (subcode) : "cc", "memory" );
+
+ return _rc;
+}
+
+/* SYSFS */
+
+#define DEFINE_IPL_ATTR_RO(_prefix, _name, _format, _value) \
+static ssize_t sys_##_prefix##_##_name##_show(struct subsystem *subsys, \
+ char *page) \
+{ \
+ return sprintf(page, _format, _value); \
+} \
+static struct subsys_attribute sys_##_prefix##_##_name##_attr = \
+ __ATTR(_name, S_IRUGO, sys_##_prefix##_##_name##_show, NULL);
+
+#define DEFINE_IPL_ATTR_RW(_prefix, _name, _fmt_out, _fmt_in, _value) \
+static ssize_t sys_##_prefix##_##_name##_show(struct subsystem *subsys, \
+ char *page) \
+{ \
+ return sprintf(page, _fmt_out, \
+ (unsigned long long) _value); \
+} \
+static ssize_t sys_##_prefix##_##_name##_store(struct subsystem *subsys,\
+ const char *buf, size_t len) \
+{ \
+ unsigned long long value; \
+ if (sscanf(buf, _fmt_in, &value) != 1) \
+ return -EINVAL; \
+ _value = value; \
+ return len; \
+} \
+static struct subsys_attribute sys_##_prefix##_##_name##_attr = \
+ __ATTR(_name,(S_IRUGO | S_IWUSR), \
+ sys_##_prefix##_##_name##_show, \
+ sys_##_prefix##_##_name##_store);
+
+static void make_attrs_ro(struct attribute **attrs)
+{
+ while (*attrs) {
+ (*attrs)->mode = S_IRUGO;
+ attrs++;
+ }
+}
+
+/*
+ * ipl section
+ */
+
+static enum ipl_type ipl_get_type(void)
+{
+ struct ipl_parameter_block *ipl = IPL_PARMBLOCK_START;
+
+ if (!(ipl_flags & IPL_DEVNO_VALID))
+ return IPL_TYPE_UNKNOWN;
+ if (!(ipl_flags & IPL_PARMBLOCK_VALID))
+ return IPL_TYPE_CCW;
+ if (ipl->hdr.version > IPL_MAX_SUPPORTED_VERSION)
+ return IPL_TYPE_UNKNOWN;
+ if (ipl->hdr.pbt != DIAG308_IPL_TYPE_FCP)
+ return IPL_TYPE_UNKNOWN;
+ return IPL_TYPE_FCP;
+}
+
+static ssize_t ipl_type_show(struct subsystem *subsys, char *page)
+{
+ return sprintf(page, "%s\n", ipl_type_str(ipl_get_type()));
+}
+
+static struct subsys_attribute sys_ipl_type_attr = __ATTR_RO(ipl_type);
+
+static ssize_t sys_ipl_device_show(struct subsystem *subsys, char *page)
+{
+ struct ipl_parameter_block *ipl = IPL_PARMBLOCK_START;
+
+ switch (ipl_get_type()) {
+ case IPL_TYPE_CCW:
+ return sprintf(page, "0.0.%04x\n", ipl_devno);
+ case IPL_TYPE_FCP:
+ return sprintf(page, "0.0.%04x\n", ipl->ipl_info.fcp.devno);
+ default:
+ return 0;
+ }
+}
+
+static struct subsys_attribute sys_ipl_device_attr =
+ __ATTR(device, S_IRUGO, sys_ipl_device_show, NULL);
+
+static ssize_t ipl_parameter_read(struct kobject *kobj, char *buf, loff_t off,
+ size_t count)
+{
+ unsigned int size = IPL_PARMBLOCK_SIZE;
+
+ if (off > size)
+ return 0;
+ if (off + count > size)
+ count = size - off;
+ memcpy(buf, (void *)IPL_PARMBLOCK_START + off, count);
+ return count;
+}
+
+static struct bin_attribute ipl_parameter_attr = {
+ .attr = {
+ .name = "binary_parameter",
+ .mode = S_IRUGO,
+ .owner = THIS_MODULE,
+ },
+ .size = PAGE_SIZE,
+ .read = &ipl_parameter_read,
+};
+
+static ssize_t ipl_scp_data_read(struct kobject *kobj, char *buf, loff_t off,
+ size_t count)
+{
+ unsigned int size = IPL_PARMBLOCK_START->ipl_info.fcp.scp_data_len;
+ void *scp_data = &IPL_PARMBLOCK_START->ipl_info.fcp.scp_data;
+
+ if (off > size)
+ return 0;
+ if (off + count > size)
+ count = size - off;
+ memcpy(buf, scp_data + off, count);
+ return count;
+}
+
+static struct bin_attribute ipl_scp_data_attr = {
+ .attr = {
+ .name = "scp_data",
+ .mode = S_IRUGO,
+ .owner = THIS_MODULE,
+ },
+ .size = PAGE_SIZE,
+ .read = &ipl_scp_data_read,
+};
+
+/* FCP ipl device attributes */
+
+DEFINE_IPL_ATTR_RO(ipl_fcp, wwpn, "0x%016llx\n", (unsigned long long)
+ IPL_PARMBLOCK_START->ipl_info.fcp.wwpn);
+DEFINE_IPL_ATTR_RO(ipl_fcp, lun, "0x%016llx\n", (unsigned long long)
+ IPL_PARMBLOCK_START->ipl_info.fcp.lun);
+DEFINE_IPL_ATTR_RO(ipl_fcp, bootprog, "%lld\n", (unsigned long long)
+ IPL_PARMBLOCK_START->ipl_info.fcp.bootprog);
+DEFINE_IPL_ATTR_RO(ipl_fcp, br_lba, "%lld\n", (unsigned long long)
+ IPL_PARMBLOCK_START->ipl_info.fcp.br_lba);
+
+static struct attribute *ipl_fcp_attrs[] = {
+ &sys_ipl_type_attr.attr,
+ &sys_ipl_device_attr.attr,
+ &sys_ipl_fcp_wwpn_attr.attr,
+ &sys_ipl_fcp_lun_attr.attr,
+ &sys_ipl_fcp_bootprog_attr.attr,
+ &sys_ipl_fcp_br_lba_attr.attr,
+ NULL,
+};
+
+static struct attribute_group ipl_fcp_attr_group = {
+ .attrs = ipl_fcp_attrs,
+};
+
+/* CCW ipl device attributes */
+
+static struct attribute *ipl_ccw_attrs[] = {
+ &sys_ipl_type_attr.attr,
+ &sys_ipl_device_attr.attr,
+ NULL,
+};
+
+static struct attribute_group ipl_ccw_attr_group = {
+ .attrs = ipl_ccw_attrs,
+};
+
+/* UNKNOWN ipl device attributes */
+
+static struct attribute *ipl_unknown_attrs[] = {
+ &sys_ipl_type_attr.attr,
+ NULL,
+};
+
+static struct attribute_group ipl_unknown_attr_group = {
+ .attrs = ipl_unknown_attrs,
+};
+
+static decl_subsys(ipl, NULL, NULL);
+
+/*
+ * reipl section
+ */
+
+/* FCP reipl device attributes */
+
+DEFINE_IPL_ATTR_RW(reipl_fcp, wwpn, "0x%016llx\n", "%016llx\n",
+ reipl_block_fcp->ipl_info.fcp.wwpn);
+DEFINE_IPL_ATTR_RW(reipl_fcp, lun, "0x%016llx\n", "%016llx\n",
+ reipl_block_fcp->ipl_info.fcp.lun);
+DEFINE_IPL_ATTR_RW(reipl_fcp, bootprog, "%lld\n", "%lld\n",
+ reipl_block_fcp->ipl_info.fcp.bootprog);
+DEFINE_IPL_ATTR_RW(reipl_fcp, br_lba, "%lld\n", "%lld\n",
+ reipl_block_fcp->ipl_info.fcp.br_lba);
+DEFINE_IPL_ATTR_RW(reipl_fcp, device, "0.0.%04llx\n", "0.0.%llx\n",
+ reipl_block_fcp->ipl_info.fcp.devno);
+
+static struct attribute *reipl_fcp_attrs[] = {
+ &sys_reipl_fcp_device_attr.attr,
+ &sys_reipl_fcp_wwpn_attr.attr,
+ &sys_reipl_fcp_lun_attr.attr,
+ &sys_reipl_fcp_bootprog_attr.attr,
+ &sys_reipl_fcp_br_lba_attr.attr,
+ NULL,
+};
+
+static struct attribute_group reipl_fcp_attr_group = {
+ .name = IPL_FCP_STR,
+ .attrs = reipl_fcp_attrs,
+};
+
+/* CCW reipl device attributes */
+
+DEFINE_IPL_ATTR_RW(reipl_ccw, device, "0.0.%04llx\n", "0.0.%llx\n",
+ reipl_block_ccw->ipl_info.ccw.devno);
+
+static struct attribute *reipl_ccw_attrs[] = {
+ &sys_reipl_ccw_device_attr.attr,
+ NULL,
+};
+
+static struct attribute_group reipl_ccw_attr_group = {
+ .name = IPL_CCW_STR,
+ .attrs = reipl_ccw_attrs,
+};
+
+/* reipl type */
+
+static int reipl_set_type(enum ipl_type type)
+{
+ if (!(reipl_capabilities & type))
+ return -EINVAL;
+
+ switch(type) {
+ case IPL_TYPE_CCW:
+ if (MACHINE_IS_VM)
+ reipl_method = IPL_METHOD_CCW_VM;
+ else
+ reipl_method = IPL_METHOD_CCW_CIO;
+ break;
+ case IPL_TYPE_FCP:
+ if (diag308_set_works)
+ reipl_method = IPL_METHOD_FCP_RW_DIAG;
+ else if (MACHINE_IS_VM)
+ reipl_method = IPL_METHOD_FCP_RO_VM;
+ else
+ reipl_method = IPL_METHOD_FCP_RO_DIAG;
+ break;
+ default:
+ reipl_method = IPL_METHOD_NONE;
+ }
+ reipl_type = type;
+ return 0;
+}
+
+static ssize_t reipl_type_show(struct subsystem *subsys, char *page)
+{
+ return sprintf(page, "%s\n", ipl_type_str(reipl_type));
+}
+
+static ssize_t reipl_type_store(struct subsystem *subsys, const char *buf,
+ size_t len)
+{
+ int rc = -EINVAL;
+
+ if (strncmp(buf, IPL_CCW_STR, strlen(IPL_CCW_STR)) == 0)
+ rc = reipl_set_type(IPL_TYPE_CCW);
+ else if (strncmp(buf, IPL_FCP_STR, strlen(IPL_FCP_STR)) == 0)
+ rc = reipl_set_type(IPL_TYPE_FCP);
+ return (rc != 0) ? rc : len;
+}
+
+static struct subsys_attribute reipl_type_attr =
+ __ATTR(reipl_type, 0644, reipl_type_show, reipl_type_store);
+
+static decl_subsys(reipl, NULL, NULL);
+
+/*
+ * dump section
+ */
+
+/* FCP dump device attributes */
+
+DEFINE_IPL_ATTR_RW(dump_fcp, wwpn, "0x%016llx\n", "%016llx\n",
+ dump_block_fcp->ipl_info.fcp.wwpn);
+DEFINE_IPL_ATTR_RW(dump_fcp, lun, "0x%016llx\n", "%016llx\n",
+ dump_block_fcp->ipl_info.fcp.lun);
+DEFINE_IPL_ATTR_RW(dump_fcp, bootprog, "%lld\n", "%lld\n",
+ dump_block_fcp->ipl_info.fcp.bootprog);
+DEFINE_IPL_ATTR_RW(dump_fcp, br_lba, "%lld\n", "%lld\n",
+ dump_block_fcp->ipl_info.fcp.br_lba);
+DEFINE_IPL_ATTR_RW(dump_fcp, device, "0.0.%04llx\n", "0.0.%llx\n",
+ dump_block_fcp->ipl_info.fcp.devno);
+
+static struct attribute *dump_fcp_attrs[] = {
+ &sys_dump_fcp_device_attr.attr,
+ &sys_dump_fcp_wwpn_attr.attr,
+ &sys_dump_fcp_lun_attr.attr,
+ &sys_dump_fcp_bootprog_attr.attr,
+ &sys_dump_fcp_br_lba_attr.attr,
+ NULL,
+};
+
+static struct attribute_group dump_fcp_attr_group = {
+ .name = IPL_FCP_STR,
+ .attrs = dump_fcp_attrs,
+};
+
+/* CCW dump device attributes */
+
+DEFINE_IPL_ATTR_RW(dump_ccw, device, "0.0.%04llx\n", "0.0.%llx\n",
+ dump_block_ccw->ipl_info.ccw.devno);
+
+static struct attribute *dump_ccw_attrs[] = {
+ &sys_dump_ccw_device_attr.attr,
+ NULL,
+};
+
+static struct attribute_group dump_ccw_attr_group = {
+ .name = IPL_CCW_STR,
+ .attrs = dump_ccw_attrs,
+};
+
+/* dump type */
+
+static int dump_set_type(enum ipl_type type)
+{
+ if (!(dump_capabilities & type))
+ return -EINVAL;
+ switch(type) {
+ case IPL_TYPE_CCW:
+ if (MACHINE_IS_VM)
+ dump_method = IPL_METHOD_CCW_VM;
+ else
+ dump_method = IPL_METHOD_CCW_CIO;
+ break;
+ case IPL_TYPE_FCP:
+ dump_method = IPL_METHOD_FCP_RW_DIAG;
+ break;
+ default:
+ dump_method = IPL_METHOD_NONE;
+ }
+ dump_type = type;
+ return 0;
+}
+
+static ssize_t dump_type_show(struct subsystem *subsys, char *page)
+{
+ return sprintf(page, "%s\n", ipl_type_str(dump_type));
+}
+
+static ssize_t dump_type_store(struct subsystem *subsys, const char *buf,
+ size_t len)
+{
+ int rc = -EINVAL;
+
+ if (strncmp(buf, IPL_NONE_STR, strlen(IPL_NONE_STR)) == 0)
+ rc = dump_set_type(IPL_TYPE_NONE);
+ else if (strncmp(buf, IPL_CCW_STR, strlen(IPL_CCW_STR)) == 0)
+ rc = dump_set_type(IPL_TYPE_CCW);
+ else if (strncmp(buf, IPL_FCP_STR, strlen(IPL_FCP_STR)) == 0)
+ rc = dump_set_type(IPL_TYPE_FCP);
+ return (rc != 0) ? rc : len;
+}
+
+static struct subsys_attribute dump_type_attr =
+ __ATTR(dump_type, 0644, dump_type_show, dump_type_store);
+
+static decl_subsys(dump, NULL, NULL);
+
+#ifdef CONFIG_SMP
+static void dump_smp_stop_all(void)
+{
+ int cpu;
+ preempt_disable();
+ for_each_online_cpu(cpu) {
+ if (cpu == smp_processor_id())
+ continue;
+ while (signal_processor(cpu, sigp_stop) == sigp_busy)
+ udelay(10);
+ }
+ preempt_enable();
+}
+#else
+#define dump_smp_stop_all() do { } while (0)
+#endif
+
+/*
+ * Shutdown actions section
+ */
+
+static decl_subsys(shutdown_actions, NULL, NULL);
+
+/* on panic */
+
+static ssize_t on_panic_show(struct subsystem *subsys, char *page)
+{
+ return sprintf(page, "%s\n", shutdown_action_str(on_panic_action));
+}
+
+static ssize_t on_panic_store(struct subsystem *subsys, const char *buf,
+ size_t len)
+{
+ if (strncmp(buf, SHUTDOWN_REIPL_STR, strlen(SHUTDOWN_REIPL_STR)) == 0)
+ on_panic_action = SHUTDOWN_REIPL;
+ else if (strncmp(buf, SHUTDOWN_DUMP_STR,
+ strlen(SHUTDOWN_DUMP_STR)) == 0)
+ on_panic_action = SHUTDOWN_DUMP;
+ else if (strncmp(buf, SHUTDOWN_STOP_STR,
+ strlen(SHUTDOWN_STOP_STR)) == 0)
+ on_panic_action = SHUTDOWN_STOP;
+ else
+ return -EINVAL;
+
+ return len;
+}
+
+static struct subsys_attribute on_panic_attr =
+ __ATTR(on_panic, 0644, on_panic_show, on_panic_store);
+
+static void print_fcp_block(struct ipl_parameter_block *fcp_block)
+{
+ printk(KERN_EMERG "wwpn: %016llx\n",
+ (unsigned long long)fcp_block->ipl_info.fcp.wwpn);
+ printk(KERN_EMERG "lun: %016llx\n",
+ (unsigned long long)fcp_block->ipl_info.fcp.lun);
+ printk(KERN_EMERG "bootprog: %lld\n",
+ (unsigned long long)fcp_block->ipl_info.fcp.bootprog);
+ printk(KERN_EMERG "br_lba: %lld\n",
+ (unsigned long long)fcp_block->ipl_info.fcp.br_lba);
+ printk(KERN_EMERG "device: %llx\n",
+ (unsigned long long)fcp_block->ipl_info.fcp.devno);
+ printk(KERN_EMERG "opt: %x\n", fcp_block->ipl_info.fcp.opt);
+}
+
+void do_reipl(void)
+{
+ struct ccw_dev_id devid;
+ static char buf[100];
+
+ switch (reipl_type) {
+ case IPL_TYPE_CCW:
+ printk(KERN_EMERG "reboot on ccw device: 0.0.%04x\n",
+ reipl_block_ccw->ipl_info.ccw.devno);
+ break;
+ case IPL_TYPE_FCP:
+ printk(KERN_EMERG "reboot on fcp device:\n");
+ print_fcp_block(reipl_block_fcp);
+ break;
+ default:
+ break;
+ }
+
+ switch (reipl_method) {
+ case IPL_METHOD_CCW_CIO:
+ devid.devno = reipl_block_ccw->ipl_info.ccw.devno;
+ devid.ssid = 0;
+ reipl_ccw_dev(&devid);
+ break;
+ case IPL_METHOD_CCW_VM:
+ sprintf(buf, "IPL %X", reipl_block_ccw->ipl_info.ccw.devno);
+ cpcmd(buf, NULL, 0, NULL);
+ break;
+ case IPL_METHOD_CCW_DIAG:
+ diag308(DIAG308_SET, reipl_block_ccw);
+ diag308(DIAG308_IPL, NULL);
+ break;
+ case IPL_METHOD_FCP_RW_DIAG:
+ diag308(DIAG308_SET, reipl_block_fcp);
+ diag308(DIAG308_IPL, NULL);
+ break;
+ case IPL_METHOD_FCP_RO_DIAG:
+ diag308(DIAG308_IPL, NULL);
+ break;
+ case IPL_METHOD_FCP_RO_VM:
+ cpcmd("IPL", NULL, 0, NULL);
+ break;
+ case IPL_METHOD_NONE:
+ default:
+ if (MACHINE_IS_VM)
+ cpcmd("IPL", NULL, 0, NULL);
+ diag308(DIAG308_IPL, NULL);
+ break;
+ }
+ panic("reipl failed!\n");
+}
+
+static void do_dump(void)
+{
+ struct ccw_dev_id devid;
+ static char buf[100];
+
+ switch (dump_type) {
+ case IPL_TYPE_CCW:
+ printk(KERN_EMERG "Automatic dump on ccw device: 0.0.%04x\n",
+ dump_block_ccw->ipl_info.ccw.devno);
+ break;
+ case IPL_TYPE_FCP:
+ printk(KERN_EMERG "Automatic dump on fcp device:\n");
+ print_fcp_block(dump_block_fcp);
+ break;
+ default:
+ return;
+ }
+
+ switch (dump_method) {
+ case IPL_METHOD_CCW_CIO:
+ dump_smp_stop_all();
+ devid.devno = dump_block_ccw->ipl_info.ccw.devno;
+ devid.ssid = 0;
+ reipl_ccw_dev(&devid);
+ break;
+ case IPL_METHOD_CCW_VM:
+ dump_smp_stop_all();
+ sprintf(buf, "STORE STATUS");
+ cpcmd(buf, NULL, 0, NULL);
+ sprintf(buf, "IPL %X", dump_block_ccw->ipl_info.ccw.devno);
+ cpcmd(buf, NULL, 0, NULL);
+ break;
+ case IPL_METHOD_CCW_DIAG:
+ diag308(DIAG308_SET, dump_block_ccw);
+ diag308(DIAG308_DUMP, NULL);
+ break;
+ case IPL_METHOD_FCP_RW_DIAG:
+ diag308(DIAG308_SET, dump_block_fcp);
+ diag308(DIAG308_DUMP, NULL);
+ break;
+ case IPL_METHOD_NONE:
+ default:
+ return;
+ }
+ printk(KERN_EMERG "Dump failed!\n");
+}
+
+/* init functions */
+
+static int __init ipl_register_fcp_files(void)
+{
+ int rc;
+
+ rc = sysfs_create_group(&ipl_subsys.kset.kobj,
+ &ipl_fcp_attr_group);
+ if (rc)
+ goto out;
+ rc = sysfs_create_bin_file(&ipl_subsys.kset.kobj,
+ &ipl_parameter_attr);
+ if (rc)
+ goto out_ipl_parm;
+ rc = sysfs_create_bin_file(&ipl_subsys.kset.kobj,
+ &ipl_scp_data_attr);
+ if (!rc)
+ goto out;
+
+ sysfs_remove_bin_file(&ipl_subsys.kset.kobj, &ipl_parameter_attr);
+
+out_ipl_parm:
+ sysfs_remove_group(&ipl_subsys.kset.kobj, &ipl_fcp_attr_group);
+out:
+ return rc;
+}
+
+static int __init ipl_init(void)
+{
+ int rc;
+
+ rc = firmware_register(&ipl_subsys);
+ if (rc)
+ return rc;
+ switch (ipl_get_type()) {
+ case IPL_TYPE_CCW:
+ rc = sysfs_create_group(&ipl_subsys.kset.kobj,
+ &ipl_ccw_attr_group);
+ break;
+ case IPL_TYPE_FCP:
+ rc = ipl_register_fcp_files();
+ break;
+ default:
+ rc = sysfs_create_group(&ipl_subsys.kset.kobj,
+ &ipl_unknown_attr_group);
+ break;
+ }
+ if (rc)
+ firmware_unregister(&ipl_subsys);
+ return rc;
+}
+
+static void __init reipl_probe(void)
+{
+ void *buffer;
+
+ buffer = (void *) get_zeroed_page(GFP_KERNEL);
+ if (!buffer)
+ return;
+ if (diag308(DIAG308_STORE, buffer) == DIAG308_RC_OK)
+ diag308_set_works = 1;
+ free_page((unsigned long)buffer);
+}
+
+static int __init reipl_ccw_init(void)
+{
+ int rc;
+
+ reipl_block_ccw = (void *) get_zeroed_page(GFP_KERNEL);
+ if (!reipl_block_ccw)
+ return -ENOMEM;
+ rc = sysfs_create_group(&reipl_subsys.kset.kobj, &reipl_ccw_attr_group);
+ if (rc) {
+ free_page((unsigned long)reipl_block_ccw);
+ return rc;
+ }
+ reipl_block_ccw->hdr.len = IPL_PARM_BLK_CCW_LEN;
+ reipl_block_ccw->hdr.version = IPL_PARM_BLOCK_VERSION;
+ reipl_block_ccw->hdr.blk0_len = sizeof(reipl_block_ccw->ipl_info.ccw);
+ reipl_block_ccw->hdr.pbt = DIAG308_IPL_TYPE_CCW;
+ if (ipl_get_type() == IPL_TYPE_CCW)
+ reipl_block_ccw->ipl_info.ccw.devno = ipl_devno;
+ reipl_capabilities |= IPL_TYPE_CCW;
+ return 0;
+}
+
+static int __init reipl_fcp_init(void)
+{
+ int rc;
+
+ if ((!diag308_set_works) && (ipl_get_type() != IPL_TYPE_FCP))
+ return 0;
+ if ((!diag308_set_works) && (ipl_get_type() == IPL_TYPE_FCP))
+ make_attrs_ro(reipl_fcp_attrs);
+
+ reipl_block_fcp = (void *) get_zeroed_page(GFP_KERNEL);
+ if (!reipl_block_fcp)
+ return -ENOMEM;
+ rc = sysfs_create_group(&reipl_subsys.kset.kobj, &reipl_fcp_attr_group);
+ if (rc) {
+ free_page((unsigned long)reipl_block_fcp);
+ return rc;
+ }
+ if (ipl_get_type() == IPL_TYPE_FCP) {
+ memcpy(reipl_block_fcp, IPL_PARMBLOCK_START, PAGE_SIZE);
+ } else {
+ reipl_block_fcp->hdr.len = IPL_PARM_BLK_FCP_LEN;
+ reipl_block_fcp->hdr.version = IPL_PARM_BLOCK_VERSION;
+ reipl_block_fcp->hdr.blk0_len =
+ sizeof(reipl_block_fcp->ipl_info.fcp);
+ reipl_block_fcp->hdr.pbt = DIAG308_IPL_TYPE_FCP;
+ reipl_block_fcp->ipl_info.fcp.opt = DIAG308_IPL_OPT_IPL;
+ }
+ reipl_capabilities |= IPL_TYPE_FCP;
+ return 0;
+}
+
+static int __init reipl_init(void)
+{
+ int rc;
+
+ rc = firmware_register(&reipl_subsys);
+ if (rc)
+ return rc;
+ rc = subsys_create_file(&reipl_subsys, &reipl_type_attr);
+ if (rc) {
+ firmware_unregister(&reipl_subsys);
+ return rc;
+ }
+ rc = reipl_ccw_init();
+ if (rc)
+ return rc;
+ rc = reipl_fcp_init();
+ if (rc)
+ return rc;
+ rc = reipl_set_type(ipl_get_type());
+ if (rc)
+ return rc;
+ return 0;
+}
+
+static int __init dump_ccw_init(void)
+{
+ int rc;
+
+ dump_block_ccw = (void *) get_zeroed_page(GFP_KERNEL);
+ if (!dump_block_ccw)
+ return -ENOMEM;
+ rc = sysfs_create_group(&dump_subsys.kset.kobj, &dump_ccw_attr_group);
+ if (rc) {
+ free_page((unsigned long)dump_block_ccw);
+ return rc;
+ }
+ dump_block_ccw->hdr.len = IPL_PARM_BLK_CCW_LEN;
+ dump_block_ccw->hdr.version = IPL_PARM_BLOCK_VERSION;
+ dump_block_ccw->hdr.blk0_len = sizeof(reipl_block_ccw->ipl_info.ccw);
+ dump_block_ccw->hdr.pbt = DIAG308_IPL_TYPE_CCW;
+ dump_capabilities |= IPL_TYPE_CCW;
+ return 0;
+}
+
+extern char s390_readinfo_sccb[];
+
+static int __init dump_fcp_init(void)
+{
+ int rc;
+
+ if(!(s390_readinfo_sccb[91] & 0x2))
+ return 0; /* LDIPL DUMP is not installed */
+ if (!diag308_set_works)
+ return 0;
+ dump_block_fcp = (void *) get_zeroed_page(GFP_KERNEL);
+ if (!dump_block_fcp)
+ return -ENOMEM;
+ rc = sysfs_create_group(&dump_subsys.kset.kobj, &dump_fcp_attr_group);
+ if (rc) {
+ free_page((unsigned long)dump_block_fcp);
+ return rc;
+ }
+ dump_block_fcp->hdr.len = IPL_PARM_BLK_FCP_LEN;
+ dump_block_fcp->hdr.version = IPL_PARM_BLOCK_VERSION;
+ dump_block_fcp->hdr.blk0_len = sizeof(dump_block_fcp->ipl_info.fcp);
+ dump_block_fcp->hdr.pbt = DIAG308_IPL_TYPE_FCP;
+ dump_block_fcp->ipl_info.fcp.opt = DIAG308_IPL_OPT_DUMP;
+ dump_capabilities |= IPL_TYPE_FCP;
+ return 0;
+}
+
+#define SHUTDOWN_ON_PANIC_PRIO 0
+
+static int shutdown_on_panic_notify(struct notifier_block *self,
+ unsigned long event, void *data)
+{
+ if (on_panic_action == SHUTDOWN_DUMP)
+ do_dump();
+ else if (on_panic_action == SHUTDOWN_REIPL)
+ do_reipl();
+ return NOTIFY_OK;
+}
+
+static struct notifier_block shutdown_on_panic_nb = {
+ .notifier_call = shutdown_on_panic_notify,
+ .priority = SHUTDOWN_ON_PANIC_PRIO
+};
+
+static int __init dump_init(void)
+{
+ int rc;
+
+ rc = firmware_register(&dump_subsys);
+ if (rc)
+ return rc;
+ rc = subsys_create_file(&dump_subsys, &dump_type_attr);
+ if (rc) {
+ firmware_unregister(&dump_subsys);
+ return rc;
+ }
+ rc = dump_ccw_init();
+ if (rc)
+ return rc;
+ rc = dump_fcp_init();
+ if (rc)
+ return rc;
+ dump_set_type(IPL_TYPE_NONE);
+ return 0;
+}
+
+static int __init shutdown_actions_init(void)
+{
+ int rc;
+
+ rc = firmware_register(&shutdown_actions_subsys);
+ if (rc)
+ return rc;
+ rc = subsys_create_file(&shutdown_actions_subsys, &on_panic_attr);
+ if (rc) {
+ firmware_unregister(&shutdown_actions_subsys);
+ return rc;
+ }
+ atomic_notifier_chain_register(&panic_notifier_list,
+ &shutdown_on_panic_nb);
+ return 0;
+}
+
+static int __init s390_ipl_init(void)
+{
+ int rc;
+
+ reipl_probe();
+ rc = ipl_init();
+ if (rc)
+ return rc;
+ rc = reipl_init();
+ if (rc)
+ return rc;
+ rc = dump_init();
+ if (rc)
+ return rc;
+ rc = shutdown_actions_init();
+ if (rc)
+ return rc;
+ return 0;
+}
+
+__initcall(s390_ipl_init);
--- /dev/null
+/*
+ * Kernel Probes (KProbes)
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) IBM Corporation, 2002, 2006
+ *
+ * s390 port, used ppc64 as template. Mike Grundy <grundym@us.ibm.com>
+ */
+
+#include <linux/config.h>
+#include <linux/kprobes.h>
+#include <linux/ptrace.h>
+#include <linux/preempt.h>
+#include <linux/stop_machine.h>
+#include <asm/cacheflush.h>
+#include <asm/kdebug.h>
+#include <asm/sections.h>
+#include <asm/uaccess.h>
+#include <linux/module.h>
+
+DEFINE_PER_CPU(struct kprobe *, current_kprobe) = NULL;
+DEFINE_PER_CPU(struct kprobe_ctlblk, kprobe_ctlblk);
+
+int __kprobes arch_prepare_kprobe(struct kprobe *p)
+{
+ /* Make sure the probe isn't going on a difficult instruction */
+ if (is_prohibited_opcode((kprobe_opcode_t *) p->addr))
+ return -EINVAL;
+
+ if ((unsigned long)p->addr & 0x01) {
+ printk("Attempt to register kprobe at an unaligned address\n");
+ return -EINVAL;
+ }
+
+ /* Use the get_insn_slot() facility for correctness */
+ if (!(p->ainsn.insn = get_insn_slot()))
+ return -ENOMEM;
+
+ memcpy(p->ainsn.insn, p->addr, MAX_INSN_SIZE * sizeof(kprobe_opcode_t));
+
+ get_instruction_type(&p->ainsn);
+ p->opcode = *p->addr;
+ return 0;
+}
+
+int __kprobes is_prohibited_opcode(kprobe_opcode_t *instruction)
+{
+ switch (*(__u8 *) instruction) {
+ case 0x0c: /* bassm */
+ case 0x0b: /* bsm */
+ case 0x83: /* diag */
+ case 0x44: /* ex */
+ return -EINVAL;
+ }
+ switch (*(__u16 *) instruction) {
+ case 0x0101: /* pr */
+ case 0xb25a: /* bsa */
+ case 0xb240: /* bakr */
+ case 0xb258: /* bsg */
+ case 0xb218: /* pc */
+ case 0xb228: /* pt */
+ return -EINVAL;
+ }
+ return 0;
+}
+
+void __kprobes get_instruction_type(struct arch_specific_insn *ainsn)
+{
+ /* default fixup method */
+ ainsn->fixup = FIXUP_PSW_NORMAL;
+
+ /* save r1 operand */
+ ainsn->reg = (*ainsn->insn & 0xf0) >> 4;
+
+ /* save the instruction length (pop 5-5) in bytes */
+ switch (*(__u8 *) (ainsn->insn) >> 4) {
+ case 0:
+ ainsn->ilen = 2;
+ break;
+ case 1:
+ case 2:
+ ainsn->ilen = 4;
+ break;
+ case 3:
+ ainsn->ilen = 6;
+ break;
+ }
+
+ switch (*(__u8 *) ainsn->insn) {
+ case 0x05: /* balr */
+ case 0x0d: /* basr */
+ ainsn->fixup = FIXUP_RETURN_REGISTER;
+ /* if r2 = 0, no branch will be taken */
+ if ((*ainsn->insn & 0x0f) == 0)
+ ainsn->fixup |= FIXUP_BRANCH_NOT_TAKEN;
+ break;
+ case 0x06: /* bctr */
+ case 0x07: /* bcr */
+ ainsn->fixup = FIXUP_BRANCH_NOT_TAKEN;
+ break;
+ case 0x45: /* bal */
+ case 0x4d: /* bas */
+ ainsn->fixup = FIXUP_RETURN_REGISTER;
+ break;
+ case 0x47: /* bc */
+ case 0x46: /* bct */
+ case 0x86: /* bxh */
+ case 0x87: /* bxle */
+ ainsn->fixup = FIXUP_BRANCH_NOT_TAKEN;
+ break;
+ case 0x82: /* lpsw */
+ ainsn->fixup = FIXUP_NOT_REQUIRED;
+ break;
+ case 0xb2: /* lpswe */
+ if (*(((__u8 *) ainsn->insn) + 1) == 0xb2) {
+ ainsn->fixup = FIXUP_NOT_REQUIRED;
+ }
+ break;
+ case 0xa7: /* bras */
+ if ((*ainsn->insn & 0x0f) == 0x05) {
+ ainsn->fixup |= FIXUP_RETURN_REGISTER;
+ }
+ break;
+ case 0xc0:
+ if ((*ainsn->insn & 0x0f) == 0x00 /* larl */
+ || (*ainsn->insn & 0x0f) == 0x05) /* brasl */
+ ainsn->fixup |= FIXUP_RETURN_REGISTER;
+ break;
+ case 0xeb:
+ if (*(((__u8 *) ainsn->insn) + 5 ) == 0x44 || /* bxhg */
+ *(((__u8 *) ainsn->insn) + 5) == 0x45) {/* bxleg */
+ ainsn->fixup = FIXUP_BRANCH_NOT_TAKEN;
+ }
+ break;
+ case 0xe3: /* bctg */
+ if (*(((__u8 *) ainsn->insn) + 5) == 0x46) {
+ ainsn->fixup = FIXUP_BRANCH_NOT_TAKEN;
+ }
+ break;
+ }
+}
+
+static int __kprobes swap_instruction(void *aref)
+{
+ struct ins_replace_args *args = aref;
+ int err = -EFAULT;
+
+ asm volatile(
+ "0: mvc 0(2,%2),0(%3)\n"
+ "1: la %0,0\n"
+ "2:\n"
+ EX_TABLE(0b,2b)
+ : "+d" (err), "=m" (*args->ptr)
+ : "a" (args->ptr), "a" (&args->new), "m" (args->new));
+ return err;
+}
+
+void __kprobes arch_arm_kprobe(struct kprobe *p)
+{
+ struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+ unsigned long status = kcb->kprobe_status;
+ struct ins_replace_args args;
+
+ args.ptr = p->addr;
+ args.old = p->opcode;
+ args.new = BREAKPOINT_INSTRUCTION;
+
+ kcb->kprobe_status = KPROBE_SWAP_INST;
+ stop_machine_run(swap_instruction, &args, NR_CPUS);
+ kcb->kprobe_status = status;
+}
+
+void __kprobes arch_disarm_kprobe(struct kprobe *p)
+{
+ struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+ unsigned long status = kcb->kprobe_status;
+ struct ins_replace_args args;
+
+ args.ptr = p->addr;
+ args.old = BREAKPOINT_INSTRUCTION;
+ args.new = p->opcode;
+
+ kcb->kprobe_status = KPROBE_SWAP_INST;
+ stop_machine_run(swap_instruction, &args, NR_CPUS);
+ kcb->kprobe_status = status;
+}
+
+void __kprobes arch_remove_kprobe(struct kprobe *p)
+{
+ mutex_lock(&kprobe_mutex);
+ free_insn_slot(p->ainsn.insn);
+ mutex_unlock(&kprobe_mutex);
+}
+
+static void __kprobes prepare_singlestep(struct kprobe *p, struct pt_regs *regs)
+{
+ per_cr_bits kprobe_per_regs[1];
+
+ memset(kprobe_per_regs, 0, sizeof(per_cr_bits));
+ regs->psw.addr = (unsigned long)p->ainsn.insn | PSW_ADDR_AMODE;
+
+ /* Set up the per control reg info, will pass to lctl */
+ kprobe_per_regs[0].em_instruction_fetch = 1;
+ kprobe_per_regs[0].starting_addr = (unsigned long)p->ainsn.insn;
+ kprobe_per_regs[0].ending_addr = (unsigned long)p->ainsn.insn + 1;
+
+ /* Set the PER control regs, turns on single step for this address */
+ __ctl_load(kprobe_per_regs, 9, 11);
+ regs->psw.mask |= PSW_MASK_PER;
+ regs->psw.mask &= ~(PSW_MASK_IO | PSW_MASK_EXT | PSW_MASK_MCHECK);
+}
+
+static void __kprobes save_previous_kprobe(struct kprobe_ctlblk *kcb)
+{
+ kcb->prev_kprobe.kp = kprobe_running();
+ kcb->prev_kprobe.status = kcb->kprobe_status;
+ kcb->prev_kprobe.kprobe_saved_imask = kcb->kprobe_saved_imask;
+ memcpy(kcb->prev_kprobe.kprobe_saved_ctl, kcb->kprobe_saved_ctl,
+ sizeof(kcb->kprobe_saved_ctl));
+}
+
+static void __kprobes restore_previous_kprobe(struct kprobe_ctlblk *kcb)
+{
+ __get_cpu_var(current_kprobe) = kcb->prev_kprobe.kp;
+ kcb->kprobe_status = kcb->prev_kprobe.status;
+ kcb->kprobe_saved_imask = kcb->prev_kprobe.kprobe_saved_imask;
+ memcpy(kcb->kprobe_saved_ctl, kcb->prev_kprobe.kprobe_saved_ctl,
+ sizeof(kcb->kprobe_saved_ctl));
+}
+
+static void __kprobes set_current_kprobe(struct kprobe *p, struct pt_regs *regs,
+ struct kprobe_ctlblk *kcb)
+{
+ __get_cpu_var(current_kprobe) = p;
+ /* Save the interrupt and per flags */
+ kcb->kprobe_saved_imask = regs->psw.mask &
+ (PSW_MASK_PER | PSW_MASK_IO | PSW_MASK_EXT | PSW_MASK_MCHECK);
+ /* Save the control regs that govern PER */
+ __ctl_store(kcb->kprobe_saved_ctl, 9, 11);
+}
+
+/* Called with kretprobe_lock held */
+void __kprobes arch_prepare_kretprobe(struct kretprobe *rp,
+ struct pt_regs *regs)
+{
+ struct kretprobe_instance *ri;
+
+ if ((ri = get_free_rp_inst(rp)) != NULL) {
+ ri->rp = rp;
+ ri->task = current;
+ ri->ret_addr = (kprobe_opcode_t *) regs->gprs[14];
+
+ /* Replace the return addr with trampoline addr */
+ regs->gprs[14] = (unsigned long)&kretprobe_trampoline;
+
+ add_rp_inst(ri);
+ } else {
+ rp->nmissed++;
+ }
+}
+
+static int __kprobes kprobe_handler(struct pt_regs *regs)
+{
+ struct kprobe *p;
+ int ret = 0;
+ unsigned long *addr = (unsigned long *)
+ ((regs->psw.addr & PSW_ADDR_INSN) - 2);
+ struct kprobe_ctlblk *kcb;
+
+ /*
+ * We don't want to be preempted for the entire
+ * duration of kprobe processing
+ */
+ preempt_disable();
+ kcb = get_kprobe_ctlblk();
+
+ /* Check we're not actually recursing */
+ if (kprobe_running()) {
+ p = get_kprobe(addr);
+ if (p) {
+ if (kcb->kprobe_status == KPROBE_HIT_SS &&
+ *p->ainsn.insn == BREAKPOINT_INSTRUCTION) {
+ regs->psw.mask &= ~PSW_MASK_PER;
+ regs->psw.mask |= kcb->kprobe_saved_imask;
+ goto no_kprobe;
+ }
+ /* We have reentered the kprobe_handler(), since
+ * another probe was hit while within the handler.
+ * We here save the original kprobes variables and
+ * just single step on the instruction of the new probe
+ * without calling any user handlers.
+ */
+ save_previous_kprobe(kcb);
+ set_current_kprobe(p, regs, kcb);
+ kprobes_inc_nmissed_count(p);
+ prepare_singlestep(p, regs);
+ kcb->kprobe_status = KPROBE_REENTER;
+ return 1;
+ } else {
+ p = __get_cpu_var(current_kprobe);
+ if (p->break_handler && p->break_handler(p, regs)) {
+ goto ss_probe;
+ }
+ }
+ goto no_kprobe;
+ }
+
+ p = get_kprobe(addr);
+ if (!p) {
+ if (*addr != BREAKPOINT_INSTRUCTION) {
+ /*
+ * The breakpoint instruction was removed right
+ * after we hit it. Another cpu has removed
+ * either a probepoint or a debugger breakpoint
+ * at this address. In either case, no further
+ * handling of this interrupt is appropriate.
+ *
+ */
+ ret = 1;
+ }
+ /* Not one of ours: let kernel handle it */
+ goto no_kprobe;
+ }
+
+ kcb->kprobe_status = KPROBE_HIT_ACTIVE;
+ set_current_kprobe(p, regs, kcb);
+ if (p->pre_handler && p->pre_handler(p, regs))
+ /* handler has already set things up, so skip ss setup */
+ return 1;
+
+ss_probe:
+ prepare_singlestep(p, regs);
+ kcb->kprobe_status = KPROBE_HIT_SS;
+ return 1;
+
+no_kprobe:
+ preempt_enable_no_resched();
+ return ret;
+}
+
+/*
+ * Function return probe trampoline:
+ * - init_kprobes() establishes a probepoint here
+ * - When the probed function returns, this probe
+ * causes the handlers to fire
+ */
+void __kprobes kretprobe_trampoline_holder(void)
+{
+ asm volatile(".global kretprobe_trampoline\n"
+ "kretprobe_trampoline: bcr 0,0\n");
+}
+
+/*
+ * Called when the probe at kretprobe trampoline is hit
+ */
+int __kprobes trampoline_probe_handler(struct kprobe *p, struct pt_regs *regs)
+{
+ struct kretprobe_instance *ri = NULL;
+ struct hlist_head *head;
+ struct hlist_node *node, *tmp;
+ unsigned long flags, orig_ret_address = 0;
+ unsigned long trampoline_address = (unsigned long)&kretprobe_trampoline;
+
+ spin_lock_irqsave(&kretprobe_lock, flags);
+ head = kretprobe_inst_table_head(current);
+
+ /*
+ * It is possible to have multiple instances associated with a given
+ * task either because an multiple functions in the call path
+ * have a return probe installed on them, and/or more then one return
+ * return probe was registered for a target function.
+ *
+ * We can handle this because:
+ * - instances are always inserted at the head of the list
+ * - when multiple return probes are registered for the same
+ * function, the first instance's ret_addr will point to the
+ * real return address, and all the rest will point to
+ * kretprobe_trampoline
+ */
+ hlist_for_each_entry_safe(ri, node, tmp, head, hlist) {
+ if (ri->task != current)
+ /* another task is sharing our hash bucket */
+ continue;
+
+ if (ri->rp && ri->rp->handler)
+ ri->rp->handler(ri, regs);
+
+ orig_ret_address = (unsigned long)ri->ret_addr;
+ recycle_rp_inst(ri);
+
+ if (orig_ret_address != trampoline_address) {
+ /*
+ * This is the real return address. Any other
+ * instances associated with this task are for
+ * other calls deeper on the call stack
+ */
+ break;
+ }
+ }
+ BUG_ON(!orig_ret_address || (orig_ret_address == trampoline_address));
+ regs->psw.addr = orig_ret_address | PSW_ADDR_AMODE;
+
+ reset_current_kprobe();
+ spin_unlock_irqrestore(&kretprobe_lock, flags);
+ preempt_enable_no_resched();
+
+ /*
+ * By returning a non-zero value, we are telling
+ * kprobe_handler() that we don't want the post_handler
+ * to run (and have re-enabled preemption)
+ */
+ return 1;
+}
+
+/*
+ * Called after single-stepping. p->addr is the address of the
+ * instruction whose first byte has been replaced by the "breakpoint"
+ * instruction. To avoid the SMP problems that can occur when we
+ * temporarily put back the original opcode to single-step, we
+ * single-stepped a copy of the instruction. The address of this
+ * copy is p->ainsn.insn.
+ */
+static void __kprobes resume_execution(struct kprobe *p, struct pt_regs *regs)
+{
+ struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+
+ regs->psw.addr &= PSW_ADDR_INSN;
+
+ if (p->ainsn.fixup & FIXUP_PSW_NORMAL)
+ regs->psw.addr = (unsigned long)p->addr +
+ ((unsigned long)regs->psw.addr -
+ (unsigned long)p->ainsn.insn);
+
+ if (p->ainsn.fixup & FIXUP_BRANCH_NOT_TAKEN)
+ if ((unsigned long)regs->psw.addr -
+ (unsigned long)p->ainsn.insn == p->ainsn.ilen)
+ regs->psw.addr = (unsigned long)p->addr + p->ainsn.ilen;
+
+ if (p->ainsn.fixup & FIXUP_RETURN_REGISTER)
+ regs->gprs[p->ainsn.reg] = ((unsigned long)p->addr +
+ (regs->gprs[p->ainsn.reg] -
+ (unsigned long)p->ainsn.insn))
+ | PSW_ADDR_AMODE;
+
+ regs->psw.addr |= PSW_ADDR_AMODE;
+ /* turn off PER mode */
+ regs->psw.mask &= ~PSW_MASK_PER;
+ /* Restore the original per control regs */
+ __ctl_load(kcb->kprobe_saved_ctl, 9, 11);
+ regs->psw.mask |= kcb->kprobe_saved_imask;
+}
+
+static int __kprobes post_kprobe_handler(struct pt_regs *regs)
+{
+ struct kprobe *cur = kprobe_running();
+ struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+
+ if (!cur)
+ return 0;
+
+ if ((kcb->kprobe_status != KPROBE_REENTER) && cur->post_handler) {
+ kcb->kprobe_status = KPROBE_HIT_SSDONE;
+ cur->post_handler(cur, regs, 0);
+ }
+
+ resume_execution(cur, regs);
+
+ /*Restore back the original saved kprobes variables and continue. */
+ if (kcb->kprobe_status == KPROBE_REENTER) {
+ restore_previous_kprobe(kcb);
+ goto out;
+ }
+ reset_current_kprobe();
+out:
+ preempt_enable_no_resched();
+
+ /*
+ * if somebody else is singlestepping across a probe point, psw mask
+ * will have PER set, in which case, continue the remaining processing
+ * of do_single_step, as if this is not a probe hit.
+ */
+ if (regs->psw.mask & PSW_MASK_PER) {
+ return 0;
+ }
+
+ return 1;
+}
+
+static int __kprobes kprobe_fault_handler(struct pt_regs *regs, int trapnr)
+{
+ struct kprobe *cur = kprobe_running();
+ struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+ const struct exception_table_entry *entry;
+
+ switch(kcb->kprobe_status) {
+ case KPROBE_SWAP_INST:
+ /* We are here because the instruction replacement failed */
+ return 0;
+ case KPROBE_HIT_SS:
+ case KPROBE_REENTER:
+ /*
+ * We are here because the instruction being single
+ * stepped caused a page fault. We reset the current
+ * kprobe and the nip points back to the probe address
+ * and allow the page fault handler to continue as a
+ * normal page fault.
+ */
+ regs->psw.addr = (unsigned long)cur->addr | PSW_ADDR_AMODE;
+ regs->psw.mask &= ~PSW_MASK_PER;
+ regs->psw.mask |= kcb->kprobe_saved_imask;
+ if (kcb->kprobe_status == KPROBE_REENTER)
+ restore_previous_kprobe(kcb);
+ else
+ reset_current_kprobe();
+ preempt_enable_no_resched();
+ break;
+ case KPROBE_HIT_ACTIVE:
+ case KPROBE_HIT_SSDONE:
+ /*
+ * We increment the nmissed count for accounting,
+ * we can also use npre/npostfault count for accouting
+ * these specific fault cases.
+ */
+ kprobes_inc_nmissed_count(cur);
+
+ /*
+ * We come here because instructions in the pre/post
+ * handler caused the page_fault, this could happen
+ * if handler tries to access user space by
+ * copy_from_user(), get_user() etc. Let the
+ * user-specified handler try to fix it first.
+ */
+ if (cur->fault_handler && cur->fault_handler(cur, regs, trapnr))
+ return 1;
+
+ /*
+ * In case the user-specified fault handler returned
+ * zero, try to fix up.
+ */
+ entry = search_exception_tables(regs->psw.addr & PSW_ADDR_INSN);
+ if (entry) {
+ regs->psw.addr = entry->fixup | PSW_ADDR_AMODE;
+ return 1;
+ }
+
+ /*
+ * fixup_exception() could not handle it,
+ * Let do_page_fault() fix it.
+ */
+ break;
+ default:
+ break;
+ }
+ return 0;
+}
+
+/*
+ * Wrapper routine to for handling exceptions.
+ */
+int __kprobes kprobe_exceptions_notify(struct notifier_block *self,
+ unsigned long val, void *data)
+{
+ struct die_args *args = (struct die_args *)data;
+ int ret = NOTIFY_DONE;
+
+ switch (val) {
+ case DIE_BPT:
+ if (kprobe_handler(args->regs))
+ ret = NOTIFY_STOP;
+ break;
+ case DIE_SSTEP:
+ if (post_kprobe_handler(args->regs))
+ ret = NOTIFY_STOP;
+ break;
+ case DIE_TRAP:
+ case DIE_PAGE_FAULT:
+ /* kprobe_running() needs smp_processor_id() */
+ preempt_disable();
+ if (kprobe_running() &&
+ kprobe_fault_handler(args->regs, args->trapnr))
+ ret = NOTIFY_STOP;
+ preempt_enable();
+ break;
+ default:
+ break;
+ }
+ return ret;
+}
+
+int __kprobes setjmp_pre_handler(struct kprobe *p, struct pt_regs *regs)
+{
+ struct jprobe *jp = container_of(p, struct jprobe, kp);
+ unsigned long addr;
+ struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+
+ memcpy(&kcb->jprobe_saved_regs, regs, sizeof(struct pt_regs));
+
+ /* setup return addr to the jprobe handler routine */
+ regs->psw.addr = (unsigned long)(jp->entry) | PSW_ADDR_AMODE;
+
+ /* r14 is the function return address */
+ kcb->jprobe_saved_r14 = (unsigned long)regs->gprs[14];
+ /* r15 is the stack pointer */
+ kcb->jprobe_saved_r15 = (unsigned long)regs->gprs[15];
+ addr = (unsigned long)kcb->jprobe_saved_r15;
+
+ memcpy(kcb->jprobes_stack, (kprobe_opcode_t *) addr,
+ MIN_STACK_SIZE(addr));
+ return 1;
+}
+
+void __kprobes jprobe_return(void)
+{
+ asm volatile(".word 0x0002");
+}
+
+void __kprobes jprobe_return_end(void)
+{
+ asm volatile("bcr 0,0");
+}
+
+int __kprobes longjmp_break_handler(struct kprobe *p, struct pt_regs *regs)
+{
+ struct kprobe_ctlblk *kcb = get_kprobe_ctlblk();
+ unsigned long stack_addr = (unsigned long)(kcb->jprobe_saved_r15);
+
+ /* Put the regs back */
+ memcpy(regs, &kcb->jprobe_saved_regs, sizeof(struct pt_regs));
+ /* put the stack back */
+ memcpy((kprobe_opcode_t *) stack_addr, kcb->jprobes_stack,
+ MIN_STACK_SIZE(stack_addr));
+ preempt_enable_no_resched();
+ return 1;
+}
+
+static struct kprobe trampoline_p = {
+ .addr = (kprobe_opcode_t *) & kretprobe_trampoline,
+ .pre_handler = trampoline_probe_handler
+};
+
+int __init arch_init_kprobes(void)
+{
+ return register_kprobe(&trampoline_p);
+}
#include <asm/lowcore.h>
- .globl do_reipl
-do_reipl: basr %r13,0
+ .globl do_reipl_asm
+do_reipl_asm: basr %r13,0
.Lpg0: lpsw .Lnewpsw-.Lpg0(%r13)
-.Lpg1: lctl %c6,%c6,.Lall-.Lpg0(%r13)
- stctl %c0,%c0,.Lctlsave-.Lpg0(%r13)
- ni .Lctlsave-.Lpg0(%r13),0xef
- lctl %c0,%c0,.Lctlsave-.Lpg0(%r13)
+
+ # switch off lowcore protection
+
+.Lpg1: stctl %c0,%c0,.Lctlsave1-.Lpg0(%r13)
+ stctl %c0,%c0,.Lctlsave2-.Lpg0(%r13)
+ ni .Lctlsave1-.Lpg0(%r13),0xef
+ lctl %c0,%c0,.Lctlsave1-.Lpg0(%r13)
+
+ # do store status of all registers
+
+ stm %r0,%r15,__LC_GPREGS_SAVE_AREA
+ stctl %c0,%c15,__LC_CREGS_SAVE_AREA
+ mvc __LC_CREGS_SAVE_AREA(4),.Lctlsave2-.Lpg0(%r13)
+ stam %a0,%a15,__LC_AREGS_SAVE_AREA
+ stpx __LC_PREFIX_SAVE_AREA
+ stckc .Lclkcmp-.Lpg0(%r13)
+ mvc __LC_CLOCK_COMP_SAVE_AREA(8),.Lclkcmp-.Lpg0(%r13)
+ stpt __LC_CPU_TIMER_SAVE_AREA
+ st %r13, __LC_PSW_SAVE_AREA+4
+
+ lctl %c6,%c6,.Lall-.Lpg0(%r13)
lr %r1,%r2
mvc __LC_PGM_NEW_PSW(8),.Lpcnew-.Lpg0(%r13)
stsch .Lschib-.Lpg0(%r13)
.Ldisab: st %r14,.Ldispsw+4-.Lpg0(%r13)
lpsw .Ldispsw-.Lpg0(%r13)
.align 8
+.Lclkcmp: .quad 0x0000000000000000
.Lall: .long 0xff000000
.Lnull: .long 0x00000000
-.Lctlsave: .long 0x00000000
+.Lctlsave1: .long 0x00000000
+.Lctlsave2: .long 0x00000000
.align 8
.Lnewpsw: .long 0x00080000,0x80000000+.Lpg1
.Lpcnew: .long 0x00080000,0x80000000+.Lecs
*/
#include <asm/lowcore.h>
- .globl do_reipl
-do_reipl: basr %r13,0
-.Lpg0: lpswe .Lnewpsw-.Lpg0(%r13)
+ .globl do_reipl_asm
+do_reipl_asm: basr %r13,0
+
+ # do store status of all registers
+
+.Lpg0: stg %r1,.Lregsave-.Lpg0(%r13)
+ lghi %r1,0x1000
+ stmg %r0,%r15,__LC_GPREGS_SAVE_AREA-0x1000(%r1)
+ lg %r0,.Lregsave-.Lpg0(%r13)
+ stg %r0,__LC_GPREGS_SAVE_AREA-0x1000+8(%r1)
+ stctg %c0,%c15,__LC_CREGS_SAVE_AREA-0x1000(%r1)
+ stam %a0,%a15,__LC_AREGS_SAVE_AREA-0x1000(%r1)
+ stpx __LC_PREFIX_SAVE_AREA-0x1000(%r1)
+ stfpc __LC_FP_CREG_SAVE_AREA-0x1000(%r1)
+ stckc .Lclkcmp-.Lpg0(%r13)
+ mvc __LC_CLOCK_COMP_SAVE_AREA-0x1000(8,%r1),.Lclkcmp-.Lpg0(%r13)
+ stpt __LC_CPU_TIMER_SAVE_AREA-0x1000(%r1)
+ stg %r13, __LC_PSW_SAVE_AREA-0x1000+8(%r1)
+
+ lpswe .Lnewpsw-.Lpg0(%r13)
.Lpg1: lctlg %c6,%c6,.Lall-.Lpg0(%r13)
- stctg %c0,%c0,.Lctlsave-.Lpg0(%r13)
- ni .Lctlsave+4-.Lpg0(%r13),0xef
- lctlg %c0,%c0,.Lctlsave-.Lpg0(%r13)
+ stctg %c0,%c0,.Lregsave-.Lpg0(%r13)
+ ni .Lregsave+4-.Lpg0(%r13),0xef
+ lctlg %c0,%c0,.Lregsave-.Lpg0(%r13)
lgr %r1,%r2
mvc __LC_PGM_NEW_PSW(16),.Lpcnew-.Lpg0(%r13)
stsch .Lschib-.Lpg0(%r13)
st %r14,.Ldispsw+12-.Lpg0(%r13)
lpswe .Ldispsw-.Lpg0(%r13)
.align 8
+.Lclkcmp: .quad 0x0000000000000000
.Lall: .quad 0x00000000ff000000
-.Lctlsave: .quad 0x0000000000000000
+.Lregsave: .quad 0x0000000000000000
.Lnull: .long 0x0000000000000000
.align 16
/*
.long 0x00000000,0x00000000
.long 0x00000000,0x00000000
-
-
+++ /dev/null
-/*
- * This file contains the implementation of the
- * Linux re-IPL support
- *
- * (C) Copyright IBM Corp. 2005
- *
- * Author(s): Volker Sameske (sameske@de.ibm.com)
- *
- */
-
-#include <linux/kernel.h>
-
-static unsigned int reipl_diag_rc1;
-static unsigned int reipl_diag_rc2;
-
-/*
- * re-IPL the system using the last used IPL parameters
- */
-void reipl_diag(void)
-{
- asm volatile (
- " la %%r4,0\n"
- " la %%r5,0\n"
- " diag %%r4,%2,0x308\n"
- "0:\n"
- " st %%r4,%0\n"
- " st %%r5,%1\n"
- ".section __ex_table,\"a\"\n"
-#ifdef CONFIG_64BIT
- " .align 8\n"
- " .quad 0b, 0b\n"
-#else
- " .align 4\n"
- " .long 0b, 0b\n"
-#endif
- ".previous\n"
- : "=m" (reipl_diag_rc1), "=m" (reipl_diag_rc2)
- : "d" (3) : "cc", "4", "5" );
-}
EXPORT_SYMBOL(_ni_bitmap);
EXPORT_SYMBOL(_zb_findmap);
EXPORT_SYMBOL(_sb_findmap);
-EXPORT_SYMBOL(__copy_from_user_asm);
-EXPORT_SYMBOL(__copy_to_user_asm);
-EXPORT_SYMBOL(__copy_in_user_asm);
-EXPORT_SYMBOL(__clear_user_asm);
-EXPORT_SYMBOL(__strncpy_from_user_asm);
-EXPORT_SYMBOL(__strnlen_user_asm);
EXPORT_SYMBOL(diag10);
/*
#include <linux/kernel_stat.h>
#include <linux/device.h>
#include <linux/notifier.h>
+#include <linux/pfn.h>
#include <asm/uaccess.h>
#include <asm/system.h>
#include <asm/ptrace.h>
#include <asm/sections.h>
+/*
+ * User copy operations.
+ */
+struct uaccess_ops uaccess;
+EXPORT_SYMBOL_GPL(uaccess);
+
/*
* Machine setup..
*/
/*
* Reboot, halt and power_off routines for non SMP.
*/
-extern void reipl(unsigned long devno);
-extern void reipl_diag(void);
static void do_machine_restart_nonsmp(char * __unused)
{
- reipl_diag();
-
- if (MACHINE_IS_VM)
- cpcmd ("IPL", NULL, 0, NULL);
- else
- reipl (0x10000 | S390_lowcore.ipl_device);
+ do_reipl();
}
static void do_machine_halt_nonsmp(void)
* partially used pages are not usable - thus
* we are rounding upwards:
*/
- start_pfn = (__pa(&_end) + PAGE_SIZE - 1) >> PAGE_SHIFT;
- end_pfn = max_pfn = memory_end >> PAGE_SHIFT;
+ start_pfn = PFN_UP(__pa(&_end));
+ end_pfn = max_pfn = PFN_DOWN(memory_end);
/* Initialize storage key for kernel pages */
for (init_pfn = 0 ; init_pfn < start_pfn; init_pfn++)
page_set_storage_key(init_pfn << PAGE_SHIFT, PAGE_DEFAULT_KEY);
+#ifdef CONFIG_BLK_DEV_INITRD
+ /*
+ * Move the initrd in case the bitmap of the bootmem allocater
+ * would overwrite it.
+ */
+
+ if (INITRD_START && INITRD_SIZE) {
+ unsigned long bmap_size;
+ unsigned long start;
+
+ bmap_size = bootmem_bootmap_pages(end_pfn - start_pfn + 1);
+ bmap_size = PFN_PHYS(bmap_size);
+
+ if (PFN_PHYS(start_pfn) + bmap_size > INITRD_START) {
+ start = PFN_PHYS(start_pfn) + bmap_size + PAGE_SIZE;
+
+ if (start + INITRD_SIZE > memory_end) {
+ printk("initrd extends beyond end of memory "
+ "(0x%08lx > 0x%08lx)\n"
+ "disabling initrd\n",
+ start + INITRD_SIZE, memory_end);
+ INITRD_START = INITRD_SIZE = 0;
+ } else {
+ printk("Moving initrd (0x%08lx -> 0x%08lx, "
+ "size: %ld)\n",
+ INITRD_START, start, INITRD_SIZE);
+ memmove((void *) start, (void *) INITRD_START,
+ INITRD_SIZE);
+ INITRD_START = start;
+ }
+ }
+ }
+#endif
+
/*
* Initialize the boot-time allocator (with low memory only):
*/
reserve_bootmem(start_pfn << PAGE_SHIFT, bootmap_size);
#ifdef CONFIG_BLK_DEV_INITRD
- if (INITRD_START) {
+ if (INITRD_START && INITRD_SIZE) {
if (INITRD_START + INITRD_SIZE <= memory_end) {
reserve_bootmem(INITRD_START, INITRD_SIZE);
initrd_start = INITRD_START;
memory_end = memory_size;
+ if (MACHINE_HAS_MVCOS)
+ memcpy(&uaccess, &uaccess_mvcos, sizeof(uaccess));
+ else
+ memcpy(&uaccess, &uaccess_std, sizeof(uaccess));
+
parse_early_param();
#ifndef CONFIG_64BIT
.show = show_cpuinfo,
};
-#define DEFINE_IPL_ATTR(_name, _format, _value) \
-static ssize_t ipl_##_name##_show(struct subsystem *subsys, \
- char *page) \
-{ \
- return sprintf(page, _format, _value); \
-} \
-static struct subsys_attribute ipl_##_name##_attr = \
- __ATTR(_name, S_IRUGO, ipl_##_name##_show, NULL);
-
-DEFINE_IPL_ATTR(wwpn, "0x%016llx\n", (unsigned long long)
- IPL_PARMBLOCK_START->fcp.wwpn);
-DEFINE_IPL_ATTR(lun, "0x%016llx\n", (unsigned long long)
- IPL_PARMBLOCK_START->fcp.lun);
-DEFINE_IPL_ATTR(bootprog, "%lld\n", (unsigned long long)
- IPL_PARMBLOCK_START->fcp.bootprog);
-DEFINE_IPL_ATTR(br_lba, "%lld\n", (unsigned long long)
- IPL_PARMBLOCK_START->fcp.br_lba);
-
-enum ipl_type_type {
- ipl_type_unknown,
- ipl_type_ccw,
- ipl_type_fcp,
-};
-
-static enum ipl_type_type
-get_ipl_type(void)
-{
- struct ipl_parameter_block *ipl = IPL_PARMBLOCK_START;
-
- if (!IPL_DEVNO_VALID)
- return ipl_type_unknown;
- if (!IPL_PARMBLOCK_VALID)
- return ipl_type_ccw;
- if (ipl->hdr.header.version > IPL_MAX_SUPPORTED_VERSION)
- return ipl_type_unknown;
- if (ipl->fcp.pbt != IPL_TYPE_FCP)
- return ipl_type_unknown;
- return ipl_type_fcp;
-}
-
-static ssize_t
-ipl_type_show(struct subsystem *subsys, char *page)
-{
- switch (get_ipl_type()) {
- case ipl_type_ccw:
- return sprintf(page, "ccw\n");
- case ipl_type_fcp:
- return sprintf(page, "fcp\n");
- default:
- return sprintf(page, "unknown\n");
- }
-}
-
-static struct subsys_attribute ipl_type_attr = __ATTR_RO(ipl_type);
-
-static ssize_t
-ipl_device_show(struct subsystem *subsys, char *page)
-{
- struct ipl_parameter_block *ipl = IPL_PARMBLOCK_START;
-
- switch (get_ipl_type()) {
- case ipl_type_ccw:
- return sprintf(page, "0.0.%04x\n", ipl_devno);
- case ipl_type_fcp:
- return sprintf(page, "0.0.%04x\n", ipl->fcp.devno);
- default:
- return 0;
- }
-}
-
-static struct subsys_attribute ipl_device_attr =
- __ATTR(device, S_IRUGO, ipl_device_show, NULL);
-
-static struct attribute *ipl_fcp_attrs[] = {
- &ipl_type_attr.attr,
- &ipl_device_attr.attr,
- &ipl_wwpn_attr.attr,
- &ipl_lun_attr.attr,
- &ipl_bootprog_attr.attr,
- &ipl_br_lba_attr.attr,
- NULL,
-};
-
-static struct attribute_group ipl_fcp_attr_group = {
- .attrs = ipl_fcp_attrs,
-};
-
-static struct attribute *ipl_ccw_attrs[] = {
- &ipl_type_attr.attr,
- &ipl_device_attr.attr,
- NULL,
-};
-
-static struct attribute_group ipl_ccw_attr_group = {
- .attrs = ipl_ccw_attrs,
-};
-
-static struct attribute *ipl_unknown_attrs[] = {
- &ipl_type_attr.attr,
- NULL,
-};
-
-static struct attribute_group ipl_unknown_attr_group = {
- .attrs = ipl_unknown_attrs,
-};
-
-static ssize_t
-ipl_parameter_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
-{
- unsigned int size = IPL_PARMBLOCK_SIZE;
-
- if (off > size)
- return 0;
- if (off + count > size)
- count = size - off;
-
- memcpy(buf, (void *) IPL_PARMBLOCK_START + off, count);
- return count;
-}
-
-static struct bin_attribute ipl_parameter_attr = {
- .attr = {
- .name = "binary_parameter",
- .mode = S_IRUGO,
- .owner = THIS_MODULE,
- },
- .size = PAGE_SIZE,
- .read = &ipl_parameter_read,
-};
-
-static ssize_t
-ipl_scp_data_read(struct kobject *kobj, char *buf, loff_t off, size_t count)
-{
- unsigned int size = IPL_PARMBLOCK_START->fcp.scp_data_len;
- void *scp_data = &IPL_PARMBLOCK_START->fcp.scp_data;
-
- if (off > size)
- return 0;
- if (off + count > size)
- count = size - off;
-
- memcpy(buf, scp_data + off, count);
- return count;
-}
-
-static struct bin_attribute ipl_scp_data_attr = {
- .attr = {
- .name = "scp_data",
- .mode = S_IRUGO,
- .owner = THIS_MODULE,
- },
- .size = PAGE_SIZE,
- .read = &ipl_scp_data_read,
-};
-
-static decl_subsys(ipl, NULL, NULL);
-
-static int ipl_register_fcp_files(void)
-{
- int rc;
-
- rc = sysfs_create_group(&ipl_subsys.kset.kobj,
- &ipl_fcp_attr_group);
- if (rc)
- goto out;
- rc = sysfs_create_bin_file(&ipl_subsys.kset.kobj,
- &ipl_parameter_attr);
- if (rc)
- goto out_ipl_parm;
- rc = sysfs_create_bin_file(&ipl_subsys.kset.kobj,
- &ipl_scp_data_attr);
- if (!rc)
- goto out;
-
- sysfs_remove_bin_file(&ipl_subsys.kset.kobj, &ipl_parameter_attr);
-
-out_ipl_parm:
- sysfs_remove_group(&ipl_subsys.kset.kobj, &ipl_fcp_attr_group);
-out:
- return rc;
-}
-
-static int __init
-ipl_device_sysfs_register(void) {
- int rc;
-
- rc = firmware_register(&ipl_subsys);
- if (rc)
- goto out;
-
- switch (get_ipl_type()) {
- case ipl_type_ccw:
- rc = sysfs_create_group(&ipl_subsys.kset.kobj,
- &ipl_ccw_attr_group);
- break;
- case ipl_type_fcp:
- rc = ipl_register_fcp_files();
- break;
- default:
- rc = sysfs_create_group(&ipl_subsys.kset.kobj,
- &ipl_unknown_attr_group);
- break;
- }
-
- if (rc)
- firmware_unregister(&ipl_subsys);
-out:
- return rc;
-}
-
-__initcall(ipl_device_sysfs_register);
static int save_sigregs(struct pt_regs *regs, _sigregs __user *sregs)
{
unsigned long old_mask = regs->psw.mask;
- int err;
-
+ _sigregs user_sregs;
+
save_access_regs(current->thread.acrs);
/* Copy a 'clean' PSW mask to the user to avoid leaking
information about whether PER is currently on. */
regs->psw.mask = PSW_MASK_MERGE(PSW_USER_BITS, regs->psw.mask);
- err = __copy_to_user(&sregs->regs.psw, ®s->psw,
- sizeof(sregs->regs.psw)+sizeof(sregs->regs.gprs));
+ memcpy(&user_sregs.regs.psw, ®s->psw, sizeof(sregs->regs.psw) +
+ sizeof(sregs->regs.gprs));
regs->psw.mask = old_mask;
- if (err != 0)
- return err;
- err = __copy_to_user(&sregs->regs.acrs, current->thread.acrs,
- sizeof(sregs->regs.acrs));
- if (err != 0)
- return err;
+ memcpy(&user_sregs.regs.acrs, current->thread.acrs,
+ sizeof(sregs->regs.acrs));
/*
* We have to store the fp registers to current->thread.fp_regs
* to merge them with the emulated registers.
*/
save_fp_regs(¤t->thread.fp_regs);
- return __copy_to_user(&sregs->fpregs, ¤t->thread.fp_regs,
- sizeof(s390_fp_regs));
+ memcpy(&user_sregs.fpregs, ¤t->thread.fp_regs,
+ sizeof(s390_fp_regs));
+ return __copy_to_user(sregs, &user_sregs, sizeof(_sigregs));
}
/* Returns positive number on error */
{
unsigned long old_mask = regs->psw.mask;
int err;
+ _sigregs user_sregs;
/* Alwys make any pending restarted system call return -EINTR */
current_thread_info()->restart_block.fn = do_no_restart_syscall;
- err = __copy_from_user(®s->psw, &sregs->regs.psw,
- sizeof(sregs->regs.psw)+sizeof(sregs->regs.gprs));
+ err = __copy_from_user(&user_sregs, sregs, sizeof(_sigregs));
regs->psw.mask = PSW_MASK_MERGE(old_mask, regs->psw.mask);
regs->psw.addr |= PSW_ADDR_AMODE;
if (err)
return err;
- err = __copy_from_user(¤t->thread.acrs, &sregs->regs.acrs,
- sizeof(sregs->regs.acrs));
- if (err)
- return err;
+ memcpy(®s->psw, &user_sregs.regs.psw, sizeof(sregs->regs.psw) +
+ sizeof(sregs->regs.gprs));
+ memcpy(¤t->thread.acrs, &user_sregs.regs.acrs,
+ sizeof(sregs->regs.acrs));
restore_access_regs(current->thread.acrs);
- err = __copy_from_user(¤t->thread.fp_regs, &sregs->fpregs,
- sizeof(s390_fp_regs));
+ memcpy(¤t->thread.fp_regs, &user_sregs.fpregs,
+ sizeof(s390_fp_regs));
current->thread.fp_regs.fpc &= FPC_VALID_MASK;
- if (err)
- return err;
restore_fp_regs(¤t->thread.fp_regs);
regs->trap = -1; /* disable syscall checks */
case -ERESTART_RESTARTBLOCK:
regs->gprs[2] = -EINTR;
}
+ regs->trap = -1; /* Don't deal with this again. */
}
/* Get signal to deliver. When running under ptrace, at this point
extern char vmhalt_cmd[];
extern char vmpoff_cmd[];
-extern void reipl(unsigned long devno);
-extern void reipl_diag(void);
-
static void smp_ext_bitcall(int, ec_bit_sig);
static void smp_ext_bitcall_others(ec_bit_sig);
* interrupted by an external interrupt and s390irq
* locks are always held disabled).
*/
- reipl_diag();
-
- if (MACHINE_IS_VM)
- cpcmd ("IPL", NULL, 0, NULL);
- else
- reipl (0x10000 | S390_lowcore.ipl_device);
+ do_reipl();
}
void machine_restart_smp(char * __unused)
#include <linux/module.h>
#include <linux/kallsyms.h>
#include <linux/reboot.h>
+#include <linux/kprobes.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/s390_ext.h>
#include <asm/lowcore.h>
#include <asm/debug.h>
+#include <asm/kdebug.h>
/* Called from entry.S only */
extern void handle_per_exception(struct pt_regs *regs);
static int kstack_depth_to_print = 20;
#endif /* CONFIG_64BIT */
+ATOMIC_NOTIFIER_HEAD(s390die_chain);
+
+int register_die_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_register(&s390die_chain, nb);
+}
+EXPORT_SYMBOL(register_die_notifier);
+
+int unregister_die_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_unregister(&s390die_chain, nb);
+}
+EXPORT_SYMBOL(unregister_die_notifier);
+
/*
* For show_trace we have tree different stack to consider:
* - the panic stack which is used if the kernel stack has overflown
#endif
}
-static void inline do_trap(long interruption_code, int signr, char *str,
- struct pt_regs *regs, siginfo_t *info)
+static void __kprobes inline do_trap(long interruption_code, int signr,
+ char *str, struct pt_regs *regs,
+ siginfo_t *info)
{
/*
* We got all needed information from the lowcore and can
if (regs->psw.mask & PSW_MASK_PSTATE)
local_irq_enable();
+ if (notify_die(DIE_TRAP, str, regs, interruption_code,
+ interruption_code, signr) == NOTIFY_STOP)
+ return;
+
if (regs->psw.mask & PSW_MASK_PSTATE) {
struct task_struct *tsk = current;
return (void __user *)((regs->psw.addr-S390_lowcore.pgm_ilc) & PSW_ADDR_INSN);
}
-void do_single_step(struct pt_regs *regs)
+void __kprobes do_single_step(struct pt_regs *regs)
{
+ if (notify_die(DIE_SSTEP, "sstep", regs, 0, 0,
+ SIGTRAP) == NOTIFY_STOP){
+ return;
+ }
if ((current->ptrace & PT_PTRACED) != 0)
force_sig(SIGTRAP, current);
}
*(.text)
SCHED_TEXT
LOCK_TEXT
+ KPROBES_TEXT
*(.fixup)
*(.gnu.warning)
} = 0x0700
/* Sections to be discarded */
/DISCARD/ : {
- *(.exitcall.exit)
+ *(.exit.text) *(.exit.data) *(.exitcall.exit)
}
/* Stabs debugging sections. */
EXTRA_AFLAGS := -traditional
-lib-y += delay.o string.o
-lib-y += $(if $(CONFIG_64BIT),uaccess64.o,uaccess.o)
+lib-y += delay.o string.o uaccess_std.o
+lib-$(CONFIG_64BIT) += uaccess_mvcos.o
lib-$(CONFIG_SMP) += spinlock.o
+++ /dev/null
-/*
- * arch/s390/lib/uaccess.S
- * __copy_{from|to}_user functions.
- *
- * s390
- * Copyright (C) 2000,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Authors(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
- *
- * These functions have standard call interface
- */
-
-#include <linux/errno.h>
-#include <asm/lowcore.h>
-#include <asm/asm-offsets.h>
-
- .text
- .align 4
- .globl __copy_from_user_asm
- # %r2 = to, %r3 = n, %r4 = from
-__copy_from_user_asm:
- slr %r0,%r0
-0: mvcp 0(%r3,%r2),0(%r4),%r0
- jnz 1f
- slr %r2,%r2
- br %r14
-1: la %r2,256(%r2)
- la %r4,256(%r4)
- ahi %r3,-256
-2: mvcp 0(%r3,%r2),0(%r4),%r0
- jnz 1b
-3: slr %r2,%r2
- br %r14
-4: lhi %r0,-4096
- lr %r5,%r4
- slr %r5,%r0
- nr %r5,%r0 # %r5 = (%r4 + 4096) & -4096
- slr %r5,%r4 # %r5 = #bytes to next user page boundary
- clr %r3,%r5 # copy crosses next page boundary ?
- jnh 6f # no, the current page faulted
- # move with the reduced length which is < 256
-5: mvcp 0(%r5,%r2),0(%r4),%r0
- slr %r3,%r5
-6: lr %r2,%r3
- br %r14
- .section __ex_table,"a"
- .long 0b,4b
- .long 2b,4b
- .long 5b,6b
- .previous
-
- .align 4
- .text
- .globl __copy_to_user_asm
- # %r2 = from, %r3 = n, %r4 = to
-__copy_to_user_asm:
- slr %r0,%r0
-0: mvcs 0(%r3,%r4),0(%r2),%r0
- jnz 1f
- slr %r2,%r2
- br %r14
-1: la %r2,256(%r2)
- la %r4,256(%r4)
- ahi %r3,-256
-2: mvcs 0(%r3,%r4),0(%r2),%r0
- jnz 1b
-3: slr %r2,%r2
- br %r14
-4: lhi %r0,-4096
- lr %r5,%r4
- slr %r5,%r0
- nr %r5,%r0 # %r5 = (%r4 + 4096) & -4096
- slr %r5,%r4 # %r5 = #bytes to next user page boundary
- clr %r3,%r5 # copy crosses next page boundary ?
- jnh 6f # no, the current page faulted
- # move with the reduced length which is < 256
-5: mvcs 0(%r5,%r4),0(%r2),%r0
- slr %r3,%r5
-6: lr %r2,%r3
- br %r14
- .section __ex_table,"a"
- .long 0b,4b
- .long 2b,4b
- .long 5b,6b
- .previous
-
- .align 4
- .text
- .globl __copy_in_user_asm
- # %r2 = from, %r3 = n, %r4 = to
-__copy_in_user_asm:
- ahi %r3,-1
- jo 6f
- sacf 256
- bras %r1,4f
-0: ahi %r3,257
-1: mvc 0(1,%r4),0(%r2)
- la %r2,1(%r2)
- la %r4,1(%r4)
- ahi %r3,-1
- jnz 1b
-2: lr %r2,%r3
- br %r14
-3: mvc 0(256,%r4),0(%r2)
- la %r2,256(%r2)
- la %r4,256(%r4)
-4: ahi %r3,-256
- jnm 3b
-5: ex %r3,4(%r1)
- sacf 0
-6: slr %r2,%r2
- br %r14
- .section __ex_table,"a"
- .long 1b,2b
- .long 3b,0b
- .long 5b,0b
- .previous
-
- .align 4
- .text
- .globl __clear_user_asm
- # %r2 = to, %r3 = n
-__clear_user_asm:
- bras %r5,0f
- .long empty_zero_page
-0: l %r5,0(%r5)
- slr %r0,%r0
-1: mvcs 0(%r3,%r2),0(%r5),%r0
- jnz 2f
- slr %r2,%r2
- br %r14
-2: la %r2,256(%r2)
- ahi %r3,-256
-3: mvcs 0(%r3,%r2),0(%r5),%r0
- jnz 2b
-4: slr %r2,%r2
- br %r14
-5: lhi %r0,-4096
- lr %r4,%r2
- slr %r4,%r0
- nr %r4,%r0 # %r4 = (%r2 + 4096) & -4096
- slr %r4,%r2 # %r4 = #bytes to next user page boundary
- clr %r3,%r4 # clear crosses next page boundary ?
- jnh 7f # no, the current page faulted
- # clear with the reduced length which is < 256
-6: mvcs 0(%r4,%r2),0(%r5),%r0
- slr %r3,%r4
-7: lr %r2,%r3
- br %r14
- .section __ex_table,"a"
- .long 1b,5b
- .long 3b,5b
- .long 6b,7b
- .previous
-
- .align 4
- .text
- .globl __strncpy_from_user_asm
- # %r2 = count, %r3 = dst, %r4 = src
-__strncpy_from_user_asm:
- lhi %r0,0
- lr %r1,%r4
- la %r4,0(%r4) # clear high order bit from %r4
- la %r2,0(%r2,%r4) # %r2 points to first byte after string
- sacf 256
-0: srst %r2,%r1
- jo 0b
- sacf 0
- lr %r1,%r2
- jh 1f # \0 found in string ?
- ahi %r1,1 # include \0 in copy
-1: slr %r1,%r4 # %r1 = copy length (without \0)
- slr %r2,%r4 # %r2 = return length (including \0)
-2: mvcp 0(%r1,%r3),0(%r4),%r0
- jnz 3f
- br %r14
-3: la %r3,256(%r3)
- la %r4,256(%r4)
- ahi %r1,-256
- mvcp 0(%r1,%r3),0(%r4),%r0
- jnz 3b
- br %r14
-4: sacf 0
- lhi %r2,-EFAULT
- br %r14
- .section __ex_table,"a"
- .long 0b,4b
- .previous
-
- .align 4
- .text
- .globl __strnlen_user_asm
- # %r2 = count, %r3 = src
-__strnlen_user_asm:
- lhi %r0,0
- lr %r1,%r3
- la %r3,0(%r3) # clear high order bit from %r4
- la %r2,0(%r2,%r3) # %r2 points to first byte after string
- sacf 256
-0: srst %r2,%r1
- jo 0b
- sacf 0
- ahi %r2,1 # strnlen_user result includes the \0
- # or return count+1 if \0 not found
- slr %r2,%r3
- br %r14
-2: sacf 0
- slr %r2,%r2 # return 0 on exception
- br %r14
- .section __ex_table,"a"
- .long 0b,2b
- .previous
+++ /dev/null
-/*
- * arch/s390x/lib/uaccess.S
- * __copy_{from|to}_user functions.
- *
- * s390
- * Copyright (C) 2000,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
- * Authors(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
- *
- * These functions have standard call interface
- */
-
-#include <linux/errno.h>
-#include <asm/lowcore.h>
-#include <asm/asm-offsets.h>
-
- .text
- .align 4
- .globl __copy_from_user_asm
- # %r2 = to, %r3 = n, %r4 = from
-__copy_from_user_asm:
- slgr %r0,%r0
-0: mvcp 0(%r3,%r2),0(%r4),%r0
- jnz 1f
- slgr %r2,%r2
- br %r14
-1: la %r2,256(%r2)
- la %r4,256(%r4)
- aghi %r3,-256
-2: mvcp 0(%r3,%r2),0(%r4),%r0
- jnz 1b
-3: slgr %r2,%r2
- br %r14
-4: lghi %r0,-4096
- lgr %r5,%r4
- slgr %r5,%r0
- ngr %r5,%r0 # %r5 = (%r4 + 4096) & -4096
- slgr %r5,%r4 # %r5 = #bytes to next user page boundary
- clgr %r3,%r5 # copy crosses next page boundary ?
- jnh 6f # no, the current page faulted
- # move with the reduced length which is < 256
-5: mvcp 0(%r5,%r2),0(%r4),%r0
- slgr %r3,%r5
-6: lgr %r2,%r3
- br %r14
- .section __ex_table,"a"
- .quad 0b,4b
- .quad 2b,4b
- .quad 5b,6b
- .previous
-
- .align 4
- .text
- .globl __copy_to_user_asm
- # %r2 = from, %r3 = n, %r4 = to
-__copy_to_user_asm:
- slgr %r0,%r0
-0: mvcs 0(%r3,%r4),0(%r2),%r0
- jnz 1f
- slgr %r2,%r2
- br %r14
-1: la %r2,256(%r2)
- la %r4,256(%r4)
- aghi %r3,-256
-2: mvcs 0(%r3,%r4),0(%r2),%r0
- jnz 1b
-3: slgr %r2,%r2
- br %r14
-4: lghi %r0,-4096
- lgr %r5,%r4
- slgr %r5,%r0
- ngr %r5,%r0 # %r5 = (%r4 + 4096) & -4096
- slgr %r5,%r4 # %r5 = #bytes to next user page boundary
- clgr %r3,%r5 # copy crosses next page boundary ?
- jnh 6f # no, the current page faulted
- # move with the reduced length which is < 256
-5: mvcs 0(%r5,%r4),0(%r2),%r0
- slgr %r3,%r5
-6: lgr %r2,%r3
- br %r14
- .section __ex_table,"a"
- .quad 0b,4b
- .quad 2b,4b
- .quad 5b,6b
- .previous
-
- .align 4
- .text
- .globl __copy_in_user_asm
- # %r2 = from, %r3 = n, %r4 = to
-__copy_in_user_asm:
- aghi %r3,-1
- jo 6f
- sacf 256
- bras %r1,4f
-0: aghi %r3,257
-1: mvc 0(1,%r4),0(%r2)
- la %r2,1(%r2)
- la %r4,1(%r4)
- aghi %r3,-1
- jnz 1b
-2: lgr %r2,%r3
- br %r14
-3: mvc 0(256,%r4),0(%r2)
- la %r2,256(%r2)
- la %r4,256(%r4)
-4: aghi %r3,-256
- jnm 3b
-5: ex %r3,4(%r1)
- sacf 0
-6: slgr %r2,%r2
- br 14
- .section __ex_table,"a"
- .quad 1b,2b
- .quad 3b,0b
- .quad 5b,0b
- .previous
-
- .align 4
- .text
- .globl __clear_user_asm
- # %r2 = to, %r3 = n
-__clear_user_asm:
- slgr %r0,%r0
- larl %r5,empty_zero_page
-1: mvcs 0(%r3,%r2),0(%r5),%r0
- jnz 2f
- slgr %r2,%r2
- br %r14
-2: la %r2,256(%r2)
- aghi %r3,-256
-3: mvcs 0(%r3,%r2),0(%r5),%r0
- jnz 2b
-4: slgr %r2,%r2
- br %r14
-5: lghi %r0,-4096
- lgr %r4,%r2
- slgr %r4,%r0
- ngr %r4,%r0 # %r4 = (%r2 + 4096) & -4096
- slgr %r4,%r2 # %r4 = #bytes to next user page boundary
- clgr %r3,%r4 # clear crosses next page boundary ?
- jnh 7f # no, the current page faulted
- # clear with the reduced length which is < 256
-6: mvcs 0(%r4,%r2),0(%r5),%r0
- slgr %r3,%r4
-7: lgr %r2,%r3
- br %r14
- .section __ex_table,"a"
- .quad 1b,5b
- .quad 3b,5b
- .quad 6b,7b
- .previous
-
- .align 4
- .text
- .globl __strncpy_from_user_asm
- # %r2 = count, %r3 = dst, %r4 = src
-__strncpy_from_user_asm:
- lghi %r0,0
- lgr %r1,%r4
- la %r2,0(%r2,%r4) # %r2 points to first byte after string
- sacf 256
-0: srst %r2,%r1
- jo 0b
- sacf 0
- lgr %r1,%r2
- jh 1f # \0 found in string ?
- aghi %r1,1 # include \0 in copy
-1: slgr %r1,%r4 # %r1 = copy length (without \0)
- slgr %r2,%r4 # %r2 = return length (including \0)
-2: mvcp 0(%r1,%r3),0(%r4),%r0
- jnz 3f
- br %r14
-3: la %r3,256(%r3)
- la %r4,256(%r4)
- aghi %r1,-256
- mvcp 0(%r1,%r3),0(%r4),%r0
- jnz 3b
- br %r14
-4: sacf 0
- lghi %r2,-EFAULT
- br %r14
- .section __ex_table,"a"
- .quad 0b,4b
- .previous
-
- .align 4
- .text
- .globl __strnlen_user_asm
- # %r2 = count, %r3 = src
-__strnlen_user_asm:
- lghi %r0,0
- lgr %r1,%r3
- la %r2,0(%r2,%r3) # %r2 points to first byte after string
- sacf 256
-0: srst %r2,%r1
- jo 0b
- sacf 0
- aghi %r2,1 # strnlen_user result includes the \0
- # or return count+1 if \0 not found
- slgr %r2,%r3
- br %r14
-2: sacf 0
- slgr %r2,%r2 # return 0 on exception
- br %r14
- .section __ex_table,"a"
- .quad 0b,2b
- .previous
--- /dev/null
+/*
+ * arch/s390/lib/uaccess_mvcos.c
+ *
+ * Optimized user space space access functions based on mvcos.
+ *
+ * Copyright (C) IBM Corp. 2006
+ * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
+ * Gerald Schaefer (gerald.schaefer@de.ibm.com)
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <asm/uaccess.h>
+#include <asm/futex.h>
+
+#ifndef __s390x__
+#define AHI "ahi"
+#define ALR "alr"
+#define CLR "clr"
+#define LHI "lhi"
+#define SLR "slr"
+#else
+#define AHI "aghi"
+#define ALR "algr"
+#define CLR "clgr"
+#define LHI "lghi"
+#define SLR "slgr"
+#endif
+
+size_t copy_from_user_mvcos(size_t size, const void __user *ptr, void *x)
+{
+ register unsigned long reg0 asm("0") = 0x81UL;
+ unsigned long tmp1, tmp2;
+
+ tmp1 = -4096UL;
+ asm volatile(
+ "0: .insn ss,0xc80000000000,0(%0,%2),0(%1),0\n"
+ " jz 4f\n"
+ "1:"ALR" %0,%3\n"
+ " "SLR" %1,%3\n"
+ " "SLR" %2,%3\n"
+ " j 0b\n"
+ "2: la %4,4095(%1)\n"/* %4 = ptr + 4095 */
+ " nr %4,%3\n" /* %4 = (ptr + 4095) & -4096 */
+ " "SLR" %4,%1\n"
+ " "CLR" %0,%4\n" /* copy crosses next page boundary? */
+ " jnh 5f\n"
+ "3: .insn ss,0xc80000000000,0(%4,%2),0(%1),0\n"
+ " "SLR" %0,%4\n"
+ " j 5f\n"
+ "4:"SLR" %0,%0\n"
+ "5: \n"
+ EX_TABLE(0b,2b) EX_TABLE(3b,5b)
+ : "+a" (size), "+a" (ptr), "+a" (x), "+a" (tmp1), "=a" (tmp2)
+ : "d" (reg0) : "cc", "memory");
+ return size;
+}
+
+size_t copy_to_user_mvcos(size_t size, void __user *ptr, const void *x)
+{
+ register unsigned long reg0 asm("0") = 0x810000UL;
+ unsigned long tmp1, tmp2;
+
+ tmp1 = -4096UL;
+ asm volatile(
+ "0: .insn ss,0xc80000000000,0(%0,%1),0(%2),0\n"
+ " jz 4f\n"
+ "1:"ALR" %0,%3\n"
+ " "SLR" %1,%3\n"
+ " "SLR" %2,%3\n"
+ " j 0b\n"
+ "2: la %4,4095(%1)\n"/* %4 = ptr + 4095 */
+ " nr %4,%3\n" /* %4 = (ptr + 4095) & -4096 */
+ " "SLR" %4,%1\n"
+ " "CLR" %0,%4\n" /* copy crosses next page boundary? */
+ " jnh 5f\n"
+ "3: .insn ss,0xc80000000000,0(%4,%1),0(%2),0\n"
+ " "SLR" %0,%4\n"
+ " j 5f\n"
+ "4:"SLR" %0,%0\n"
+ "5: \n"
+ EX_TABLE(0b,2b) EX_TABLE(3b,5b)
+ : "+a" (size), "+a" (ptr), "+a" (x), "+a" (tmp1), "=a" (tmp2)
+ : "d" (reg0) : "cc", "memory");
+ return size;
+}
+
+size_t copy_in_user_mvcos(size_t size, void __user *to, const void __user *from)
+{
+ register unsigned long reg0 asm("0") = 0x810081UL;
+ unsigned long tmp1, tmp2;
+
+ tmp1 = -4096UL;
+ /* FIXME: copy with reduced length. */
+ asm volatile(
+ "0: .insn ss,0xc80000000000,0(%0,%1),0(%2),0\n"
+ " jz 2f\n"
+ "1:"ALR" %0,%3\n"
+ " "SLR" %1,%3\n"
+ " "SLR" %2,%3\n"
+ " j 0b\n"
+ "2:"SLR" %0,%0\n"
+ "3: \n"
+ EX_TABLE(0b,3b)
+ : "+a" (size), "+a" (to), "+a" (from), "+a" (tmp1), "=a" (tmp2)
+ : "d" (reg0) : "cc", "memory");
+ return size;
+}
+
+size_t clear_user_mvcos(size_t size, void __user *to)
+{
+ register unsigned long reg0 asm("0") = 0x810000UL;
+ unsigned long tmp1, tmp2;
+
+ tmp1 = -4096UL;
+ asm volatile(
+ "0: .insn ss,0xc80000000000,0(%0,%1),0(%4),0\n"
+ " jz 4f\n"
+ "1:"ALR" %0,%2\n"
+ " "SLR" %1,%2\n"
+ " j 0b\n"
+ "2: la %3,4095(%1)\n"/* %4 = to + 4095 */
+ " nr %3,%2\n" /* %4 = (to + 4095) & -4096 */
+ " "SLR" %3,%1\n"
+ " "CLR" %0,%3\n" /* copy crosses next page boundary? */
+ " jnh 5f\n"
+ "3: .insn ss,0xc80000000000,0(%3,%1),0(%4),0\n"
+ " "SLR" %0,%3\n"
+ " j 5f\n"
+ "4:"SLR" %0,%0\n"
+ "5: \n"
+ EX_TABLE(0b,2b) EX_TABLE(3b,5b)
+ : "+a" (size), "+a" (to), "+a" (tmp1), "=a" (tmp2)
+ : "a" (empty_zero_page), "d" (reg0) : "cc", "memory");
+ return size;
+}
+
+extern size_t copy_from_user_std_small(size_t, const void __user *, void *);
+extern size_t copy_to_user_std_small(size_t, void __user *, const void *);
+extern size_t strnlen_user_std(size_t, const char __user *);
+extern size_t strncpy_from_user_std(size_t, const char __user *, char *);
+extern int futex_atomic_op(int, int __user *, int, int *);
+extern int futex_atomic_cmpxchg(int __user *, int, int);
+
+struct uaccess_ops uaccess_mvcos = {
+ .copy_from_user = copy_from_user_mvcos,
+ .copy_from_user_small = copy_from_user_std_small,
+ .copy_to_user = copy_to_user_mvcos,
+ .copy_to_user_small = copy_to_user_std_small,
+ .copy_in_user = copy_in_user_mvcos,
+ .clear_user = clear_user_mvcos,
+ .strnlen_user = strnlen_user_std,
+ .strncpy_from_user = strncpy_from_user_std,
+ .futex_atomic_op = futex_atomic_op,
+ .futex_atomic_cmpxchg = futex_atomic_cmpxchg,
+};
--- /dev/null
+/*
+ * arch/s390/lib/uaccess_std.c
+ *
+ * Standard user space access functions based on mvcp/mvcs and doing
+ * interesting things in the secondary space mode.
+ *
+ * Copyright (C) IBM Corp. 2006
+ * Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
+ * Gerald Schaefer (gerald.schaefer@de.ibm.com)
+ */
+
+#include <linux/errno.h>
+#include <linux/mm.h>
+#include <asm/uaccess.h>
+#include <asm/futex.h>
+
+#ifndef __s390x__
+#define AHI "ahi"
+#define ALR "alr"
+#define CLR "clr"
+#define LHI "lhi"
+#define SLR "slr"
+#else
+#define AHI "aghi"
+#define ALR "algr"
+#define CLR "clgr"
+#define LHI "lghi"
+#define SLR "slgr"
+#endif
+
+size_t copy_from_user_std(size_t size, const void __user *ptr, void *x)
+{
+ unsigned long tmp1, tmp2;
+
+ tmp1 = -256UL;
+ asm volatile(
+ "0: mvcp 0(%0,%2),0(%1),%3\n"
+ " jz 5f\n"
+ "1:"ALR" %0,%3\n"
+ " la %1,256(%1)\n"
+ " la %2,256(%2)\n"
+ "2: mvcp 0(%0,%2),0(%1),%3\n"
+ " jnz 1b\n"
+ " j 5f\n"
+ "3: la %4,255(%1)\n" /* %4 = ptr + 255 */
+ " "LHI" %3,-4096\n"
+ " nr %4,%3\n" /* %4 = (ptr + 255) & -4096 */
+ " "SLR" %4,%1\n"
+ " "CLR" %0,%4\n" /* copy crosses next page boundary? */
+ " jnh 6f\n"
+ "4: mvcp 0(%4,%2),0(%1),%3\n"
+ " "SLR" %0,%4\n"
+ " j 6f\n"
+ "5:"SLR" %0,%0\n"
+ "6: \n"
+ EX_TABLE(0b,3b) EX_TABLE(2b,3b) EX_TABLE(4b,6b)
+ : "+a" (size), "+a" (ptr), "+a" (x), "+a" (tmp1), "=a" (tmp2)
+ : : "cc", "memory");
+ return size;
+}
+
+size_t copy_from_user_std_small(size_t size, const void __user *ptr, void *x)
+{
+ unsigned long tmp1, tmp2;
+
+ tmp1 = 0UL;
+ asm volatile(
+ "0: mvcp 0(%0,%2),0(%1),%3\n"
+ " "SLR" %0,%0\n"
+ " j 3f\n"
+ "1: la %4,255(%1)\n" /* %4 = ptr + 255 */
+ " "LHI" %3,-4096\n"
+ " nr %4,%3\n" /* %4 = (ptr + 255) & -4096 */
+ " "SLR" %4,%1\n"
+ " "CLR" %0,%4\n" /* copy crosses next page boundary? */
+ " jnh 3f\n"
+ "2: mvcp 0(%4,%2),0(%1),%3\n"
+ " "SLR" %0,%4\n"
+ "3:\n"
+ EX_TABLE(0b,1b) EX_TABLE(2b,3b)
+ : "+a" (size), "+a" (ptr), "+a" (x), "+a" (tmp1), "=a" (tmp2)
+ : : "cc", "memory");
+ return size;
+}
+
+size_t copy_to_user_std(size_t size, void __user *ptr, const void *x)
+{
+ unsigned long tmp1, tmp2;
+
+ tmp1 = -256UL;
+ asm volatile(
+ "0: mvcs 0(%0,%1),0(%2),%3\n"
+ " jz 5f\n"
+ "1:"ALR" %0,%3\n"
+ " la %1,256(%1)\n"
+ " la %2,256(%2)\n"
+ "2: mvcs 0(%0,%1),0(%2),%3\n"
+ " jnz 1b\n"
+ " j 5f\n"
+ "3: la %4,255(%1)\n" /* %4 = ptr + 255 */
+ " "LHI" %3,-4096\n"
+ " nr %4,%3\n" /* %4 = (ptr + 255) & -4096 */
+ " "SLR" %4,%1\n"
+ " "CLR" %0,%4\n" /* copy crosses next page boundary? */
+ " jnh 6f\n"
+ "4: mvcs 0(%4,%1),0(%2),%3\n"
+ " "SLR" %0,%4\n"
+ " j 6f\n"
+ "5:"SLR" %0,%0\n"
+ "6: \n"
+ EX_TABLE(0b,3b) EX_TABLE(2b,3b) EX_TABLE(4b,6b)
+ : "+a" (size), "+a" (ptr), "+a" (x), "+a" (tmp1), "=a" (tmp2)
+ : : "cc", "memory");
+ return size;
+}
+
+size_t copy_to_user_std_small(size_t size, void __user *ptr, const void *x)
+{
+ unsigned long tmp1, tmp2;
+
+ tmp1 = 0UL;
+ asm volatile(
+ "0: mvcs 0(%0,%1),0(%2),%3\n"
+ " "SLR" %0,%0\n"
+ " j 3f\n"
+ "1: la %4,255(%1)\n" /* ptr + 255 */
+ " "LHI" %3,-4096\n"
+ " nr %4,%3\n" /* (ptr + 255) & -4096UL */
+ " "SLR" %4,%1\n"
+ " "CLR" %0,%4\n" /* copy crosses next page boundary? */
+ " jnh 3f\n"
+ "2: mvcs 0(%4,%1),0(%2),%3\n"
+ " "SLR" %0,%4\n"
+ "3:\n"
+ EX_TABLE(0b,1b) EX_TABLE(2b,3b)
+ : "+a" (size), "+a" (ptr), "+a" (x), "+a" (tmp1), "=a" (tmp2)
+ : : "cc", "memory");
+ return size;
+}
+
+size_t copy_in_user_std(size_t size, void __user *to, const void __user *from)
+{
+ unsigned long tmp1;
+
+ asm volatile(
+ " "AHI" %0,-1\n"
+ " jo 5f\n"
+ " sacf 256\n"
+ " bras %3,3f\n"
+ "0:"AHI" %0,257\n"
+ "1: mvc 0(1,%1),0(%2)\n"
+ " la %1,1(%1)\n"
+ " la %2,1(%2)\n"
+ " "AHI" %0,-1\n"
+ " jnz 1b\n"
+ " j 5f\n"
+ "2: mvc 0(256,%1),0(%2)\n"
+ " la %1,256(%1)\n"
+ " la %2,256(%2)\n"
+ "3:"AHI" %0,-256\n"
+ " jnm 2b\n"
+ "4: ex %0,1b-0b(%3)\n"
+ " sacf 0\n"
+ "5: "SLR" %0,%0\n"
+ "6:\n"
+ EX_TABLE(1b,6b) EX_TABLE(2b,0b) EX_TABLE(4b,0b)
+ : "+a" (size), "+a" (to), "+a" (from), "=a" (tmp1)
+ : : "cc", "memory");
+ return size;
+}
+
+size_t clear_user_std(size_t size, void __user *to)
+{
+ unsigned long tmp1, tmp2;
+
+ asm volatile(
+ " "AHI" %0,-1\n"
+ " jo 5f\n"
+ " sacf 256\n"
+ " bras %3,3f\n"
+ " xc 0(1,%1),0(%1)\n"
+ "0:"AHI" %0,257\n"
+ " la %2,255(%1)\n" /* %2 = ptr + 255 */
+ " srl %2,12\n"
+ " sll %2,12\n" /* %2 = (ptr + 255) & -4096 */
+ " "SLR" %2,%1\n"
+ " "CLR" %0,%2\n" /* clear crosses next page boundary? */
+ " jnh 5f\n"
+ " "AHI" %2,-1\n"
+ "1: ex %2,0(%3)\n"
+ " "AHI" %2,1\n"
+ " "SLR" %0,%2\n"
+ " j 5f\n"
+ "2: xc 0(256,%1),0(%1)\n"
+ " la %1,256(%1)\n"
+ "3:"AHI" %0,-256\n"
+ " jnm 2b\n"
+ "4: ex %0,0(%3)\n"
+ " sacf 0\n"
+ "5: "SLR" %0,%0\n"
+ "6:\n"
+ EX_TABLE(1b,6b) EX_TABLE(2b,0b) EX_TABLE(4b,0b)
+ : "+a" (size), "+a" (to), "=a" (tmp1), "=a" (tmp2)
+ : : "cc", "memory");
+ return size;
+}
+
+size_t strnlen_user_std(size_t size, const char __user *src)
+{
+ register unsigned long reg0 asm("0") = 0UL;
+ unsigned long tmp1, tmp2;
+
+ asm volatile(
+ " la %2,0(%1)\n"
+ " la %3,0(%0,%1)\n"
+ " "SLR" %0,%0\n"
+ " sacf 256\n"
+ "0: srst %3,%2\n"
+ " jo 0b\n"
+ " la %0,1(%3)\n" /* strnlen_user results includes \0 */
+ " "SLR" %0,%1\n"
+ "1: sacf 0\n"
+ EX_TABLE(0b,1b)
+ : "+a" (size), "+a" (src), "=a" (tmp1), "=a" (tmp2)
+ : "d" (reg0) : "cc", "memory");
+ return size;
+}
+
+size_t strncpy_from_user_std(size_t size, const char __user *src, char *dst)
+{
+ register unsigned long reg0 asm("0") = 0UL;
+ unsigned long tmp1, tmp2;
+
+ asm volatile(
+ " la %3,0(%1)\n"
+ " la %4,0(%0,%1)\n"
+ " sacf 256\n"
+ "0: srst %4,%3\n"
+ " jo 0b\n"
+ " sacf 0\n"
+ " la %0,0(%4)\n"
+ " jh 1f\n" /* found \0 in string ? */
+ " "AHI" %4,1\n" /* include \0 in copy */
+ "1:"SLR" %0,%1\n" /* %0 = return length (without \0) */
+ " "SLR" %4,%1\n" /* %4 = copy length (including \0) */
+ "2: mvcp 0(%4,%2),0(%1),%5\n"
+ " jz 9f\n"
+ "3:"AHI" %4,-256\n"
+ " la %1,256(%1)\n"
+ " la %2,256(%2)\n"
+ "4: mvcp 0(%4,%2),0(%1),%5\n"
+ " jnz 3b\n"
+ " j 9f\n"
+ "7: sacf 0\n"
+ "8:"LHI" %0,%6\n"
+ "9:\n"
+ EX_TABLE(0b,7b) EX_TABLE(2b,8b) EX_TABLE(4b,8b)
+ : "+a" (size), "+a" (src), "+d" (dst), "=a" (tmp1), "=a" (tmp2)
+ : "d" (reg0), "K" (-EFAULT) : "cc", "memory");
+ return size;
+}
+
+#define __futex_atomic_op(insn, ret, oldval, newval, uaddr, oparg) \
+ asm volatile( \
+ " sacf 256\n" \
+ "0: l %1,0(%6)\n" \
+ "1:"insn \
+ "2: cs %1,%2,0(%6)\n" \
+ "3: jl 1b\n" \
+ " lhi %0,0\n" \
+ "4: sacf 0\n" \
+ EX_TABLE(0b,4b) EX_TABLE(2b,4b) EX_TABLE(3b,4b) \
+ : "=d" (ret), "=&d" (oldval), "=&d" (newval), \
+ "=m" (*uaddr) \
+ : "0" (-EFAULT), "d" (oparg), "a" (uaddr), \
+ "m" (*uaddr) : "cc");
+
+int futex_atomic_op(int op, int __user *uaddr, int oparg, int *old)
+{
+ int oldval = 0, newval, ret;
+
+ inc_preempt_count();
+
+ switch (op) {
+ case FUTEX_OP_SET:
+ __futex_atomic_op("lr %2,%5\n",
+ ret, oldval, newval, uaddr, oparg);
+ break;
+ case FUTEX_OP_ADD:
+ __futex_atomic_op("lr %2,%1\nar %2,%5\n",
+ ret, oldval, newval, uaddr, oparg);
+ break;
+ case FUTEX_OP_OR:
+ __futex_atomic_op("lr %2,%1\nor %2,%5\n",
+ ret, oldval, newval, uaddr, oparg);
+ break;
+ case FUTEX_OP_ANDN:
+ __futex_atomic_op("lr %2,%1\nnr %2,%5\n",
+ ret, oldval, newval, uaddr, oparg);
+ break;
+ case FUTEX_OP_XOR:
+ __futex_atomic_op("lr %2,%1\nxr %2,%5\n",
+ ret, oldval, newval, uaddr, oparg);
+ break;
+ default:
+ ret = -ENOSYS;
+ }
+ dec_preempt_count();
+ *old = oldval;
+ return ret;
+}
+
+int futex_atomic_cmpxchg(int __user *uaddr, int oldval, int newval)
+{
+ int ret;
+
+ asm volatile(
+ " sacf 256\n"
+ " cs %1,%4,0(%5)\n"
+ "0: lr %0,%1\n"
+ "1: sacf 0\n"
+ EX_TABLE(0b,1b)
+ : "=d" (ret), "+d" (oldval), "=m" (*uaddr)
+ : "0" (-EFAULT), "d" (newval), "a" (uaddr), "m" (*uaddr)
+ : "cc", "memory" );
+ return ret;
+}
+
+struct uaccess_ops uaccess_std = {
+ .copy_from_user = copy_from_user_std,
+ .copy_from_user_small = copy_from_user_std_small,
+ .copy_to_user = copy_to_user_std,
+ .copy_to_user_small = copy_to_user_std_small,
+ .copy_in_user = copy_in_user_std,
+ .clear_user = clear_user_std,
+ .strnlen_user = strnlen_user_std,
+ .strncpy_from_user = strncpy_from_user_std,
+ .futex_atomic_op = futex_atomic_op,
+ .futex_atomic_cmpxchg = futex_atomic_cmpxchg,
+};
static void cmm_timer_fn(unsigned long);
static void cmm_set_timer(void);
-static long
-cmm_strtoul(const char *cp, char **endp)
-{
- unsigned int base = 10;
-
- if (*cp == '0') {
- base = 8;
- cp++;
- if ((*cp == 'x' || *cp == 'X') && isxdigit(cp[1])) {
- base = 16;
- cp++;
- }
- }
- return simple_strtoul(cp, endp, base);
-}
-
static long
cmm_alloc_pages(long pages, long *counter, struct cmm_page_array **list)
{
return -EFAULT;
buf[sizeof(buf) - 1] = '\0';
cmm_skip_blanks(buf, &p);
- pages = cmm_strtoul(p, &p);
+ pages = simple_strtoul(p, &p, 0);
if (ctl == &cmm_table[0])
cmm_set_pages(pages);
else
return -EFAULT;
buf[sizeof(buf) - 1] = '\0';
cmm_skip_blanks(buf, &p);
- pages = cmm_strtoul(p, &p);
+ pages = simple_strtoul(p, &p, 0);
cmm_skip_blanks(p, &p);
- seconds = cmm_strtoul(p, &p);
+ seconds = simple_strtoul(p, &p, 0);
cmm_set_timeout(pages, seconds);
} else {
len = sprintf(buf, "%ld %ld\n",
if (strncmp(msg, "SHRINK", 6) == 0) {
if (!cmm_skip_blanks(msg + 6, &msg))
return;
- pages = cmm_strtoul(msg, &msg);
+ pages = simple_strtoul(msg, &msg, 0);
cmm_skip_blanks(msg, &msg);
if (*msg == '\0')
cmm_set_pages(pages);
} else if (strncmp(msg, "RELEASE", 7) == 0) {
if (!cmm_skip_blanks(msg + 7, &msg))
return;
- pages = cmm_strtoul(msg, &msg);
+ pages = simple_strtoul(msg, &msg, 0);
cmm_skip_blanks(msg, &msg);
if (*msg == '\0')
cmm_add_timed_pages(pages);
} else if (strncmp(msg, "REUSE", 5) == 0) {
if (!cmm_skip_blanks(msg + 5, &msg))
return;
- pages = cmm_strtoul(msg, &msg);
+ pages = simple_strtoul(msg, &msg, 0);
if (!cmm_skip_blanks(msg, &msg))
return;
- seconds = cmm_strtoul(msg, &msg);
+ seconds = simple_strtoul(msg, &msg, 0);
cmm_skip_blanks(msg, &msg);
if (*msg == '\0')
cmm_set_timeout(pages, seconds);
#include <linux/console.h>
#include <linux/module.h>
#include <linux/hardirq.h>
+#include <linux/kprobes.h>
#include <asm/system.h>
#include <asm/uaccess.h>
#include <asm/pgtable.h>
+#include <asm/kdebug.h>
#ifndef CONFIG_64BIT
#define __FAIL_ADDR_MASK 0x7ffff000
extern void die(const char *,struct pt_regs *,long);
+#ifdef CONFIG_KPROBES
+ATOMIC_NOTIFIER_HEAD(notify_page_fault_chain);
+int register_page_fault_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_register(¬ify_page_fault_chain, nb);
+}
+
+int unregister_page_fault_notifier(struct notifier_block *nb)
+{
+ return atomic_notifier_chain_unregister(¬ify_page_fault_chain, nb);
+}
+
+static inline int notify_page_fault(enum die_val val, const char *str,
+ struct pt_regs *regs, long err, int trap, int sig)
+{
+ struct die_args args = {
+ .regs = regs,
+ .str = str,
+ .err = err,
+ .trapnr = trap,
+ .signr = sig
+ };
+ return atomic_notifier_call_chain(¬ify_page_fault_chain, val, &args);
+}
+#else
+static inline int notify_page_fault(enum die_val val, const char *str,
+ struct pt_regs *regs, long err, int trap, int sig)
+{
+ return NOTIFY_DONE;
+}
+#endif
+
extern spinlock_t timerlist_lock;
/*
* 11 Page translation -> Not present (nullification)
* 3b Region third trans. -> Not present (nullification)
*/
-static inline void
+static inline void __kprobes
do_exception(struct pt_regs *regs, unsigned long error_code, int is_protection)
{
struct task_struct *tsk;
tsk = current;
mm = tsk->mm;
+ if (notify_page_fault(DIE_PAGE_FAULT, "page fault", regs, error_code, 14,
+ SIGSEGV) == NOTIFY_STOP)
+ return;
+
/*
* Check for low-address protection. This needs to be treated
* as a special case because the translation exception code
unsigned long pgdir_k = (__pa(swapper_pg_dir) & PAGE_MASK) | _KERNSEG_TABLE;
static const int ssm_mask = 0x04000000L;
unsigned long ro_start_pfn, ro_end_pfn;
+ unsigned long zones_size[MAX_NR_ZONES];
ro_start_pfn = PFN_DOWN((unsigned long)&__start_rodata);
ro_end_pfn = PFN_UP((unsigned long)&__end_rodata);
+ memset(zones_size, 0, sizeof(zones_size));
+ zones_size[ZONE_DMA] = max_low_pfn;
+ free_area_init_node(0, &contig_page_data, zones_size,
+ __pa(PAGE_OFFSET) >> PAGE_SHIFT,
+ zholes_size);
+
/* unmap whole virtual address space */
pg_dir = swapper_pg_dir;
- for (i=0;i<KERNEL_PGD_PTRS;i++)
- pmd_clear((pmd_t*)pg_dir++);
+ for (i = 0; i < PTRS_PER_PGD; i++)
+ pmd_clear((pmd_t *) pg_dir++);
/*
* map whole physical memory to virtual memory (identity mapping)
*/
pg_table = (pte_t *) alloc_bootmem_pages(PAGE_SIZE);
- pg_dir->pgd0 = (_PAGE_TABLE | __pa(pg_table));
- pg_dir->pgd1 = (_PAGE_TABLE | (__pa(pg_table)+1024));
- pg_dir->pgd2 = (_PAGE_TABLE | (__pa(pg_table)+2048));
- pg_dir->pgd3 = (_PAGE_TABLE | (__pa(pg_table)+3072));
+ pmd_populate_kernel(&init_mm, (pmd_t *) pg_dir, pg_table);
pg_dir++;
for (tmp = 0 ; tmp < PTRS_PER_PTE ; tmp++,pg_table++) {
else
pte = pfn_pte(pfn, PAGE_KERNEL);
if (pfn >= max_low_pfn)
- pte_clear(&init_mm, 0, &pte);
- set_pte(pg_table, pte);
+ pte_val(pte) = _PAGE_TYPE_EMPTY;
+ set_pte(pg_table, pte);
pfn++;
}
}
: : "m" (pgdir_k), "m" (ssm_mask));
local_flush_tlb();
-
- {
- unsigned long zones_size[MAX_NR_ZONES];
-
- memset(zones_size, 0, sizeof(zones_size));
- zones_size[ZONE_DMA] = max_low_pfn;
- free_area_init_node(0, &contig_page_data, zones_size,
- __pa(PAGE_OFFSET) >> PAGE_SHIFT,
- zholes_size);
- }
return;
}
pte = pfn_pte(pfn, __pgprot(_PAGE_RO));
else
pte = pfn_pte(pfn, PAGE_KERNEL);
- if (pfn >= max_low_pfn) {
- pte_clear(&init_mm, 0, &pte);
- continue;
- }
+ if (pfn >= max_low_pfn)
+ pte_val(pte) = _PAGE_TYPE_EMPTY;
set_pte(pt_dir, pte);
pfn++;
}
/*
* hypervisor.c - /sys/hypervisor subsystem.
*
- * This file is released under the GPLv2
+ * Copyright (C) IBM Corp. 2006
*
+ * This file is released under the GPLv2
*/
#include <linux/kobject.h>
help
Character device driver for reading z/VM monitor service records
+config MONWRITER
+ tristate "API for writing z/VM monitor service records"
+ default "m"
+ help
+ Character device driver for writing z/VM monitor service records
+
endmenu
menu "Cryptographic devices"
-config Z90CRYPT
+config ZCRYPT
tristate "Support for PCI-attached cryptographic adapters"
- default "m"
- help
+ select ZCRYPT_MONOLITHIC if ZCRYPT="y"
+ default "m"
+ help
Select this option if you want to use a PCI-attached cryptographic
- adapter like the PCI Cryptographic Accelerator (PCICA) or the PCI
- Cryptographic Coprocessor (PCICC). This option is also available
- as a module called z90crypt.ko.
+ adapter like:
+ + PCI Cryptographic Accelerator (PCICA)
+ + PCI Cryptographic Coprocessor (PCICC)
+ + PCI-X Cryptographic Coprocessor (PCIXCC)
+ + Crypto Express2 Coprocessor (CEX2C)
+ + Crypto Express2 Accelerator (CEX2A)
+
+config ZCRYPT_MONOLITHIC
+ bool "Monolithic zcrypt module"
+ depends on ZCRYPT="m"
+ help
+ Select this option if you want to have a single module z90crypt.ko
+ that contains all parts of the crypto device driver (ap bus,
+ request router and all the card drivers).
endmenu
device->debug_area = debug_register(device->cdev->dev.bus_id, 1, 2,
8 * sizeof (long));
debug_register_view(device->debug_area, &debug_sprintf_view);
- debug_set_level(device->debug_area, DBF_EMERG);
+ debug_set_level(device->debug_area, DBF_WARNING);
DBF_DEV_EVENT(DBF_EMERG, device, "%s", "debug area created");
device->state = DASD_STATE_BASIC;
device = (struct dasd_device *) cqr->device;
if (device == NULL ||
- device != dasd_device_from_cdev(cdev) ||
+ device != dasd_device_from_cdev_locked(cdev) ||
strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
MESSAGE(KERN_DEBUG, "invalid device in request: bus_id %s",
cdev->dev.bus_id);
/* first of all check for state change pending interrupt */
mask = DEV_STAT_ATTENTION | DEV_STAT_DEV_END | DEV_STAT_UNIT_EXCEP;
if ((irb->scsw.dstat & mask) == mask) {
- device = dasd_device_from_cdev(cdev);
+ device = dasd_device_from_cdev_locked(cdev);
if (!IS_ERR(device)) {
dasd_handle_state_change_pending(device);
dasd_put_device(device);
goto failed;
}
debug_register_view(dasd_debug_area, &debug_sprintf_view);
- debug_set_level(dasd_debug_area, DBF_EMERG);
+ debug_set_level(dasd_debug_area, DBF_WARNING);
DBF_EVENT(DBF_EMERG, "%s", "debug area created");
return residual_str;
}
if (strncmp("nopav", parsestring, length) == 0) {
- dasd_nopav = 1;
- MESSAGE(KERN_INFO, "%s", "disable PAV mode");
+ if (MACHINE_IS_VM)
+ MESSAGE(KERN_INFO, "%s", "'nopav' not supported on VM");
+ else {
+ dasd_nopav = 1;
+ MESSAGE(KERN_INFO, "%s", "disable PAV mode");
+ }
return residual_str;
}
if (strncmp("fixedbuffers", parsestring, length) == 0) {
{
struct dasd_devmap *devmap;
struct dasd_device *device;
+ unsigned long flags;
int rc;
devmap = dasd_devmap_from_cdev(cdev);
if (IS_ERR(devmap))
return (void *) devmap;
- cdev->dev.driver_data = devmap;
device = dasd_alloc_device();
if (IS_ERR(device))
return device;
- atomic_set(&device->ref_count, 2);
+ atomic_set(&device->ref_count, 3);
spin_lock(&dasd_devmap_lock);
if (!devmap->device) {
dasd_free_device(device);
return ERR_PTR(rc);
}
+
+ spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+ cdev->dev.driver_data = device;
+ spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
+
return device;
}
{
struct ccw_device *cdev;
struct dasd_devmap *devmap;
+ unsigned long flags;
/* First remove device pointer from devmap. */
devmap = dasd_find_busid(device->cdev->dev.bus_id);
devmap->device = NULL;
spin_unlock(&dasd_devmap_lock);
- /* Drop ref_count by 2, one for the devmap reference and
- * one for the passed reference. */
- atomic_sub(2, &device->ref_count);
+ /* Disconnect dasd_device structure from ccw_device structure. */
+ spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
+ device->cdev->dev.driver_data = NULL;
+ spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
+
+ /*
+ * Drop ref_count by 3, one for the devmap reference, one for
+ * the cdev reference and one for the passed reference.
+ */
+ atomic_sub(3, &device->ref_count);
/* Wait for reference counter to drop to zero. */
wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0);
cdev = device->cdev;
device->cdev = NULL;
- /* Disconnect dasd_devmap structure from ccw_device structure. */
- cdev->dev.driver_data = NULL;
-
/* Put ccw_device structure. */
put_device(&cdev->dev);
wake_up(&dasd_delete_wq);
}
+/*
+ * Return dasd_device structure associated with cdev.
+ * This function needs to be called with the ccw device
+ * lock held. It can be used from interrupt context.
+ */
+struct dasd_device *
+dasd_device_from_cdev_locked(struct ccw_device *cdev)
+{
+ struct dasd_device *device = cdev->dev.driver_data;
+
+ if (!device)
+ return ERR_PTR(-ENODEV);
+ dasd_get_device(device);
+ return device;
+}
+
/*
* Return dasd_device structure associated with cdev.
*/
struct dasd_device *
dasd_device_from_cdev(struct ccw_device *cdev)
{
- struct dasd_devmap *devmap;
struct dasd_device *device;
+ unsigned long flags;
- device = ERR_PTR(-ENODEV);
- spin_lock(&dasd_devmap_lock);
- devmap = cdev->dev.driver_data;
- if (devmap && devmap->device) {
- device = devmap->device;
- dasd_get_device(device);
- }
- spin_unlock(&dasd_devmap_lock);
+ spin_lock_irqsave(get_ccwdev_lock(cdev), flags);
+ device = dasd_device_from_cdev_locked(cdev);
+ spin_unlock_irqrestore(get_ccwdev_lock(cdev), flags);
return device;
}
dasd_discipline_show(struct device *dev, struct device_attribute *attr,
char *buf)
{
- struct dasd_devmap *devmap;
- char *dname;
+ struct dasd_device *device;
+ ssize_t len;
- spin_lock(&dasd_devmap_lock);
- dname = "none";
- devmap = dev->driver_data;
- if (devmap && devmap->device && devmap->device->discipline)
- dname = devmap->device->discipline->name;
- spin_unlock(&dasd_devmap_lock);
- return snprintf(buf, PAGE_SIZE, "%s\n", dname);
+ device = dasd_device_from_cdev(to_ccwdev(dev));
+ if (!IS_ERR(device) && device->discipline) {
+ len = snprintf(buf, PAGE_SIZE, "%s\n",
+ device->discipline->name);
+ dasd_put_device(device);
+ } else
+ len = snprintf(buf, PAGE_SIZE, "none\n");
+ return len;
}
static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL);
return 0;
}
-void __exit dasd_eer_exit(void)
+void dasd_eer_exit(void)
{
WARN_ON(misc_deregister(&dasd_eer_dev) != 0);
}
void dasd_remove_sysfs_files(struct ccw_device *);
struct dasd_device *dasd_device_from_cdev(struct ccw_device *);
+struct dasd_device *dasd_device_from_cdev_locked(struct ccw_device *);
struct dasd_device *dasd_device_from_devindex(int);
int dasd_parse(void);
PRINT_WARN("No expanded memory available\n");
return -ENODEV;
}
- xpram_pages = xpram_highest_page_index();
+ xpram_pages = xpram_highest_page_index() + 1;
PRINT_INFO(" %u pages expanded memory found (%lu KB).\n",
xpram_pages, (unsigned long) xpram_pages*4);
rc = xpram_setup_sizes(xpram_pages);
obj-$(CONFIG_S390_TAPE_34XX) += tape_34xx.o
obj-$(CONFIG_S390_TAPE_3590) += tape_3590.o
obj-$(CONFIG_MONREADER) += monreader.o
+obj-$(CONFIG_MONWRITER) += monwriter.o
--- /dev/null
+/*
+ * drivers/s390/char/monwriter.c
+ *
+ * Character device driver for writing z/VM *MONITOR service records.
+ *
+ * Copyright (C) IBM Corp. 2006
+ *
+ * Author(s): Melissa Howland <Melissa.Howland@us.ibm.com>
+ */
+
+#include <linux/module.h>
+#include <linux/moduleparam.h>
+#include <linux/init.h>
+#include <linux/errno.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/miscdevice.h>
+#include <linux/ctype.h>
+#include <linux/poll.h>
+#include <asm/uaccess.h>
+#include <asm/ebcdic.h>
+#include <asm/io.h>
+#include <asm/appldata.h>
+#include <asm/monwriter.h>
+
+#define MONWRITE_MAX_DATALEN 4024
+
+static int mon_max_bufs = 255;
+
+struct mon_buf {
+ struct list_head list;
+ struct monwrite_hdr hdr;
+ int diag_done;
+ char *data;
+};
+
+struct mon_private {
+ struct list_head list;
+ struct monwrite_hdr hdr;
+ size_t hdr_to_read;
+ size_t data_to_read;
+ struct mon_buf *current_buf;
+ int mon_buf_count;
+};
+
+/*
+ * helper functions
+ */
+
+static int monwrite_diag(struct monwrite_hdr *myhdr, char *buffer, int fcn)
+{
+ struct appldata_product_id id;
+ int rc;
+
+ strcpy(id.prod_nr, "LNXAPPL");
+ id.prod_fn = myhdr->applid;
+ id.record_nr = myhdr->record_num;
+ id.version_nr = myhdr->version;
+ id.release_nr = myhdr->release;
+ id.mod_lvl = myhdr->mod_level;
+ rc = appldata_asm(&id, fcn, (void *) buffer, myhdr->datalen);
+ if (rc <= 0)
+ return rc;
+ if (rc == 5)
+ return -EPERM;
+ printk("DIAG X'DC' error with return code: %i\n", rc);
+ return -EINVAL;
+}
+
+static inline struct mon_buf *monwrite_find_hdr(struct mon_private *monpriv,
+ struct monwrite_hdr *monhdr)
+{
+ struct mon_buf *entry, *next;
+
+ list_for_each_entry_safe(entry, next, &monpriv->list, list)
+ if (entry->hdr.applid == monhdr->applid &&
+ entry->hdr.record_num == monhdr->record_num &&
+ entry->hdr.version == monhdr->version &&
+ entry->hdr.release == monhdr->release &&
+ entry->hdr.mod_level == monhdr->mod_level)
+ return entry;
+ return NULL;
+}
+
+static int monwrite_new_hdr(struct mon_private *monpriv)
+{
+ struct monwrite_hdr *monhdr = &monpriv->hdr;
+ struct mon_buf *monbuf;
+ int rc;
+
+ if (monhdr->datalen > MONWRITE_MAX_DATALEN ||
+ monhdr->mon_function > MONWRITE_START_CONFIG ||
+ monhdr->hdrlen != sizeof(struct monwrite_hdr))
+ return -EINVAL;
+ monbuf = monwrite_find_hdr(monpriv, monhdr);
+ if (monbuf) {
+ if (monhdr->mon_function == MONWRITE_STOP_INTERVAL) {
+ monhdr->datalen = monbuf->hdr.datalen;
+ rc = monwrite_diag(monhdr, monbuf->data,
+ APPLDATA_STOP_REC);
+ list_del(&monbuf->list);
+ monpriv->mon_buf_count--;
+ kfree(monbuf->data);
+ kfree(monbuf);
+ monbuf = NULL;
+ }
+ } else {
+ if (monpriv->mon_buf_count >= mon_max_bufs)
+ return -ENOSPC;
+ monbuf = kzalloc(sizeof(struct mon_buf), GFP_KERNEL);
+ if (!monbuf)
+ return -ENOMEM;
+ monbuf->data = kzalloc(monbuf->hdr.datalen,
+ GFP_KERNEL | GFP_DMA);
+ if (!monbuf->data) {
+ kfree(monbuf);
+ return -ENOMEM;
+ }
+ monbuf->hdr = *monhdr;
+ list_add_tail(&monbuf->list, &monpriv->list);
+ monpriv->mon_buf_count++;
+ }
+ monpriv->current_buf = monbuf;
+ return 0;
+}
+
+static int monwrite_new_data(struct mon_private *monpriv)
+{
+ struct monwrite_hdr *monhdr = &monpriv->hdr;
+ struct mon_buf *monbuf = monpriv->current_buf;
+ int rc = 0;
+
+ switch (monhdr->mon_function) {
+ case MONWRITE_START_INTERVAL:
+ if (!monbuf->diag_done) {
+ rc = monwrite_diag(monhdr, monbuf->data,
+ APPLDATA_START_INTERVAL_REC);
+ monbuf->diag_done = 1;
+ }
+ break;
+ case MONWRITE_START_CONFIG:
+ if (!monbuf->diag_done) {
+ rc = monwrite_diag(monhdr, monbuf->data,
+ APPLDATA_START_CONFIG_REC);
+ monbuf->diag_done = 1;
+ }
+ break;
+ case MONWRITE_GEN_EVENT:
+ rc = monwrite_diag(monhdr, monbuf->data,
+ APPLDATA_GEN_EVENT_REC);
+ list_del(&monpriv->current_buf->list);
+ kfree(monpriv->current_buf->data);
+ kfree(monpriv->current_buf);
+ monpriv->current_buf = NULL;
+ break;
+ default:
+ /* monhdr->mon_function is checked in monwrite_new_hdr */
+ BUG();
+ }
+ return rc;
+}
+
+/*
+ * file operations
+ */
+
+static int monwrite_open(struct inode *inode, struct file *filp)
+{
+ struct mon_private *monpriv;
+
+ monpriv = kzalloc(sizeof(struct mon_private), GFP_KERNEL);
+ if (!monpriv)
+ return -ENOMEM;
+ INIT_LIST_HEAD(&monpriv->list);
+ monpriv->hdr_to_read = sizeof(monpriv->hdr);
+ filp->private_data = monpriv;
+ return nonseekable_open(inode, filp);
+}
+
+static int monwrite_close(struct inode *inode, struct file *filp)
+{
+ struct mon_private *monpriv = filp->private_data;
+ struct mon_buf *entry, *next;
+
+ list_for_each_entry_safe(entry, next, &monpriv->list, list) {
+ if (entry->hdr.mon_function != MONWRITE_GEN_EVENT)
+ monwrite_diag(&entry->hdr, entry->data,
+ APPLDATA_STOP_REC);
+ monpriv->mon_buf_count--;
+ list_del(&entry->list);
+ kfree(entry->data);
+ kfree(entry);
+ }
+ kfree(monpriv);
+ return 0;
+}
+
+static ssize_t monwrite_write(struct file *filp, const char __user *data,
+ size_t count, loff_t *ppos)
+{
+ struct mon_private *monpriv = filp->private_data;
+ size_t len, written;
+ void *to;
+ int rc;
+
+ for (written = 0; written < count; ) {
+ if (monpriv->hdr_to_read) {
+ len = min(count - written, monpriv->hdr_to_read);
+ to = (char *) &monpriv->hdr +
+ sizeof(monpriv->hdr) - monpriv->hdr_to_read;
+ if (copy_from_user(to, data + written, len)) {
+ rc = -EFAULT;
+ goto out_error;
+ }
+ monpriv->hdr_to_read -= len;
+ written += len;
+ if (monpriv->hdr_to_read > 0)
+ continue;
+ rc = monwrite_new_hdr(monpriv);
+ if (rc)
+ goto out_error;
+ monpriv->data_to_read = monpriv->current_buf ?
+ monpriv->current_buf->hdr.datalen : 0;
+ }
+
+ if (monpriv->data_to_read) {
+ len = min(count - written, monpriv->data_to_read);
+ to = monpriv->current_buf->data +
+ monpriv->hdr.datalen - monpriv->data_to_read;
+ if (copy_from_user(to, data + written, len)) {
+ rc = -EFAULT;
+ goto out_error;
+ }
+ monpriv->data_to_read -= len;
+ written += len;
+ if (monpriv->data_to_read > 0)
+ continue;
+ rc = monwrite_new_data(monpriv);
+ if (rc)
+ goto out_error;
+ }
+ monpriv->hdr_to_read = sizeof(monpriv->hdr);
+ }
+ return written;
+
+out_error:
+ monpriv->data_to_read = 0;
+ monpriv->hdr_to_read = sizeof(struct monwrite_hdr);
+ return rc;
+}
+
+static struct file_operations monwrite_fops = {
+ .owner = THIS_MODULE,
+ .open = &monwrite_open,
+ .release = &monwrite_close,
+ .write = &monwrite_write,
+};
+
+static struct miscdevice mon_dev = {
+ .name = "monwriter",
+ .fops = &monwrite_fops,
+ .minor = MISC_DYNAMIC_MINOR,
+};
+
+/*
+ * module init/exit
+ */
+
+static int __init mon_init(void)
+{
+ if (MACHINE_IS_VM)
+ return misc_register(&mon_dev);
+ else
+ return -ENODEV;
+}
+
+static void __exit mon_exit(void)
+{
+ WARN_ON(misc_deregister(&mon_dev) != 0);
+}
+
+module_init(mon_init);
+module_exit(mon_exit);
+
+module_param_named(max_bufs, mon_max_bufs, int, 0644);
+MODULE_PARM_DESC(max_bufs, "Maximum number of sample monitor data buffers"
+ "that can be active at one time");
+
+MODULE_AUTHOR("Melissa Howland <Melissa.Howland@us.ibm.com>");
+MODULE_DESCRIPTION("Character device driver for writing z/VM "
+ "APPLDATA monitor records.");
+MODULE_LICENSE("GPL");
/*
* Copyright (C) 2004,2005 IBM Corporation
- * Interface implementation for communication with the v/VM control program
+ * Interface implementation for communication with the z/VM control program
* Author(s): Christian Borntraeger <cborntra@de.ibm.com>
*
*
/*
* Copyright (C) 2004, 2005 IBM Corporation
- * Interface implementation for communication with the v/VM control program
+ * Interface implementation for communication with the z/VM control program
* Version 1.0
* Author(s): Christian Borntraeger <cborntra@de.ibm.com>
*
/* trigger path verification. */
if (sch->driver && sch->driver->verify)
sch->driver->verify(&sch->dev);
- else if (sch->vpm == mask)
+ else if (sch->lpm == mask)
goto out_unreg;
out_unlock:
spin_unlock_irq(&sch->lock);
if (chp_mask == 0) {
spin_unlock_irq(&sch->lock);
+ put_device(&sch->dev);
return 0;
}
old_lpm = sch->lpm;
spin_unlock_irq(&sch->lock);
put_device(&sch->dev);
- return (res_data->fla_mask == 0xffff) ? -ENODEV : 0;
+ return 0;
}
#include <linux/device.h>
#include <linux/kernel_stat.h>
#include <linux/interrupt.h>
-
#include <asm/cio.h>
#include <asm/delay.h>
#include <asm/irq.h>
-
+#include <asm/setup.h>
#include "airq.h"
#include "cio.h"
#include "css.h"
sch->orb.pfch = sch->options.prefetch == 0;
sch->orb.spnd = sch->options.suspend;
sch->orb.ssic = sch->options.suspend && sch->options.inter;
- sch->orb.lpm = (lpm != 0) ? (lpm & sch->opm) : sch->lpm;
+ sch->orb.lpm = (lpm != 0) ? lpm : sch->lpm;
#ifdef CONFIG_64BIT
/*
* for 64 bit we always support 64 bit IDAWs with 4k page size only
sch->opm = 0xff;
if (!cio_is_console(sch->schid))
chsc_validate_chpids(sch);
- sch->lpm = sch->schib.pmcw.pim &
- sch->schib.pmcw.pam &
- sch->schib.pmcw.pom &
- sch->opm;
+ sch->lpm = sch->schib.pmcw.pam & sch->opm;
CIO_DEBUG(KERN_INFO, 0,
"Detected device %04x on subchannel 0.%x.%04X"
return -EBUSY;
}
-extern void do_reipl(unsigned long devno);
-static int
-__shutdown_subchannel_easy(struct subchannel_id schid, void *data)
+struct sch_match_id {
+ struct subchannel_id schid;
+ struct ccw_dev_id devid;
+ int rc;
+};
+
+static int __shutdown_subchannel_easy_and_match(struct subchannel_id schid,
+ void *data)
{
struct schib schib;
+ struct sch_match_id *match_id = data;
if (stsch_err(schid, &schib))
return -ENXIO;
+ if (match_id && schib.pmcw.dnv &&
+ (schib.pmcw.dev == match_id->devid.devno) &&
+ (schid.ssid == match_id->devid.ssid)) {
+ match_id->schid = schid;
+ match_id->rc = 0;
+ }
if (!schib.pmcw.ena)
return 0;
switch(__disable_subchannel_easy(schid, &schib)) {
return 0;
}
-void
-clear_all_subchannels(void)
+static int clear_all_subchannels_and_match(struct ccw_dev_id *devid,
+ struct subchannel_id *schid)
{
+ struct sch_match_id match_id;
+
+ match_id.devid = *devid;
+ match_id.rc = -ENODEV;
local_irq_disable();
- for_each_subchannel(__shutdown_subchannel_easy, NULL);
+ for_each_subchannel(__shutdown_subchannel_easy_and_match, &match_id);
+ if (match_id.rc == 0)
+ *schid = match_id.schid;
+ return match_id.rc;
}
+
+void clear_all_subchannels(void)
+{
+ local_irq_disable();
+ for_each_subchannel(__shutdown_subchannel_easy_and_match, NULL);
+}
+
+extern void do_reipl_asm(__u32 schid);
+
/* Make sure all subchannels are quiet before we re-ipl an lpar. */
-void
-reipl(unsigned long devno)
+void reipl_ccw_dev(struct ccw_dev_id *devid)
{
- clear_all_subchannels();
+ struct subchannel_id schid;
+
+ if (clear_all_subchannels_and_match(devid, &schid))
+ panic("IPL Device not found\n");
cio_reset_channel_paths();
- do_reipl(devno);
+ do_reipl_asm(*((__u32*)&schid));
+}
+
+extern struct schib ipl_schib;
+
+/*
+ * ipl_save_parameters gets called very early. It is not allowed to access
+ * anything in the bss section at all. The bss section is not cleared yet,
+ * but may contain some ipl parameters written by the firmware.
+ * These parameters (if present) are copied to 0x2000.
+ * To avoid corruption of the ipl parameters, all variables used by this
+ * function must reside on the stack or in the data section.
+ */
+void ipl_save_parameters(void)
+{
+ struct subchannel_id schid;
+ unsigned int *ipl_ptr;
+ void *src, *dst;
+
+ schid = *(struct subchannel_id *)__LC_SUBCHANNEL_ID;
+ if (!schid.one)
+ return;
+ if (stsch(schid, &ipl_schib))
+ return;
+ if (!ipl_schib.pmcw.dnv)
+ return;
+ ipl_devno = ipl_schib.pmcw.dev;
+ ipl_flags |= IPL_DEVNO_VALID;
+ if (!ipl_schib.pmcw.qf)
+ return;
+ ipl_flags |= IPL_PARMBLOCK_VALID;
+ ipl_ptr = (unsigned int *)__LC_IPL_PARMBLOCK_PTR;
+ src = (void *)(unsigned long)*ipl_ptr;
+ dst = (void *)IPL_PARMBLOCK_ORIGIN;
+ memmove(dst, src, PAGE_SIZE);
+ *ipl_ptr = IPL_PARMBLOCK_ORIGIN;
}
return dev ? to_subchannel(dev) : NULL;
}
-
-static inline int
-css_get_subchannel_status(struct subchannel *sch, struct subchannel_id schid)
+static inline int css_get_subchannel_status(struct subchannel *sch)
{
struct schib schib;
- int cc;
- cc = stsch(schid, &schib);
- if (cc)
- return CIO_GONE;
- if (!schib.pmcw.dnv)
+ if (stsch(sch->schid, &schib) || !schib.pmcw.dnv)
return CIO_GONE;
- if (sch && sch->schib.pmcw.dnv &&
- (schib.pmcw.dev != sch->schib.pmcw.dev))
+ if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev))
return CIO_REVALIDATE;
- if (sch && !sch->lpm)
+ if (!sch->lpm)
return CIO_NO_PATH;
return CIO_OPER;
}
-
-static int
-css_evaluate_subchannel(struct subchannel_id schid, int slow)
+
+static int css_evaluate_known_subchannel(struct subchannel *sch, int slow)
{
int event, ret, disc;
- struct subchannel *sch;
unsigned long flags;
+ enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE } action;
- sch = get_subchannel_by_schid(schid);
- disc = sch ? device_is_disconnected(sch) : 0;
+ spin_lock_irqsave(&sch->lock, flags);
+ disc = device_is_disconnected(sch);
if (disc && slow) {
- if (sch)
- put_device(&sch->dev);
- return 0; /* Already processed. */
+ /* Disconnected devices are evaluated directly only.*/
+ spin_unlock_irqrestore(&sch->lock, flags);
+ return 0;
}
- /*
- * We've got a machine check, so running I/O won't get an interrupt.
- * Kill any pending timers.
- */
- if (sch)
- device_kill_pending_timer(sch);
+ /* No interrupt after machine check - kill pending timers. */
+ device_kill_pending_timer(sch);
if (!disc && !slow) {
- if (sch)
- put_device(&sch->dev);
- return -EAGAIN; /* Will be done on the slow path. */
+ /* Non-disconnected devices are evaluated on the slow path. */
+ spin_unlock_irqrestore(&sch->lock, flags);
+ return -EAGAIN;
}
- event = css_get_subchannel_status(sch, schid);
+ event = css_get_subchannel_status(sch);
CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n",
- schid.ssid, schid.sch_no, event,
- sch?(disc?"disconnected":"normal"):"unknown",
- slow?"slow":"fast");
+ 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:
- case CIO_GONE:
- if (!sch) {
- /* Never used this subchannel. Ignore. */
- ret = 0;
+ if (disc) {
+ /* Check if paths have become available. */
+ action = REPROBE;
break;
}
- if (disc && (event == CIO_NO_PATH)) {
- /*
- * Uargh, hack again. Because we don't get a machine
- * check on configure on, our path bookkeeping can
- * be out of date here (it's fine while we only do
- * logical varying or get chsc machine checks). We
- * need to force reprobing or we might miss devices
- * coming operational again. It won't do harm in real
- * no path situations.
- */
- spin_lock_irqsave(&sch->lock, flags);
- device_trigger_reprobe(sch);
+ /* fall through */
+ case CIO_GONE:
+ /* Prevent unwanted effects when opening lock. */
+ cio_disable_subchannel(sch);
+ device_set_disconnected(sch);
+ /* Ask driver what to do with device. */
+ action = UNREGISTER;
+ if (sch->driver && sch->driver->notify) {
spin_unlock_irqrestore(&sch->lock, flags);
- ret = 0;
- break;
- }
- if (sch->driver && sch->driver->notify &&
- sch->driver->notify(&sch->dev, event)) {
- cio_disable_subchannel(sch);
- device_set_disconnected(sch);
- ret = 0;
- break;
+ ret = sch->driver->notify(&sch->dev, event);
+ spin_lock_irqsave(&sch->lock, flags);
+ if (ret)
+ action = NONE;
}
- /*
- * Unregister subchannel.
- * The device will be killed automatically.
- */
- cio_disable_subchannel(sch);
- css_sch_device_unregister(sch);
- /* Reset intparm to zeroes. */
- sch->schib.pmcw.intparm = 0;
- cio_modify(sch);
- put_device(&sch->dev);
- ret = 0;
break;
case CIO_REVALIDATE:
- /*
- * Revalidation machine check. Sick.
- * We don't notify the driver since we have to throw the device
- * away in any case.
- */
- if (!disc) {
- css_sch_device_unregister(sch);
- /* Reset intparm to zeroes. */
- sch->schib.pmcw.intparm = 0;
- cio_modify(sch);
- put_device(&sch->dev);
- ret = css_probe_device(schid);
- } else {
- /*
- * We can't immediately deregister the disconnected
- * device since it might block.
- */
- spin_lock_irqsave(&sch->lock, flags);
- device_trigger_reprobe(sch);
- spin_unlock_irqrestore(&sch->lock, flags);
- ret = 0;
- }
+ /* Device will be removed, so no notify necessary. */
+ if (disc)
+ /* Reprobe because immediate unregister might block. */
+ action = REPROBE;
+ else
+ action = UNREGISTER_PROBE;
break;
case CIO_OPER:
- if (disc) {
- spin_lock_irqsave(&sch->lock, flags);
+ if (disc)
/* Get device operational again. */
- device_trigger_reprobe(sch);
- spin_unlock_irqrestore(&sch->lock, flags);
- }
- ret = sch ? 0 : css_probe_device(schid);
+ action = REPROBE;
+ break;
+ }
+ /* Perform action. */
+ ret = 0;
+ switch (action) {
+ case UNREGISTER:
+ case UNREGISTER_PROBE:
+ /* Unregister device (will use subchannel lock). */
+ spin_unlock_irqrestore(&sch->lock, flags);
+ css_sch_device_unregister(sch);
+ spin_lock_irqsave(&sch->lock, flags);
+
+ /* Reset intparm to zeroes. */
+ sch->schib.pmcw.intparm = 0;
+ cio_modify(sch);
+
+ /* Probe if necessary. */
+ if (action == UNREGISTER_PROBE)
+ ret = css_probe_device(sch->schid);
+ break;
+ case REPROBE:
+ device_trigger_reprobe(sch);
break;
default:
- BUG();
- ret = 0;
+ break;
+ }
+ spin_unlock_irqrestore(&sch->lock, flags);
+
+ return ret;
+}
+
+static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
+{
+ struct schib schib;
+
+ if (!slow) {
+ /* Will be done on the slow path. */
+ return -EAGAIN;
}
+ if (stsch(schid, &schib) || !schib.pmcw.dnv) {
+ /* 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);
+
+ return css_probe_device(schid);
+}
+
+static int css_evaluate_subchannel(struct subchannel_id schid, int slow)
+{
+ struct subchannel *sch;
+ int ret;
+
+ sch = get_subchannel_by_schid(schid);
+ if (sch) {
+ ret = css_evaluate_known_subchannel(sch, slow);
+ put_device(&sch->dev);
+ } else
+ ret = css_evaluate_new_subchannel(schid, slow);
+
return ret;
}
return 1;
}
-/*
- * Hotplugging interface for ccw devices.
- * Heavily modeled on pci and usb hotplug.
- */
-static int
-ccw_uevent (struct device *dev, char **envp, int num_envp,
- char *buffer, int buffer_size)
+/* Store modalias string delimited by prefix/suffix string into buffer with
+ * specified size. Return length of resulting string (excluding trailing '\0')
+ * even if string doesn't fit buffer (snprintf semantics). */
+static int snprint_alias(char *buf, size_t size, const char *prefix,
+ struct ccw_device_id *id, const char *suffix)
{
- struct ccw_device *cdev = to_ccwdev(dev);
- int i = 0;
- int length = 0;
+ int len;
- if (!cdev)
- return -ENODEV;
+ len = snprintf(buf, size, "%sccw:t%04Xm%02X", prefix, id->cu_type,
+ id->cu_model);
+ if (len > size)
+ return len;
+ buf += len;
+ size -= len;
- /* what we want to pass to /sbin/hotplug */
+ if (id->dev_type != 0)
+ len += snprintf(buf, size, "dt%04Xdm%02X%s", id->dev_type,
+ id->dev_model, suffix);
+ else
+ len += snprintf(buf, size, "dtdm%s", suffix);
- envp[i++] = buffer;
- length += scnprintf(buffer, buffer_size - length, "CU_TYPE=%04X",
- cdev->id.cu_type);
- if ((buffer_size - length <= 0) || (i >= num_envp))
- return -ENOMEM;
- ++length;
- buffer += length;
+ return len;
+}
+/* Set up environment variables for ccw device uevent. Return 0 on success,
+ * non-zero otherwise. */
+static int ccw_uevent(struct device *dev, char **envp, int num_envp,
+ char *buffer, int buffer_size)
+{
+ struct ccw_device *cdev = to_ccwdev(dev);
+ struct ccw_device_id *id = &(cdev->id);
+ int i = 0;
+ int len;
+
+ /* CU_TYPE= */
+ len = snprintf(buffer, buffer_size, "CU_TYPE=%04X", id->cu_type) + 1;
+ if (len > buffer_size || i >= num_envp)
+ return -ENOMEM;
envp[i++] = buffer;
- length += scnprintf(buffer, buffer_size - length, "CU_MODEL=%02X",
- cdev->id.cu_model);
- if ((buffer_size - length <= 0) || (i >= num_envp))
+ buffer += len;
+ buffer_size -= len;
+
+ /* CU_MODEL= */
+ len = snprintf(buffer, buffer_size, "CU_MODEL=%02X", id->cu_model) + 1;
+ if (len > buffer_size || i >= num_envp)
return -ENOMEM;
- ++length;
- buffer += length;
+ envp[i++] = buffer;
+ buffer += len;
+ buffer_size -= len;
/* The next two can be zero, that's ok for us */
- envp[i++] = buffer;
- length += scnprintf(buffer, buffer_size - length, "DEV_TYPE=%04X",
- cdev->id.dev_type);
- if ((buffer_size - length <= 0) || (i >= num_envp))
+ /* DEV_TYPE= */
+ len = snprintf(buffer, buffer_size, "DEV_TYPE=%04X", id->dev_type) + 1;
+ if (len > buffer_size || i >= num_envp)
return -ENOMEM;
- ++length;
- buffer += length;
+ envp[i++] = buffer;
+ buffer += len;
+ buffer_size -= len;
+ /* DEV_MODEL= */
+ len = snprintf(buffer, buffer_size, "DEV_MODEL=%02X",
+ (unsigned char) id->dev_model) + 1;
+ if (len > buffer_size || i >= num_envp)
+ return -ENOMEM;
envp[i++] = buffer;
- length += scnprintf(buffer, buffer_size - length, "DEV_MODEL=%02X",
- cdev->id.dev_model);
- if ((buffer_size - length <= 0) || (i >= num_envp))
+ buffer += len;
+ buffer_size -= len;
+
+ /* MODALIAS= */
+ len = snprint_alias(buffer, buffer_size, "MODALIAS=", id, "") + 1;
+ if (len > buffer_size || i >= num_envp)
return -ENOMEM;
+ envp[i++] = buffer;
+ buffer += len;
+ buffer_size -= len;
envp[i] = NULL;
{
struct ccw_device *cdev = to_ccwdev(dev);
struct ccw_device_id *id = &(cdev->id);
- int ret;
+ int len;
- ret = sprintf(buf, "ccw:t%04Xm%02X",
- id->cu_type, id->cu_model);
- if (id->dev_type != 0)
- ret += sprintf(buf + ret, "dt%04Xdm%02X\n",
- id->dev_type, id->dev_model);
- else
- ret += sprintf(buf + ret, "dtdm\n");
- return ret;
+ len = snprint_alias(buf, PAGE_SIZE, "", id, "\n") + 1;
+
+ return len > PAGE_SIZE ? PAGE_SIZE : len;
}
static ssize_t
*/
old_lpm = sch->lpm;
stsch(sch->schid, &sch->schib);
- sch->lpm = sch->schib.pmcw.pim &
- sch->schib.pmcw.pam &
- sch->schib.pmcw.pom &
- sch->opm;
+ sch->lpm = sch->schib.pmcw.pam & sch->opm;
/* Check since device may again have become not operational. */
if (!sch->schib.pmcw.dnv)
state = DEV_STATE_NOT_OPER;
notify = 1;
}
/* fill out sense information */
+ memset(&cdev->id, 0, sizeof(cdev->id));
cdev->id.cu_type = cdev->private->senseid.cu_type;
cdev->id.cu_model = cdev->private->senseid.cu_model;
cdev->id.dev_type = cdev->private->senseid.dev_type;
return;
}
/* Start Path Group verification. */
- sch->vpm = 0; /* Start with no path groups set. */
cdev->private->state = DEV_STATE_VERIFY;
+ cdev->private->flags.doverify = 0;
ccw_device_verify_start(cdev);
}
void
ccw_device_verify_done(struct ccw_device *cdev, int err)
{
- cdev->private->flags.doverify = 0;
+ struct subchannel *sch;
+
+ sch = to_subchannel(cdev->dev.parent);
+ /* Update schib - pom may have changed. */
+ stsch(sch->schid, &sch->schib);
+ /* 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;
+ }
switch (err) {
case -EOPNOTSUPP: /* path grouping not supported, just set online. */
cdev->private->options.pgroup = 0;
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;
}
/* Are we doing path grouping? */
if (!cdev->private->options.pgroup) {
/* No, set state offline immediately. */
- sch->vpm = 0;
ccw_device_done(cdev, DEV_STATE_OFFLINE);
return 0;
}
}
/* 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);
}
}
static void
-ccw_device_wait4io_verify(struct ccw_device *cdev, enum dev_event dev_event)
+ccw_device_delay_verify(struct ccw_device *cdev, enum dev_event dev_event)
{
- /* When the I/O has terminated, we have to start verification. */
+ /* Start verification after current task finished. */
cdev->private->flags.doverify = 1;
}
* The pim, pam, pom values may not be accurate, but they are the best
* we have before performing device selection :/
*/
- sch->lpm = sch->schib.pmcw.pim &
- sch->schib.pmcw.pam &
- sch->schib.pmcw.pom &
- sch->opm;
+ sch->lpm = sch->schib.pmcw.pam & sch->opm;
/* Re-set some bits in the pmcw that were lost. */
sch->schib.pmcw.isc = 3;
sch->schib.pmcw.csense = 1;
[DEV_EVENT_NOTOPER] = ccw_device_online_notoper,
[DEV_EVENT_INTERRUPT] = ccw_device_verify_irq,
[DEV_EVENT_TIMEOUT] = ccw_device_onoff_timeout,
- [DEV_EVENT_VERIFY] = ccw_device_nop,
+ [DEV_EVENT_VERIFY] = ccw_device_delay_verify,
},
[DEV_STATE_ONLINE] = {
[DEV_EVENT_NOTOPER] = ccw_device_online_notoper,
[DEV_EVENT_NOTOPER] = ccw_device_online_notoper,
[DEV_EVENT_INTERRUPT] = ccw_device_wait4io_irq,
[DEV_EVENT_TIMEOUT] = ccw_device_wait4io_timeout,
- [DEV_EVENT_VERIFY] = ccw_device_wait4io_verify,
+ [DEV_EVENT_VERIFY] = ccw_device_delay_verify,
},
[DEV_STATE_QUIESCE] = {
[DEV_EVENT_NOTOPER] = ccw_device_quiesce_done,
[DEV_EVENT_NOTOPER] = ccw_device_nop,
[DEV_EVENT_INTERRUPT] = ccw_device_start_id,
[DEV_EVENT_TIMEOUT] = ccw_device_bug,
- [DEV_EVENT_VERIFY] = ccw_device_nop,
+ [DEV_EVENT_VERIFY] = ccw_device_start_id,
},
[DEV_STATE_DISCONNECTED_SENSE_ID] = {
[DEV_EVENT_NOTOPER] = ccw_device_recog_notoper,
ret = cio_set_options (sch, flags);
if (ret)
return ret;
+ /* Adjust requested path mask to excluded varied off paths. */
+ if (lpm) {
+ lpm &= sch->opm;
+ if (lpm == 0)
+ return -EACCES;
+ }
ret = cio_start_key (sch, cpa, lpm, key);
if (ret == 0)
cdev->private->intparm = intparm;
if (!sch)
return 0;
else
- return sch->vpm;
+ return sch->lpm;
}
static void
sch = to_subchannel(cdev->dev.parent);
do {
ret = cio_start (sch, ccw, lpm);
- if ((ret == -EBUSY) || (ret == -EACCES)) {
+ if (ret == -EBUSY) {
/* Try again later. */
spin_unlock_irq(&sch->lock);
msleep(10);
if (!ciw || ciw->cmd == 0)
return -EOPNOTSUPP;
+ /* Adjust requested path mask to excluded varied off paths. */
+ if (lpm) {
+ lpm &= sch->opm;
+ if (lpm == 0)
+ return -EACCES;
+ }
+
rcd_ccw = kzalloc(sizeof(struct ccw1), GFP_KERNEL | GFP_DMA);
if (!rcd_ccw)
return -ENOMEM;
memset(&cdev->private->irb, 0, sizeof(struct irb));
/* Try multiple times. */
- ret = -ENODEV;
+ ret = -EACCES;
if (cdev->private->iretry > 0) {
cdev->private->iretry--;
ret = cio_start (sch, cdev->private->iccws,
cdev->private->imask);
- /* ret is 0, -EBUSY, -EACCES or -ENODEV */
- if ((ret != -EACCES) && (ret != -ENODEV))
+ /* We expect an interrupt in case of success or busy
+ * indication. */
+ if ((ret == 0) || (ret == -EBUSY))
return ret;
}
- /* PGID command failed on this path. Switch it off. */
- sch->lpm &= ~cdev->private->imask;
- sch->vpm &= ~cdev->private->imask;
+ /* PGID command failed on this path. */
CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel "
"0.%x.%04x, lpm %02X, became 'not operational'\n",
cdev->private->devno, sch->schid.ssid,
memset(&cdev->private->irb, 0, sizeof(struct irb));
/* Try multiple times. */
- ret = -ENODEV;
+ ret = -EACCES;
if (cdev->private->iretry > 0) {
cdev->private->iretry--;
ret = cio_start (sch, cdev->private->iccws,
cdev->private->imask);
- /* ret is 0, -EBUSY, -EACCES or -ENODEV */
- if ((ret != -EACCES) && (ret != -ENODEV))
+ /* We expect an interrupt in case of success or busy
+ * indication. */
+ if ((ret == 0) || (ret == -EBUSY))
return ret;
}
- /* nop command failed on this path. Switch it off. */
- sch->lpm &= ~cdev->private->imask;
- sch->vpm &= ~cdev->private->imask;
+ /* nop command failed on this path. */
CIO_MSG_EVENT(2, "NOP - Device %04x on Subchannel "
"0.%x.%04x, lpm %02X, became 'not operational'\n",
cdev->private->devno, sch->schid.ssid,
__ccw_device_verify_start(struct ccw_device *cdev)
{
struct subchannel *sch;
- __u8 imask, func;
+ __u8 func;
int ret;
sch = to_subchannel(cdev->dev.parent);
- while (sch->vpm != sch->lpm) {
- /* Find first unequal bit in vpm vs. lpm */
- for (imask = 0x80; imask != 0; imask >>= 1)
- if ((sch->vpm & imask) != (sch->lpm & imask))
- break;
- cdev->private->imask = imask;
+ /* 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. */
+ continue;
if (cdev->private->options.pgroup) {
- func = (sch->vpm & imask) ?
- SPID_FUNC_RESIGN : SPID_FUNC_ESTABLISH;
+ 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;
- cdev->private->iretry = 5;
+ /* Permanent path failure, try next. */
}
- ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
+ /* Done with all paths. */
+ ccw_device_verify_done(cdev, (sch->vpm != 0) ? 0 : -ENODEV);
}
/*
else
ret = __ccw_device_check_nop(cdev);
memset(&cdev->private->irb, 0, sizeof(struct irb));
+
switch (ret) {
/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
case 0:
- /* Establish or Resign Path Group done. Update vpm. */
- if ((sch->lpm & cdev->private->imask) != 0)
- sch->vpm |= cdev->private->imask;
- else
- sch->vpm &= ~cdev->private->imask;
+ /* 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);
break;
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);
ccw_device_verify_done(cdev, -ETIME);
break;
case -EACCES: /* channel is not operational. */
- sch->lpm &= ~cdev->private->imask;
- sch->vpm &= ~cdev->private->imask;
+ cdev->private->imask >>= 1;
cdev->private->iretry = 5;
__ccw_device_verify_start(cdev);
break;
struct subchannel *sch = to_subchannel(cdev->dev.parent);
cdev->private->flags.pgid_single = 0;
+ cdev->private->imask = 0x80;
cdev->private->iretry = 5;
- /*
- * Update sch->lpm with current values to catch paths becoming
- * available again.
- */
+
+ /* Start with empty vpm. */
+ sch->vpm = 0;
+
+ /* Get current pam. */
if (stsch(sch->schid, &sch->schib)) {
ccw_device_verify_done(cdev, -ENODEV);
return;
}
- sch->lpm = sch->schib.pmcw.pim &
- sch->schib.pmcw.pam &
- sch->schib.pmcw.pom &
- sch->opm;
__ccw_device_verify_start(cdev);
}
switch (ret) {
/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
case 0: /* disband successful. */
- sch->vpm = 0;
ccw_device_disband_done(cdev, ret);
break;
case -EOPNOTSUPP:
static inline __u64
qdio_get_micros(void)
{
- return (get_clock() >> 10); /* time>>12 is microseconds */
+ return (get_clock() >> 12); /* time>>12 is microseconds */
}
/*
#ifdef QDIO_USE_PROCESSING_STATE
if (last_position>=0)
- set_slsb(q, &last_position, SLSB_P_INPUT_NOT_INIT, &count);
+ set_slsb(q, &last_position, SLSB_P_INPUT_PROCESSING, &count);
#endif /* QDIO_USE_PROCESSING_STATE */
QDIO_DBF_HEX4(0,trace,&q->first_to_check,sizeof(int));
#if QDIO_VERBOSE_LEVEL>8
#define QDIO_PRINT_STUPID(x...) printk( KERN_DEBUG QDIO_PRINTK_HEADER x)
#else
-#define QDIO_PRINT_STUPID(x...)
+#define QDIO_PRINT_STUPID(x...) do { } while (0)
#endif
#if QDIO_VERBOSE_LEVEL>7
#define QDIO_PRINT_ALL(x...) printk( QDIO_PRINTK_HEADER x)
#else
-#define QDIO_PRINT_ALL(x...)
+#define QDIO_PRINT_ALL(x...) do { } while (0)
#endif
#if QDIO_VERBOSE_LEVEL>6
#define QDIO_PRINT_INFO(x...) printk( QDIO_PRINTK_HEADER x)
#else
-#define QDIO_PRINT_INFO(x...)
+#define QDIO_PRINT_INFO(x...) do { } while (0)
#endif
#if QDIO_VERBOSE_LEVEL>5
#define QDIO_PRINT_WARN(x...) printk( QDIO_PRINTK_HEADER x)
#else
-#define QDIO_PRINT_WARN(x...)
+#define QDIO_PRINT_WARN(x...) do { } while (0)
#endif
#if QDIO_VERBOSE_LEVEL>4
#define QDIO_PRINT_ERR(x...) printk( QDIO_PRINTK_HEADER x)
#else
-#define QDIO_PRINT_ERR(x...)
+#define QDIO_PRINT_ERR(x...) do { } while (0)
#endif
#if QDIO_VERBOSE_LEVEL>3
#define QDIO_PRINT_CRIT(x...) printk( QDIO_PRINTK_HEADER x)
#else
-#define QDIO_PRINT_CRIT(x...)
+#define QDIO_PRINT_CRIT(x...) do { } while (0)
#endif
#if QDIO_VERBOSE_LEVEL>2
#define QDIO_PRINT_ALERT(x...) printk( QDIO_PRINTK_HEADER x)
#else
-#define QDIO_PRINT_ALERT(x...)
+#define QDIO_PRINT_ALERT(x...) do { } while (0)
#endif
#if QDIO_VERBOSE_LEVEL>1
#define QDIO_PRINT_EMERG(x...) printk( QDIO_PRINTK_HEADER x)
#else
-#define QDIO_PRINT_EMERG(x...)
+#define QDIO_PRINT_EMERG(x...) do { } while (0)
#endif
#define HEXDUMP16(importance,header,ptr) \
# S/390 crypto devices
#
-z90crypt-objs := z90main.o z90hardware.o
-obj-$(CONFIG_Z90CRYPT) += z90crypt.o
+ifdef CONFIG_ZCRYPT_MONOLITHIC
+
+z90crypt-objs := zcrypt_mono.o ap_bus.o zcrypt_api.o \
+ zcrypt_pcica.o zcrypt_pcicc.o zcrypt_pcixcc.o zcrypt_cex2a.o
+obj-$(CONFIG_ZCRYPT) += z90crypt.o
+
+else
+
+ap-objs := ap_bus.o
+obj-$(CONFIG_ZCRYPT) += ap.o zcrypt_api.o zcrypt_pcicc.o zcrypt_pcixcc.o
+obj-$(CONFIG_ZCRYPT) += zcrypt_pcica.o zcrypt_cex2a.o
+
+endif
--- /dev/null
+/*
+ * linux/drivers/s390/crypto/ap_bus.c
+ *
+ * Copyright (C) 2006 IBM Corporation
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Ralph Wuerthner <rwuerthn@de.ibm.com>
+ *
+ * Adjunct processor bus.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#include <linux/module.h>
+#include <linux/init.h>
+#include <linux/delay.h>
+#include <linux/err.h>
+#include <linux/interrupt.h>
+#include <linux/workqueue.h>
+#include <linux/notifier.h>
+#include <linux/kthread.h>
+#include <linux/mutex.h>
+#include <asm/s390_rdev.h>
+
+#include "ap_bus.h"
+
+/* Some prototypes. */
+static void ap_scan_bus(void *);
+static void ap_poll_all(unsigned long);
+static void ap_poll_timeout(unsigned long);
+static int ap_poll_thread_start(void);
+static void ap_poll_thread_stop(void);
+
+/**
+ * Module description.
+ */
+MODULE_AUTHOR("IBM Corporation");
+MODULE_DESCRIPTION("Adjunct Processor Bus driver, "
+ "Copyright 2006 IBM Corporation");
+MODULE_LICENSE("GPL");
+
+/**
+ * Module parameter
+ */
+int ap_domain_index = -1; /* Adjunct Processor Domain Index */
+module_param_named(domain, ap_domain_index, int, 0000);
+MODULE_PARM_DESC(domain, "domain index for ap devices");
+EXPORT_SYMBOL(ap_domain_index);
+
+static int ap_thread_flag = 1;
+module_param_named(poll_thread, ap_thread_flag, int, 0000);
+MODULE_PARM_DESC(poll_thread, "Turn on/off poll thread, default is 1 (on).");
+
+static struct device *ap_root_device = NULL;
+
+/**
+ * Workqueue & timer for bus rescan.
+ */
+static struct workqueue_struct *ap_work_queue;
+static struct timer_list ap_config_timer;
+static int ap_config_time = AP_CONFIG_TIME;
+static DECLARE_WORK(ap_config_work, ap_scan_bus, NULL);
+
+/**
+ * Tasklet & timer for AP request polling.
+ */
+static struct timer_list ap_poll_timer = TIMER_INITIALIZER(ap_poll_timeout,0,0);
+static DECLARE_TASKLET(ap_tasklet, ap_poll_all, 0);
+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);
+
+/**
+ * Test if ap instructions are available.
+ *
+ * Returns 0 if the ap instructions are installed.
+ */
+static inline int ap_instructions_available(void)
+{
+ register unsigned long reg0 asm ("0") = AP_MKQID(0,0);
+ register unsigned long reg1 asm ("1") = -ENODEV;
+ register unsigned long reg2 asm ("2") = 0UL;
+
+ asm volatile(
+ " .long 0xb2af0000\n" /* PQAP(TAPQ) */
+ "0: la %1,0\n"
+ "1:\n"
+ EX_TABLE(0b, 1b)
+ : "+d" (reg0), "+d" (reg1), "+d" (reg2) : : "cc" );
+ return reg1;
+}
+
+/**
+ * Test adjunct processor queue.
+ * @qid: the ap queue number
+ * @queue_depth: pointer to queue depth value
+ * @device_type: pointer to device type value
+ *
+ * Returns ap queue status structure.
+ */
+static inline struct ap_queue_status
+ap_test_queue(ap_qid_t qid, int *queue_depth, int *device_type)
+{
+ register unsigned long reg0 asm ("0") = qid;
+ register struct ap_queue_status reg1 asm ("1");
+ register unsigned long reg2 asm ("2") = 0UL;
+
+ asm volatile(".long 0xb2af0000" /* PQAP(TAPQ) */
+ : "+d" (reg0), "=d" (reg1), "+d" (reg2) : : "cc");
+ *device_type = (int) (reg2 >> 24);
+ *queue_depth = (int) (reg2 & 0xff);
+ return reg1;
+}
+
+/**
+ * Reset adjunct processor queue.
+ * @qid: the ap queue number
+ *
+ * Returns ap queue status structure.
+ */
+static inline struct ap_queue_status ap_reset_queue(ap_qid_t qid)
+{
+ register unsigned long reg0 asm ("0") = qid | 0x01000000UL;
+ register struct ap_queue_status reg1 asm ("1");
+ register unsigned long reg2 asm ("2") = 0UL;
+
+ asm volatile(
+ ".long 0xb2af0000" /* PQAP(RAPQ) */
+ : "+d" (reg0), "=d" (reg1), "+d" (reg2) : : "cc");
+ return reg1;
+}
+
+/**
+ * Send message to adjunct processor queue.
+ * @qid: the ap queue number
+ * @psmid: the program supplied message identifier
+ * @msg: the message text
+ * @length: the message length
+ *
+ * Returns ap queue status structure.
+ *
+ * Condition code 1 on NQAP can't happen because the L bit is 1.
+ *
+ * Condition code 2 on NQAP also means the send is incomplete,
+ * because a segment boundary was reached. The NQAP is repeated.
+ */
+static inline struct ap_queue_status
+__ap_send(ap_qid_t qid, unsigned long long psmid, void *msg, size_t length)
+{
+ typedef struct { char _[length]; } msgblock;
+ register unsigned long reg0 asm ("0") = qid | 0x40000000UL;
+ register struct ap_queue_status reg1 asm ("1");
+ register unsigned long reg2 asm ("2") = (unsigned long) msg;
+ register unsigned long reg3 asm ("3") = (unsigned long) length;
+ register unsigned long reg4 asm ("4") = (unsigned int) (psmid >> 32);
+ register unsigned long reg5 asm ("5") = (unsigned int) psmid;
+
+ asm volatile (
+ "0: .long 0xb2ad0042\n" /* DQAP */
+ " brc 2,0b"
+ : "+d" (reg0), "=d" (reg1), "+d" (reg2), "+d" (reg3)
+ : "d" (reg4), "d" (reg5), "m" (*(msgblock *) msg)
+ : "cc" );
+ return reg1;
+}
+
+int ap_send(ap_qid_t qid, unsigned long long psmid, void *msg, size_t length)
+{
+ struct ap_queue_status status;
+
+ status = __ap_send(qid, psmid, msg, length);
+ switch (status.response_code) {
+ case AP_RESPONSE_NORMAL:
+ return 0;
+ case AP_RESPONSE_Q_FULL:
+ return -EBUSY;
+ default: /* Device is gone. */
+ return -ENODEV;
+ }
+}
+EXPORT_SYMBOL(ap_send);
+
+/*
+ * Receive message from adjunct processor queue.
+ * @qid: the ap queue number
+ * @psmid: pointer to program supplied message identifier
+ * @msg: the message text
+ * @length: the message length
+ *
+ * Returns ap queue status structure.
+ *
+ * Condition code 1 on DQAP means the receive has taken place
+ * but only partially. The response is incomplete, hence the
+ * DQAP is repeated.
+ *
+ * Condition code 2 on DQAP also means the receive is incomplete,
+ * this time because a segment boundary was reached. Again, the
+ * DQAP is repeated.
+ *
+ * Note that gpr2 is used by the DQAP instruction to keep track of
+ * any 'residual' length, in case the instruction gets interrupted.
+ * Hence it gets zeroed before the instruction.
+ */
+static inline struct ap_queue_status
+__ap_recv(ap_qid_t qid, unsigned long long *psmid, void *msg, size_t length)
+{
+ typedef struct { char _[length]; } msgblock;
+ register unsigned long reg0 asm("0") = qid | 0x80000000UL;
+ register struct ap_queue_status reg1 asm ("1");
+ register unsigned long reg2 asm("2") = 0UL;
+ register unsigned long reg4 asm("4") = (unsigned long) msg;
+ register unsigned long reg5 asm("5") = (unsigned long) length;
+ register unsigned long reg6 asm("6") = 0UL;
+ register unsigned long reg7 asm("7") = 0UL;
+
+
+ asm volatile(
+ "0: .long 0xb2ae0064\n"
+ " brc 6,0b\n"
+ : "+d" (reg0), "=d" (reg1), "+d" (reg2),
+ "+d" (reg4), "+d" (reg5), "+d" (reg6), "+d" (reg7),
+ "=m" (*(msgblock *) msg) : : "cc" );
+ *psmid = (((unsigned long long) reg6) << 32) + reg7;
+ return reg1;
+}
+
+int ap_recv(ap_qid_t qid, unsigned long long *psmid, void *msg, size_t length)
+{
+ struct ap_queue_status status;
+
+ status = __ap_recv(qid, psmid, msg, length);
+ switch (status.response_code) {
+ case AP_RESPONSE_NORMAL:
+ return 0;
+ case AP_RESPONSE_NO_PENDING_REPLY:
+ if (status.queue_empty)
+ return -ENOENT;
+ return -EBUSY;
+ default:
+ return -ENODEV;
+ }
+}
+EXPORT_SYMBOL(ap_recv);
+
+/**
+ * Check if an AP queue is available. The test is repeated for
+ * AP_MAX_RESET times.
+ * @qid: the ap queue number
+ * @queue_depth: pointer to queue depth value
+ * @device_type: pointer to device type value
+ */
+static int ap_query_queue(ap_qid_t qid, int *queue_depth, int *device_type)
+{
+ struct ap_queue_status status;
+ int t_depth, t_device_type, rc, i;
+
+ rc = -EBUSY;
+ for (i = 0; i < AP_MAX_RESET; i++) {
+ status = ap_test_queue(qid, &t_depth, &t_device_type);
+ switch (status.response_code) {
+ case AP_RESPONSE_NORMAL:
+ *queue_depth = t_depth + 1;
+ *device_type = t_device_type;
+ rc = 0;
+ break;
+ case AP_RESPONSE_Q_NOT_AVAIL:
+ rc = -ENODEV;
+ break;
+ case AP_RESPONSE_RESET_IN_PROGRESS:
+ break;
+ case AP_RESPONSE_DECONFIGURED:
+ rc = -ENODEV;
+ break;
+ case AP_RESPONSE_CHECKSTOPPED:
+ rc = -ENODEV;
+ break;
+ case AP_RESPONSE_BUSY:
+ break;
+ default:
+ BUG();
+ }
+ if (rc != -EBUSY)
+ break;
+ if (i < AP_MAX_RESET - 1)
+ udelay(5);
+ }
+ return rc;
+}
+
+/**
+ * Reset an AP queue and wait for it to become available again.
+ * @qid: the ap queue number
+ */
+static int ap_init_queue(ap_qid_t qid)
+{
+ struct ap_queue_status status;
+ int rc, dummy, i;
+
+ rc = -ENODEV;
+ status = ap_reset_queue(qid);
+ for (i = 0; i < AP_MAX_RESET; i++) {
+ switch (status.response_code) {
+ case AP_RESPONSE_NORMAL:
+ if (status.queue_empty)
+ rc = 0;
+ break;
+ case AP_RESPONSE_Q_NOT_AVAIL:
+ case AP_RESPONSE_DECONFIGURED:
+ case AP_RESPONSE_CHECKSTOPPED:
+ i = AP_MAX_RESET; /* return with -ENODEV */
+ break;
+ case AP_RESPONSE_RESET_IN_PROGRESS:
+ case AP_RESPONSE_BUSY:
+ default:
+ break;
+ }
+ if (rc != -ENODEV)
+ break;
+ if (i < AP_MAX_RESET - 1) {
+ udelay(5);
+ status = ap_test_queue(qid, &dummy, &dummy);
+ }
+ }
+ return rc;
+}
+
+/**
+ * AP device related attributes.
+ */
+static ssize_t ap_hwtype_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+ return snprintf(buf, PAGE_SIZE, "%d\n", ap_dev->device_type);
+}
+static DEVICE_ATTR(hwtype, 0444, ap_hwtype_show, NULL);
+
+static ssize_t ap_depth_show(struct device *dev, struct device_attribute *attr,
+ char *buf)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+ return snprintf(buf, PAGE_SIZE, "%d\n", ap_dev->queue_depth);
+}
+static DEVICE_ATTR(depth, 0444, ap_depth_show, NULL);
+
+static ssize_t ap_request_count_show(struct device *dev,
+ struct device_attribute *attr,
+ char *buf)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+ int rc;
+
+ spin_lock_bh(&ap_dev->lock);
+ rc = snprintf(buf, PAGE_SIZE, "%d\n", ap_dev->total_request_count);
+ spin_unlock_bh(&ap_dev->lock);
+ return rc;
+}
+
+static DEVICE_ATTR(request_count, 0444, ap_request_count_show, NULL);
+
+static ssize_t ap_modalias_show(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ return sprintf(buf, "ap:t%02X", to_ap_dev(dev)->device_type);
+}
+
+static DEVICE_ATTR(modalias, 0444, ap_modalias_show, NULL);
+
+static struct attribute *ap_dev_attrs[] = {
+ &dev_attr_hwtype.attr,
+ &dev_attr_depth.attr,
+ &dev_attr_request_count.attr,
+ &dev_attr_modalias.attr,
+ NULL
+};
+static struct attribute_group ap_dev_attr_group = {
+ .attrs = ap_dev_attrs
+};
+
+/**
+ * AP bus driver registration/unregistration.
+ */
+static int ap_bus_match(struct device *dev, struct device_driver *drv)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+ struct ap_driver *ap_drv = to_ap_drv(drv);
+ struct ap_device_id *id;
+
+ /**
+ * Compare device type of the device with the list of
+ * supported types of the device_driver.
+ */
+ for (id = ap_drv->ids; id->match_flags; id++) {
+ if ((id->match_flags & AP_DEVICE_ID_MATCH_DEVICE_TYPE) &&
+ (id->dev_type != ap_dev->device_type))
+ continue;
+ return 1;
+ }
+ return 0;
+}
+
+/**
+ * uevent function for AP devices. It sets up a single environment
+ * variable DEV_TYPE which contains the hardware device type.
+ */
+static int ap_uevent (struct device *dev, char **envp, int num_envp,
+ char *buffer, int buffer_size)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+ int length;
+
+ if (!ap_dev)
+ return -ENODEV;
+
+ /* Set up DEV_TYPE environment variable. */
+ envp[0] = buffer;
+ length = scnprintf(buffer, buffer_size, "DEV_TYPE=%04X",
+ ap_dev->device_type);
+ if (buffer_size - length <= 0)
+ return -ENOMEM;
+ envp[1] = 0;
+ return 0;
+}
+
+static struct bus_type ap_bus_type = {
+ .name = "ap",
+ .match = &ap_bus_match,
+ .uevent = &ap_uevent,
+};
+
+static int ap_device_probe(struct device *dev)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+ struct ap_driver *ap_drv = to_ap_drv(dev->driver);
+ int rc;
+
+ ap_dev->drv = ap_drv;
+ rc = ap_drv->probe ? ap_drv->probe(ap_dev) : -ENODEV;
+ if (rc)
+ ap_dev->unregistered = 1;
+ return rc;
+}
+
+/**
+ * Flush all requests from the request/pending queue of an AP device.
+ * @ap_dev: pointer to the AP device.
+ */
+static inline void __ap_flush_queue(struct ap_device *ap_dev)
+{
+ struct ap_message *ap_msg, *next;
+
+ list_for_each_entry_safe(ap_msg, next, &ap_dev->pendingq, list) {
+ list_del_init(&ap_msg->list);
+ ap_dev->pendingq_count--;
+ ap_dev->drv->receive(ap_dev, ap_msg, ERR_PTR(-ENODEV));
+ }
+ list_for_each_entry_safe(ap_msg, next, &ap_dev->requestq, list) {
+ list_del_init(&ap_msg->list);
+ ap_dev->requestq_count--;
+ ap_dev->drv->receive(ap_dev, ap_msg, ERR_PTR(-ENODEV));
+ }
+}
+
+void ap_flush_queue(struct ap_device *ap_dev)
+{
+ spin_lock_bh(&ap_dev->lock);
+ __ap_flush_queue(ap_dev);
+ spin_unlock_bh(&ap_dev->lock);
+}
+EXPORT_SYMBOL(ap_flush_queue);
+
+static int ap_device_remove(struct device *dev)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+ struct ap_driver *ap_drv = ap_dev->drv;
+
+ spin_lock_bh(&ap_dev->lock);
+ __ap_flush_queue(ap_dev);
+ /**
+ * set ->unregistered to 1 while holding the lock. This prevents
+ * new messages to be put on the queue from now on.
+ */
+ ap_dev->unregistered = 1;
+ spin_unlock_bh(&ap_dev->lock);
+ if (ap_drv->remove)
+ ap_drv->remove(ap_dev);
+ return 0;
+}
+
+int ap_driver_register(struct ap_driver *ap_drv, struct module *owner,
+ char *name)
+{
+ struct device_driver *drv = &ap_drv->driver;
+
+ drv->bus = &ap_bus_type;
+ drv->probe = ap_device_probe;
+ drv->remove = ap_device_remove;
+ drv->owner = owner;
+ drv->name = name;
+ return driver_register(drv);
+}
+EXPORT_SYMBOL(ap_driver_register);
+
+void ap_driver_unregister(struct ap_driver *ap_drv)
+{
+ driver_unregister(&ap_drv->driver);
+}
+EXPORT_SYMBOL(ap_driver_unregister);
+
+/**
+ * AP bus attributes.
+ */
+static ssize_t ap_domain_show(struct bus_type *bus, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", ap_domain_index);
+}
+
+static BUS_ATTR(ap_domain, 0444, ap_domain_show, NULL);
+
+static ssize_t ap_config_time_show(struct bus_type *bus, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", ap_config_time);
+}
+
+static ssize_t ap_config_time_store(struct bus_type *bus,
+ const char *buf, size_t count)
+{
+ int time;
+
+ if (sscanf(buf, "%d\n", &time) != 1 || time < 5 || time > 120)
+ return -EINVAL;
+ ap_config_time = time;
+ if (!timer_pending(&ap_config_timer) ||
+ !mod_timer(&ap_config_timer, jiffies + ap_config_time * HZ)) {
+ ap_config_timer.expires = jiffies + ap_config_time * HZ;
+ add_timer(&ap_config_timer);
+ }
+ return count;
+}
+
+static BUS_ATTR(config_time, 0644, ap_config_time_show, ap_config_time_store);
+
+static ssize_t ap_poll_thread_show(struct bus_type *bus, char *buf)
+{
+ return snprintf(buf, PAGE_SIZE, "%d\n", ap_poll_kthread ? 1 : 0);
+}
+
+static ssize_t ap_poll_thread_store(struct bus_type *bus,
+ const char *buf, size_t count)
+{
+ int flag, rc;
+
+ if (sscanf(buf, "%d\n", &flag) != 1)
+ return -EINVAL;
+ if (flag) {
+ rc = ap_poll_thread_start();
+ if (rc)
+ return rc;
+ }
+ else
+ ap_poll_thread_stop();
+ return count;
+}
+
+static BUS_ATTR(poll_thread, 0644, ap_poll_thread_show, ap_poll_thread_store);
+
+static struct bus_attribute *const ap_bus_attrs[] = {
+ &bus_attr_ap_domain,
+ &bus_attr_config_time,
+ &bus_attr_poll_thread,
+ NULL
+};
+
+/**
+ * Pick one of the 16 ap domains.
+ */
+static inline int ap_select_domain(void)
+{
+ int queue_depth, device_type, count, max_count, best_domain;
+ int rc, i, j;
+
+ /**
+ * We want to use a single domain. Either the one specified with
+ * the "domain=" parameter or the domain with the maximum number
+ * of devices.
+ */
+ if (ap_domain_index >= 0 && ap_domain_index < AP_DOMAINS)
+ /* Domain has already been selected. */
+ return 0;
+ best_domain = -1;
+ max_count = 0;
+ for (i = 0; i < AP_DOMAINS; i++) {
+ count = 0;
+ for (j = 0; j < AP_DEVICES; j++) {
+ ap_qid_t qid = AP_MKQID(j, i);
+ rc = ap_query_queue(qid, &queue_depth, &device_type);
+ if (rc)
+ continue;
+ count++;
+ }
+ if (count > max_count) {
+ max_count = count;
+ best_domain = i;
+ }
+ }
+ if (best_domain >= 0){
+ ap_domain_index = best_domain;
+ return 0;
+ }
+ return -ENODEV;
+}
+
+/**
+ * Find the device type if query queue returned a device type of 0.
+ * @ap_dev: pointer to the AP device.
+ */
+static int ap_probe_device_type(struct ap_device *ap_dev)
+{
+ static unsigned char msg[] = {
+ 0x00,0x06,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x58,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x01,0x00,0x43,0x43,0x41,0x2d,0x41,0x50,
+ 0x50,0x4c,0x20,0x20,0x20,0x01,0x01,0x01,
+ 0x00,0x00,0x00,0x00,0x50,0x4b,0x00,0x00,
+ 0x00,0x00,0x01,0x1c,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x05,0xb8,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x70,0x00,0x41,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x54,0x32,0x01,0x00,0xa0,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0xb8,0x05,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x0a,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,
+ 0x00,0x00,0x00,0x00,0x00,0x00,0x08,0x00,
+ 0x49,0x43,0x53,0x46,0x20,0x20,0x20,0x20,
+ 0x50,0x4b,0x0a,0x00,0x50,0x4b,0x43,0x53,
+ 0x2d,0x31,0x2e,0x32,0x37,0x00,0x11,0x22,
+ 0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00,
+ 0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,
+ 0x99,0x00,0x11,0x22,0x33,0x44,0x55,0x66,
+ 0x77,0x88,0x99,0x00,0x11,0x22,0x33,0x44,
+ 0x55,0x66,0x77,0x88,0x99,0x00,0x11,0x22,
+ 0x33,0x44,0x55,0x66,0x77,0x88,0x99,0x00,
+ 0x11,0x22,0x33,0x5d,0x00,0x5b,0x00,0x77,
+ 0x88,0x1e,0x00,0x00,0x57,0x00,0x00,0x00,
+ 0x00,0x04,0x00,0x00,0x4f,0x00,0x00,0x00,
+ 0x03,0x02,0x00,0x00,0x40,0x01,0x00,0x01,
+ 0xce,0x02,0x68,0x2d,0x5f,0xa9,0xde,0x0c,
+ 0xf6,0xd2,0x7b,0x58,0x4b,0xf9,0x28,0x68,
+ 0x3d,0xb4,0xf4,0xef,0x78,0xd5,0xbe,0x66,
+ 0x63,0x42,0xef,0xf8,0xfd,0xa4,0xf8,0xb0,
+ 0x8e,0x29,0xc2,0xc9,0x2e,0xd8,0x45,0xb8,
+ 0x53,0x8c,0x6f,0x4e,0x72,0x8f,0x6c,0x04,
+ 0x9c,0x88,0xfc,0x1e,0xc5,0x83,0x55,0x57,
+ 0xf7,0xdd,0xfd,0x4f,0x11,0x36,0x95,0x5d,
+ };
+ struct ap_queue_status status;
+ unsigned long long psmid;
+ char *reply;
+ int rc, i;
+
+ reply = (void *) get_zeroed_page(GFP_KERNEL);
+ if (!reply) {
+ rc = -ENOMEM;
+ goto out;
+ }
+
+ status = __ap_send(ap_dev->qid, 0x0102030405060708ULL,
+ msg, sizeof(msg));
+ if (status.response_code != AP_RESPONSE_NORMAL) {
+ rc = -ENODEV;
+ goto out_free;
+ }
+
+ /* Wait for the test message to complete. */
+ for (i = 0; i < 6; i++) {
+ mdelay(300);
+ status = __ap_recv(ap_dev->qid, &psmid, reply, 4096);
+ if (status.response_code == AP_RESPONSE_NORMAL &&
+ psmid == 0x0102030405060708ULL)
+ break;
+ }
+ if (i < 6) {
+ /* Got an answer. */
+ if (reply[0] == 0x00 && reply[1] == 0x86)
+ ap_dev->device_type = AP_DEVICE_TYPE_PCICC;
+ else
+ ap_dev->device_type = AP_DEVICE_TYPE_PCICA;
+ rc = 0;
+ } else
+ rc = -ENODEV;
+
+out_free:
+ free_page((unsigned long) reply);
+out:
+ return rc;
+}
+
+/**
+ * Scan the ap bus for new devices.
+ */
+static int __ap_scan_bus(struct device *dev, void *data)
+{
+ return to_ap_dev(dev)->qid == (ap_qid_t)(unsigned long) data;
+}
+
+static void ap_device_release(struct device *dev)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+
+ kfree(ap_dev);
+}
+
+static void ap_scan_bus(void *data)
+{
+ struct ap_device *ap_dev;
+ struct device *dev;
+ ap_qid_t qid;
+ int queue_depth, device_type;
+ int rc, i;
+
+ if (ap_select_domain() != 0)
+ return;
+ for (i = 0; i < AP_DEVICES; i++) {
+ qid = AP_MKQID(i, ap_domain_index);
+ dev = bus_find_device(&ap_bus_type, NULL,
+ (void *)(unsigned long)qid,
+ __ap_scan_bus);
+ if (dev) {
+ put_device(dev);
+ continue;
+ }
+ rc = ap_query_queue(qid, &queue_depth, &device_type);
+ if (rc)
+ continue;
+ rc = ap_init_queue(qid);
+ if (rc)
+ continue;
+ ap_dev = kzalloc(sizeof(*ap_dev), GFP_KERNEL);
+ if (!ap_dev)
+ break;
+ ap_dev->qid = qid;
+ ap_dev->queue_depth = queue_depth;
+ spin_lock_init(&ap_dev->lock);
+ INIT_LIST_HEAD(&ap_dev->pendingq);
+ INIT_LIST_HEAD(&ap_dev->requestq);
+ if (device_type == 0)
+ ap_probe_device_type(ap_dev);
+ else
+ ap_dev->device_type = device_type;
+
+ ap_dev->device.bus = &ap_bus_type;
+ ap_dev->device.parent = ap_root_device;
+ snprintf(ap_dev->device.bus_id, BUS_ID_SIZE, "card%02x",
+ AP_QID_DEVICE(ap_dev->qid));
+ ap_dev->device.release = ap_device_release;
+ rc = device_register(&ap_dev->device);
+ if (rc) {
+ kfree(ap_dev);
+ continue;
+ }
+ /* Add device attributes. */
+ rc = sysfs_create_group(&ap_dev->device.kobj,
+ &ap_dev_attr_group);
+ if (rc)
+ device_unregister(&ap_dev->device);
+ }
+}
+
+static void
+ap_config_timeout(unsigned long ptr)
+{
+ queue_work(ap_work_queue, &ap_config_work);
+ ap_config_timer.expires = jiffies + ap_config_time * HZ;
+ add_timer(&ap_config_timer);
+}
+
+/**
+ * Set up the timer to run the poll tasklet
+ */
+static inline void ap_schedule_poll_timer(void)
+{
+ if (timer_pending(&ap_poll_timer))
+ return;
+ mod_timer(&ap_poll_timer, jiffies + AP_POLL_TIME);
+}
+
+/**
+ * Receive pending reply messages from an AP device.
+ * @ap_dev: pointer to the AP device
+ * @flags: pointer to control flags, bit 2^0 is set if another poll is
+ * required, bit 2^1 is set if the poll timer needs to get armed
+ * Returns 0 if the device is still present, -ENODEV if not.
+ */
+static inline int ap_poll_read(struct ap_device *ap_dev, unsigned long *flags)
+{
+ struct ap_queue_status status;
+ struct ap_message *ap_msg;
+
+ if (ap_dev->queue_count <= 0)
+ return 0;
+ status = __ap_recv(ap_dev->qid, &ap_dev->reply->psmid,
+ ap_dev->reply->message, ap_dev->reply->length);
+ switch (status.response_code) {
+ case AP_RESPONSE_NORMAL:
+ atomic_dec(&ap_poll_requests);
+ ap_dev->queue_count--;
+ list_for_each_entry(ap_msg, &ap_dev->pendingq, list) {
+ if (ap_msg->psmid != ap_dev->reply->psmid)
+ continue;
+ list_del_init(&ap_msg->list);
+ ap_dev->pendingq_count--;
+ ap_dev->drv->receive(ap_dev, ap_msg, ap_dev->reply);
+ break;
+ }
+ if (ap_dev->queue_count > 0)
+ *flags |= 1;
+ break;
+ case AP_RESPONSE_NO_PENDING_REPLY:
+ if (status.queue_empty) {
+ /* The card shouldn't forget requests but who knows. */
+ ap_dev->queue_count = 0;
+ list_splice_init(&ap_dev->pendingq, &ap_dev->requestq);
+ ap_dev->requestq_count += ap_dev->pendingq_count;
+ ap_dev->pendingq_count = 0;
+ } else
+ *flags |= 2;
+ break;
+ default:
+ return -ENODEV;
+ }
+ return 0;
+}
+
+/**
+ * Send messages from the request queue to an AP device.
+ * @ap_dev: pointer to the AP device
+ * @flags: pointer to control flags, bit 2^0 is set if another poll is
+ * required, bit 2^1 is set if the poll timer needs to get armed
+ * Returns 0 if the device is still present, -ENODEV if not.
+ */
+static inline int ap_poll_write(struct ap_device *ap_dev, unsigned long *flags)
+{
+ struct ap_queue_status status;
+ struct ap_message *ap_msg;
+
+ if (ap_dev->requestq_count <= 0 ||
+ ap_dev->queue_count >= ap_dev->queue_depth)
+ return 0;
+ /* Start the next request on the queue. */
+ ap_msg = list_entry(ap_dev->requestq.next, struct ap_message, list);
+ status = __ap_send(ap_dev->qid, ap_msg->psmid,
+ ap_msg->message, ap_msg->length);
+ switch (status.response_code) {
+ case AP_RESPONSE_NORMAL:
+ atomic_inc(&ap_poll_requests);
+ ap_dev->queue_count++;
+ list_move_tail(&ap_msg->list, &ap_dev->pendingq);
+ ap_dev->requestq_count--;
+ ap_dev->pendingq_count++;
+ if (ap_dev->queue_count < ap_dev->queue_depth &&
+ ap_dev->requestq_count > 0)
+ *flags |= 1;
+ *flags |= 2;
+ break;
+ case AP_RESPONSE_Q_FULL:
+ *flags |= 2;
+ break;
+ case AP_RESPONSE_MESSAGE_TOO_BIG:
+ return -EINVAL;
+ default:
+ return -ENODEV;
+ }
+ return 0;
+}
+
+/**
+ * Poll AP device for pending replies and send new messages. If either
+ * ap_poll_read or ap_poll_write returns -ENODEV unregister the device.
+ * @ap_dev: pointer to the bus device
+ * @flags: pointer to control flags, bit 2^0 is set if another poll is
+ * required, bit 2^1 is set if the poll timer needs to get armed
+ * Returns 0.
+ */
+static inline int ap_poll_queue(struct ap_device *ap_dev, unsigned long *flags)
+{
+ int rc;
+
+ rc = ap_poll_read(ap_dev, flags);
+ if (rc)
+ return rc;
+ return ap_poll_write(ap_dev, flags);
+}
+
+/**
+ * Queue a message to a device.
+ * @ap_dev: pointer to the AP device
+ * @ap_msg: the message to be queued
+ */
+static int __ap_queue_message(struct ap_device *ap_dev, struct ap_message *ap_msg)
+{
+ struct ap_queue_status status;
+
+ if (list_empty(&ap_dev->requestq) &&
+ ap_dev->queue_count < ap_dev->queue_depth) {
+ status = __ap_send(ap_dev->qid, ap_msg->psmid,
+ ap_msg->message, ap_msg->length);
+ switch (status.response_code) {
+ case AP_RESPONSE_NORMAL:
+ list_add_tail(&ap_msg->list, &ap_dev->pendingq);
+ atomic_inc(&ap_poll_requests);
+ ap_dev->pendingq_count++;
+ ap_dev->queue_count++;
+ ap_dev->total_request_count++;
+ break;
+ case AP_RESPONSE_Q_FULL:
+ list_add_tail(&ap_msg->list, &ap_dev->requestq);
+ ap_dev->requestq_count++;
+ ap_dev->total_request_count++;
+ return -EBUSY;
+ case AP_RESPONSE_MESSAGE_TOO_BIG:
+ ap_dev->drv->receive(ap_dev, ap_msg, ERR_PTR(-EINVAL));
+ return -EINVAL;
+ default: /* Device is gone. */
+ ap_dev->drv->receive(ap_dev, ap_msg, ERR_PTR(-ENODEV));
+ return -ENODEV;
+ }
+ } else {
+ list_add_tail(&ap_msg->list, &ap_dev->requestq);
+ ap_dev->requestq_count++;
+ ap_dev->total_request_count++;
+ return -EBUSY;
+ }
+ ap_schedule_poll_timer();
+ return 0;
+}
+
+void ap_queue_message(struct ap_device *ap_dev, struct ap_message *ap_msg)
+{
+ unsigned long flags;
+ int rc;
+
+ spin_lock_bh(&ap_dev->lock);
+ if (!ap_dev->unregistered) {
+ /* Make room on the queue by polling for finished requests. */
+ rc = ap_poll_queue(ap_dev, &flags);
+ if (!rc)
+ rc = __ap_queue_message(ap_dev, ap_msg);
+ if (!rc)
+ wake_up(&ap_poll_wait);
+ } else {
+ ap_dev->drv->receive(ap_dev, ap_msg, ERR_PTR(-ENODEV));
+ rc = 0;
+ }
+ spin_unlock_bh(&ap_dev->lock);
+ if (rc == -ENODEV)
+ device_unregister(&ap_dev->device);
+}
+EXPORT_SYMBOL(ap_queue_message);
+
+/**
+ * Cancel a crypto request. This is done by removing the request
+ * from the devive pendingq or requestq queue. Note that the
+ * request stays on the AP queue. When it finishes the message
+ * reply will be discarded because the psmid can't be found.
+ * @ap_dev: AP device that has the message queued
+ * @ap_msg: the message that is to be removed
+ */
+void ap_cancel_message(struct ap_device *ap_dev, struct ap_message *ap_msg)
+{
+ struct ap_message *tmp;
+
+ spin_lock_bh(&ap_dev->lock);
+ if (!list_empty(&ap_msg->list)) {
+ list_for_each_entry(tmp, &ap_dev->pendingq, list)
+ if (tmp->psmid == ap_msg->psmid) {
+ ap_dev->pendingq_count--;
+ goto found;
+ }
+ ap_dev->requestq_count--;
+ found:
+ list_del_init(&ap_msg->list);
+ }
+ spin_unlock_bh(&ap_dev->lock);
+}
+EXPORT_SYMBOL(ap_cancel_message);
+
+/**
+ * AP receive polling for finished AP requests
+ */
+static void ap_poll_timeout(unsigned long unused)
+{
+ tasklet_schedule(&ap_tasklet);
+}
+
+/**
+ * Poll all AP devices on the bus in a round robin fashion. Continue
+ * polling until bit 2^0 of the control flags is not set. If bit 2^1
+ * of the control flags has been set arm the poll timer.
+ */
+static int __ap_poll_all(struct device *dev, void *data)
+{
+ struct ap_device *ap_dev = to_ap_dev(dev);
+ int rc;
+
+ spin_lock(&ap_dev->lock);
+ if (!ap_dev->unregistered) {
+ rc = ap_poll_queue(to_ap_dev(dev), (unsigned long *) data);
+ } else
+ rc = 0;
+ spin_unlock(&ap_dev->lock);
+ if (rc)
+ device_unregister(&ap_dev->device);
+ return 0;
+}
+
+static void ap_poll_all(unsigned long dummy)
+{
+ unsigned long flags;
+
+ do {
+ flags = 0;
+ bus_for_each_dev(&ap_bus_type, NULL, &flags, __ap_poll_all);
+ } while (flags & 1);
+ if (flags & 2)
+ ap_schedule_poll_timer();
+}
+
+/**
+ * AP bus poll thread. The purpose of this thread is to poll for
+ * finished requests in a loop if there is a "free" cpu - that is
+ * a cpu that doesn't have anything better to do. The polling stops
+ * as soon as there is another task or if all messages have been
+ * delivered.
+ */
+static int ap_poll_thread(void *data)
+{
+ DECLARE_WAITQUEUE(wait, current);
+ unsigned long flags;
+ int requests;
+
+ set_user_nice(current, -20);
+ while (1) {
+ if (need_resched()) {
+ schedule();
+ continue;
+ }
+ add_wait_queue(&ap_poll_wait, &wait);
+ set_current_state(TASK_INTERRUPTIBLE);
+ if (kthread_should_stop())
+ break;
+ requests = atomic_read(&ap_poll_requests);
+ if (requests <= 0)
+ schedule();
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&ap_poll_wait, &wait);
+
+ local_bh_disable();
+ flags = 0;
+ bus_for_each_dev(&ap_bus_type, NULL, &flags, __ap_poll_all);
+ local_bh_enable();
+ }
+ set_current_state(TASK_RUNNING);
+ remove_wait_queue(&ap_poll_wait, &wait);
+ return 0;
+}
+
+static int ap_poll_thread_start(void)
+{
+ int rc;
+
+ mutex_lock(&ap_poll_thread_mutex);
+ if (!ap_poll_kthread) {
+ ap_poll_kthread = kthread_run(ap_poll_thread, NULL, "appoll");
+ rc = IS_ERR(ap_poll_kthread) ? PTR_ERR(ap_poll_kthread) : 0;
+ if (rc)
+ ap_poll_kthread = NULL;
+ }
+ else
+ rc = 0;
+ mutex_unlock(&ap_poll_thread_mutex);
+ return rc;
+}
+
+static void ap_poll_thread_stop(void)
+{
+ mutex_lock(&ap_poll_thread_mutex);
+ if (ap_poll_kthread) {
+ kthread_stop(ap_poll_kthread);
+ ap_poll_kthread = NULL;
+ }
+ mutex_unlock(&ap_poll_thread_mutex);
+}
+
+/**
+ * The module initialization code.
+ */
+int __init ap_module_init(void)
+{
+ int rc, i;
+
+ if (ap_domain_index < -1 || ap_domain_index >= AP_DOMAINS) {
+ printk(KERN_WARNING "Invalid param: domain = %d. "
+ " Not loading.\n", ap_domain_index);
+ return -EINVAL;
+ }
+ if (ap_instructions_available() != 0) {
+ printk(KERN_WARNING "AP instructions not installed.\n");
+ return -ENODEV;
+ }
+
+ /* Create /sys/bus/ap. */
+ rc = bus_register(&ap_bus_type);
+ if (rc)
+ goto out;
+ for (i = 0; ap_bus_attrs[i]; i++) {
+ rc = bus_create_file(&ap_bus_type, ap_bus_attrs[i]);
+ if (rc)
+ goto out_bus;
+ }
+
+ /* Create /sys/devices/ap. */
+ ap_root_device = s390_root_dev_register("ap");
+ rc = IS_ERR(ap_root_device) ? PTR_ERR(ap_root_device) : 0;
+ if (rc)
+ goto out_bus;
+
+ ap_work_queue = create_singlethread_workqueue("kapwork");
+ if (!ap_work_queue) {
+ rc = -ENOMEM;
+ goto out_root;
+ }
+
+ if (ap_select_domain() == 0)
+ ap_scan_bus(NULL);
+
+ /* Setup the ap bus rescan timer. */
+ init_timer(&ap_config_timer);
+ ap_config_timer.function = ap_config_timeout;
+ ap_config_timer.data = 0;
+ ap_config_timer.expires = jiffies + ap_config_time * HZ;
+ add_timer(&ap_config_timer);
+
+ /* Start the low priority AP bus poll thread. */
+ if (ap_thread_flag) {
+ rc = ap_poll_thread_start();
+ if (rc)
+ goto out_work;
+ }
+
+ return 0;
+
+out_work:
+ del_timer_sync(&ap_config_timer);
+ del_timer_sync(&ap_poll_timer);
+ destroy_workqueue(ap_work_queue);
+out_root:
+ s390_root_dev_unregister(ap_root_device);
+out_bus:
+ while (i--)
+ bus_remove_file(&ap_bus_type, ap_bus_attrs[i]);
+ bus_unregister(&ap_bus_type);
+out:
+ return rc;
+}
+
+static int __ap_match_all(struct device *dev, void *data)
+{
+ return 1;
+}
+
+/**
+ * The module termination code
+ */
+void ap_module_exit(void)
+{
+ int i;
+ struct device *dev;
+
+ ap_poll_thread_stop();
+ del_timer_sync(&ap_config_timer);
+ del_timer_sync(&ap_poll_timer);
+ destroy_workqueue(ap_work_queue);
+ s390_root_dev_unregister(ap_root_device);
+ while ((dev = bus_find_device(&ap_bus_type, NULL, NULL,
+ __ap_match_all)))
+ {
+ device_unregister(dev);
+ put_device(dev);
+ }
+ for (i = 0; ap_bus_attrs[i]; i++)
+ bus_remove_file(&ap_bus_type, ap_bus_attrs[i]);
+ bus_unregister(&ap_bus_type);
+}
+
+#ifndef CONFIG_ZCRYPT_MONOLITHIC
+module_init(ap_module_init);
+module_exit(ap_module_exit);
+#endif
--- /dev/null
+/*
+ * linux/drivers/s390/crypto/ap_bus.h
+ *
+ * Copyright (C) 2006 IBM Corporation
+ * Author(s): Cornelia Huck <cornelia.huck@de.ibm.com>
+ * Martin Schwidefsky <schwidefsky@de.ibm.com>
+ * Ralph Wuerthner <rwuerthn@de.ibm.com>
+ *
+ * Adjunct processor bus header file.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
+ */
+
+#ifndef _AP_BUS_H_
+#define _AP_BUS_H_
+
+#include <linux/device.h>
+#include <linux/mod_devicetable.h>
+#include <linux/types.h>
+
+#define AP_DEVICES 64 /* Number of AP devices. */
+#define AP_DOMAINS 16 /* Number of AP domains. */
+#define AP_MAX_RESET 90 /* Maximum number of resets. */
+#define AP_CONFIG_TIME 30 /* Time in seconds between AP bus rescans. */
+#define AP_POLL_TIME 1 /* Time in ticks between receive polls. */
+
+extern int ap_domain_index;
+
+/**
+ * The ap_qid_t identifier of an ap queue. It contains a
+ * 6 bit device index and a 4 bit queue index (domain).
+ */
+typedef unsigned int ap_qid_t;
+
+#define AP_MKQID(_device,_queue) (((_device) & 63) << 8 | ((_queue) & 15))
+#define AP_QID_DEVICE(_qid) (((_qid) >> 8) & 63)
+#define AP_QID_QUEUE(_qid) ((_qid) & 15)
+
+/**
+ * The ap queue status word is returned by all three AP functions
+ * (PQAP, NQAP and DQAP). There's a set of flags in the first
+ * byte, followed by a 1 byte response code.
+ */
+struct ap_queue_status {
+ unsigned int queue_empty : 1;
+ unsigned int replies_waiting : 1;
+ unsigned int queue_full : 1;
+ unsigned int pad1 : 5;
+ unsigned int response_code : 8;
+ unsigned int pad2 : 16;
+};
+
+#define AP_RESPONSE_NORMAL 0x00
+#define AP_RESPONSE_Q_NOT_AVAIL 0x01
+#define AP_RESPONSE_RESET_IN_PROGRESS 0x02
+#define AP_RESPONSE_DECONFIGURED 0x03
+#define AP_RESPONSE_CHECKSTOPPED 0x04
+#define AP_RESPONSE_BUSY 0x05
+#define AP_RESPONSE_Q_FULL 0x10
+#define AP_RESPONSE_NO_PENDING_REPLY 0x10
+#define AP_RESPONSE_INDEX_TOO_BIG 0x11
+#define AP_RESPONSE_NO_FIRST_PART 0x13
+#define AP_RESPONSE_MESSAGE_TOO_BIG 0x15
+
+/**
+ * Known device types
+ */
+#define AP_DEVICE_TYPE_PCICC 3
+#define AP_DEVICE_TYPE_PCICA 4
+#define AP_DEVICE_TYPE_PCIXCC 5
+#define AP_DEVICE_TYPE_CEX2A 6
+#define AP_DEVICE_TYPE_CEX2C 7
+
+struct ap_device;
+struct ap_message;
+
+struct ap_driver {
+ struct device_driver driver;
+ struct ap_device_id *ids;
+
+ int (*probe)(struct ap_device *);
+ void (*remove)(struct ap_device *);
+ /* receive is called from tasklet context */
+ void (*receive)(struct ap_device *, struct ap_message *,
+ struct ap_message *);
+};
+
+#define to_ap_drv(x) container_of((x), struct ap_driver, driver)
+
+int ap_driver_register(struct ap_driver *, struct module *, char *);
+void ap_driver_unregister(struct ap_driver *);
+
+struct ap_device {
+ struct device device;
+ struct ap_driver *drv; /* Pointer to AP device driver. */
+ spinlock_t lock; /* Per device lock. */
+
+ ap_qid_t qid; /* AP queue id. */
+ int queue_depth; /* AP queue depth.*/
+ int device_type; /* AP device type. */
+ int unregistered; /* marks AP device as unregistered */
+
+ int queue_count; /* # messages currently on AP queue. */
+
+ struct list_head pendingq; /* List of message sent to AP queue. */
+ int pendingq_count; /* # requests on pendingq list. */
+ struct list_head requestq; /* List of message yet to be sent. */
+ int requestq_count; /* # requests on requestq list. */
+ int total_request_count; /* # requests ever for this AP device. */
+
+ struct ap_message *reply; /* Per device reply message. */
+
+ void *private; /* ap driver private pointer. */
+};
+
+#define to_ap_dev(x) container_of((x), struct ap_device, device)
+
+struct ap_message {
+ struct list_head list; /* Request queueing. */
+ unsigned long long psmid; /* Message id. */
+ void *message; /* Pointer to message buffer. */
+ size_t length; /* Message length. */
+
+ void *private; /* ap driver private pointer. */
+};
+
+#define AP_DEVICE(dt) \
+ .dev_type=(dt), \
+ .match_flags=AP_DEVICE_ID_MATCH_DEVICE_TYPE,
+
+/**
+ * Note: don't use ap_send/ap_recv after using ap_queue_message
+ * for the first time. Otherwise the ap message queue will get
+ * confused.
+ */
+int ap_send(ap_qid_t, unsigned long long, void *, size_t);
+int ap_recv(ap_qid_t, unsigned long long *, void *, size_t);
+
+void ap_queue_message(struct ap_device *ap_dev, struct ap_message *ap_msg);
+void ap_cancel_message(struct ap_device *ap_dev, struct ap_message *ap_msg);
+void ap_flush_queue(struct ap_device *ap_dev);
+
+int ap_module_init(void);
+void ap_module_exit(void);
+
+#endif /* _AP_BUS_H_ */
+++ /dev/null
-/*
- * linux/drivers/s390/crypto/z90common.h
- *
- * z90crypt 1.3.3
- *
- * Copyright (C) 2001, 2005 IBM Corporation
- * Author(s): Robert Burroughs (burrough@us.ibm.com)
- * Eric Rossman (edrossma@us.ibm.com)
- *
- * Hotplug & misc device support: Jochen Roehrig (roehrig@de.ibm.com)
- *
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
- */
-
-#ifndef _Z90COMMON_H_
-#define _Z90COMMON_H_
-
-
-#define RESPBUFFSIZE 256
-#define PCI_FUNC_KEY_DECRYPT 0x5044
-#define PCI_FUNC_KEY_ENCRYPT 0x504B
-extern int ext_bitlens;
-
-enum devstat {
- DEV_GONE,
- DEV_ONLINE,
- DEV_QUEUE_FULL,
- DEV_EMPTY,
- DEV_NO_WORK,
- DEV_BAD_MESSAGE,
- DEV_TSQ_EXCEPTION,
- DEV_RSQ_EXCEPTION,
- DEV_SEN_EXCEPTION,
- DEV_REC_EXCEPTION
-};
-
-enum hdstat {
- HD_NOT_THERE,
- HD_BUSY,
- HD_DECONFIGURED,
- HD_CHECKSTOPPED,
- HD_ONLINE,
- HD_TSQ_EXCEPTION
-};
-