Linux-2.6.12-rc2

Initial git repository build. I'm not bothering with the full history,
even though we have it. We can create a separate "historical" git
archive of that later if we want to, and in the meantime it's about
3.2GB when imported into git - space that would just make the early
git days unnecessarily complicated, when we don't have a lot of good
infrastructure for it.

Let it rip!
This commit is contained in:
Linus Torvalds 2005-04-16 15:20:36 -07:00
commit 1da177e4c3
17291 changed files with 6718755 additions and 0 deletions

209
drivers/s390/Kconfig Normal file
View file

@ -0,0 +1,209 @@
config CCW
bool
default y
source "drivers/block/Kconfig"
source "drivers/md/Kconfig"
menu "Character device drivers"
config UNIX98_PTYS
bool "Unix98 PTY support"
---help---
A pseudo terminal (PTY) is a software device consisting of two
halves: a master and a slave. The slave device behaves identical to
a physical terminal; the master device is used by a process to
read data from and write data to the slave, thereby emulating a
terminal. Typical programs for the master side are telnet servers
and xterms.
Linux has traditionally used the BSD-like names /dev/ptyxx for
masters and /dev/ttyxx for slaves of pseudo terminals. This scheme
has a number of problems. The GNU C library glibc 2.1 and later,
however, supports the Unix98 naming standard: in order to acquire a
pseudo terminal, a process opens /dev/ptmx; the number of the pseudo
terminal is then made available to the process and the pseudo
terminal slave can be accessed as /dev/pts/<number>. What was
traditionally /dev/ttyp2 will then be /dev/pts/2, for example.
The entries in /dev/pts/ are created on the fly by a virtual
file system; therefore, if you say Y here you should say Y to
"/dev/pts file system for Unix98 PTYs" as well.
If you want to say Y here, you need to have the C library glibc 2.1
or later (equal to libc-6.1, check with "ls -l /lib/libc.so.*").
Read the instructions in <file:Documentation/Changes> pertaining to
pseudo terminals. It's safe to say N.
config UNIX98_PTY_COUNT
int "Maximum number of Unix98 PTYs in use (0-2048)"
depends on UNIX98_PTYS
default "256"
help
The maximum number of Unix98 PTYs that can be used at any one time.
The default is 256, and should be enough for desktop systems. Server
machines which support incoming telnet/rlogin/ssh connections and/or
serve several X terminals may want to increase this: every incoming
connection and every xterm uses up one PTY.
When not in use, each additional set of 256 PTYs occupy
approximately 8 KB of kernel memory on 32-bit architectures.
source "drivers/char/watchdog/Kconfig"
comment "S/390 character device drivers"
config TN3270
tristate "Support for locally attached 3270 terminals"
help
Include support for IBM 3270 terminals.
config TN3270_TTY
tristate "Support for tty input/output on 3270 terminals"
depends on TN3270
help
Include support for using an IBM 3270 terminal as a Linux tty.
config TN3270_FS
tristate "Support for fullscreen applications on 3270 terminals"
depends on TN3270
help
Include support for fullscreen applications on an IBM 3270 terminal.
config TN3270_CONSOLE
bool "Support for console on 3270 terminal"
depends on TN3270=y && TN3270_TTY=y
help
Include support for using an IBM 3270 terminal as a Linux system
console. Available only if 3270 support is compiled in statically.
config TN3215
bool "Support for 3215 line mode terminal"
help
Include support for IBM 3215 line-mode terminals.
config TN3215_CONSOLE
bool "Support for console on 3215 line mode terminal"
depends on TN3215
help
Include support for using an IBM 3215 line-mode terminal as a
Linux system console.
config CCW_CONSOLE
bool
depends on TN3215_CONSOLE || TN3270_CONSOLE
default y
config SCLP
bool "Support for SCLP"
help
Include support for the SCLP interface to the service element.
config SCLP_TTY
bool "Support for SCLP line mode terminal"
depends on SCLP
help
Include support for IBM SCLP line-mode terminals.
config SCLP_CONSOLE
bool "Support for console on SCLP line mode terminal"
depends on SCLP_TTY
help
Include support for using an IBM HWC line-mode terminal as the Linux
system console.
config SCLP_VT220_TTY
bool "Support for SCLP VT220-compatible terminal"
depends on SCLP
help
Include support for an IBM SCLP VT220-compatible terminal.
config SCLP_VT220_CONSOLE
bool "Support for console on SCLP VT220-compatible terminal"
depends on SCLP_VT220_TTY
help
Include support for using an IBM SCLP VT220-compatible terminal as a
Linux system console.
config SCLP_CPI
tristate "Control-Program Identification"
depends on SCLP
help
This option enables the hardware console interface for system
identification. This is commonly used for workload management and
gives you a nice name for the system on the service element.
Please select this option as a module since built-in operation is
completely untested.
You should only select this option if you know what you are doing,
need this feature and intend to run your kernel in LPAR.
config S390_TAPE
tristate "S/390 tape device support"
help
Select this option if you want to access channel-attached tape
devices on IBM S/390 or zSeries.
If you select this option you will also want to select at
least one of the tape interface options and one of the tape
hardware options in order to access a tape device.
This option is also available as a module. The module will be
called tape390 and include all selected interfaces and
hardware drivers.
comment "S/390 tape interface support"
depends on S390_TAPE
config S390_TAPE_BLOCK
bool "Support for tape block devices"
depends on S390_TAPE
help
Select this option if you want to access your channel-attached tape
devices using the block device interface. This interface is similar
to CD-ROM devices on other platforms. The tapes can only be
accessed read-only when using this interface. Have a look at
<file:Documentation/s390/TAPE> for further information about creating
volumes for and using this interface. It is safe to say "Y" here.
comment "S/390 tape hardware support"
depends on S390_TAPE
config S390_TAPE_34XX
tristate "Support for 3480/3490 tape hardware"
depends on S390_TAPE
help
Select this option if you want to access IBM 3480/3490 magnetic
tape subsystems and 100% compatibles.
It is safe to say "Y" here.
config VMLOGRDR
tristate "Support for the z/VM recording system services (VM only)"
depends on IUCV
help
Select this option if you want to be able to receive records collected
by the z/VM recording system services, eg. from *LOGREC, *ACCOUNT or
*SYMPTOM.
This driver depends on the IUCV support driver.
config MONREADER
tristate "API for reading z/VM monitor service records"
depends on IUCV
help
Character device driver for reading z/VM monitor service records
endmenu
menu "Cryptographic devices"
config Z90CRYPT
tristate "Support for PCI-attached cryptographic adapters"
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.
endmenu

9
drivers/s390/Makefile Normal file
View file

@ -0,0 +1,9 @@
#
# Makefile for the S/390 specific device drivers
#
obj-y += s390mach.o sysinfo.o
obj-y += cio/ block/ char/ crypto/ net/ scsi/
drivers-y += drivers/s390/built-in.o

View file

@ -0,0 +1,68 @@
if ARCH_S390
comment "S/390 block device drivers"
depends on ARCH_S390
config BLK_DEV_XPRAM
tristate "XPRAM disk support"
depends on ARCH_S390
help
Select this option if you want to use your expanded storage on S/390
or zSeries as a disk. This is useful as a _fast_ swap device if you
want to access more than 2G of memory when running in 31 bit mode.
This option is also available as a module which will be called
xpram. If unsure, say "N".
config DCSSBLK
tristate "DCSSBLK support"
help
Support for dcss block device
config DASD
tristate "Support for DASD devices"
depends on CCW
help
Enable this option if you want to access DASDs directly utilizing
S/390s channel subsystem commands. This is necessary for running
natively on a single image or an LPAR.
config DASD_PROFILE
bool "Profiling support for dasd devices"
depends on DASD
help
Enable this option if you want to see profiling information
in /proc/dasd/statistics.
config DASD_ECKD
tristate "Support for ECKD Disks"
depends on DASD
help
ECKD devices are the most commonly used devices. You should enable
this option unless you are very sure to have no ECKD device.
config DASD_FBA
tristate "Support for FBA Disks"
depends on DASD
help
Select this option to be able to access FBA devices. It is safe to
say "Y".
config DASD_DIAG
tristate "Support for DIAG access to Disks"
depends on DASD && ARCH_S390X = 'n'
help
Select this option if you want to use Diagnose250 command to access
Disks under VM. If you are not running under VM or unsure what it is,
say "N".
config DASD_CMB
tristate "Compatibility interface for DASD channel measurement blocks"
depends on DASD
help
This driver provides an additional interface to the channel measurement
facility, which is normally accessed though sysfs, with a set of
ioctl functions specific to the dasd driver.
This is only needed if you want to use applications written for
linux-2.4 dasd channel measurement facility interface.
endif

View file

@ -0,0 +1,17 @@
#
# S/390 block devices
#
dasd_eckd_mod-objs := dasd_eckd.o dasd_3990_erp.o dasd_9343_erp.o
dasd_fba_mod-objs := dasd_fba.o dasd_3370_erp.o dasd_9336_erp.o
dasd_diag_mod-objs := dasd_diag.o
dasd_mod-objs := dasd.o dasd_ioctl.o dasd_proc.o dasd_devmap.o \
dasd_genhd.o dasd_erp.o
obj-$(CONFIG_DASD) += dasd_mod.o
obj-$(CONFIG_DASD_DIAG) += dasd_diag_mod.o
obj-$(CONFIG_DASD_ECKD) += dasd_eckd_mod.o
obj-$(CONFIG_DASD_FBA) += dasd_fba_mod.o
obj-$(CONFIG_DASD_CMB) += dasd_cmb.o
obj-$(CONFIG_BLK_DEV_XPRAM) += xpram.o
obj-$(CONFIG_DCSSBLK) += dcssblk.o

2065
drivers/s390/block/dasd.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,104 @@
/*
* File...........: linux/drivers/s390/block/dasd_3370_erp.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
*
* $Revision: 1.9 $
*/
#define PRINTK_HEADER "dasd_erp(3370)"
#include "dasd_int.h"
/*
* DASD_3370_ERP_EXAMINE
*
* DESCRIPTION
* Checks only for fatal/no/recover error.
* A detailed examination of the sense data is done later outside
* the interrupt handler.
*
* The logic is based on the 'IBM 3880 Storage Control Reference' manual
* 'Chapter 7. 3370 Sense Data'.
*
* RETURN VALUES
* dasd_era_none no error
* dasd_era_fatal for all fatal (unrecoverable errors)
* dasd_era_recover for all others.
*/
dasd_era_t
dasd_3370_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
{
char *sense = irb->ecw;
/* check for successful execution first */
if (irb->scsw.cstat == 0x00 &&
irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
return dasd_era_none;
if (sense[0] & 0x80) { /* CMD reject */
return dasd_era_fatal;
}
if (sense[0] & 0x40) { /* Drive offline */
return dasd_era_recover;
}
if (sense[0] & 0x20) { /* Bus out parity */
return dasd_era_recover;
}
if (sense[0] & 0x10) { /* equipment check */
if (sense[1] & 0x80) {
return dasd_era_fatal;
}
return dasd_era_recover;
}
if (sense[0] & 0x08) { /* data check */
if (sense[1] & 0x80) {
return dasd_era_fatal;
}
return dasd_era_recover;
}
if (sense[0] & 0x04) { /* overrun */
if (sense[1] & 0x80) {
return dasd_era_fatal;
}
return dasd_era_recover;
}
if (sense[1] & 0x40) { /* invalid blocksize */
return dasd_era_fatal;
}
if (sense[1] & 0x04) { /* file protected */
return dasd_era_recover;
}
if (sense[1] & 0x01) { /* operation incomplete */
return dasd_era_recover;
}
if (sense[2] & 0x80) { /* check data erroor */
return dasd_era_recover;
}
if (sense[2] & 0x10) { /* Env. data present */
return dasd_era_recover;
}
/* examine the 24 byte sense data */
return dasd_era_recover;
} /* END dasd_3370_erp_examine */
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: 1
* tab-width: 8
* End:
*/

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,61 @@
/*
* File...........: linux/drivers/s390/block/dasd_9336_erp.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
*
* $Revision: 1.8 $
*/
#define PRINTK_HEADER "dasd_erp(9336)"
#include "dasd_int.h"
/*
* DASD_9336_ERP_EXAMINE
*
* DESCRIPTION
* Checks only for fatal/no/recover error.
* A detailed examination of the sense data is done later outside
* the interrupt handler.
*
* The logic is based on the 'IBM 3880 Storage Control Reference' manual
* 'Chapter 7. 9336 Sense Data'.
*
* RETURN VALUES
* dasd_era_none no error
* dasd_era_fatal for all fatal (unrecoverable errors)
* dasd_era_recover for all others.
*/
dasd_era_t
dasd_9336_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
{
/* check for successful execution first */
if (irb->scsw.cstat == 0x00 &&
irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
return dasd_era_none;
/* examine the 24 byte sense data */
return dasd_era_recover;
} /* END dasd_9336_erp_examine */
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: 1
* tab-width: 8
* End:
*/

View file

@ -0,0 +1,22 @@
/*
* File...........: linux/drivers/s390/block/dasd_9345_erp.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 2000
*
* $Revision: 1.13 $
*/
#define PRINTK_HEADER "dasd_erp(9343)"
#include "dasd_int.h"
dasd_era_t
dasd_9343_erp_examine(struct dasd_ccw_req * cqr, struct irb * irb)
{
if (irb->scsw.cstat == 0x00 &&
irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
return dasd_era_none;
return dasd_era_recover;
}

View file

@ -0,0 +1,145 @@
/*
* linux/drivers/s390/block/dasd_cmb.c ($Revision: 1.6 $)
*
* Linux on zSeries Channel Measurement Facility support
* (dasd device driver interface)
*
* Copyright 2000,2003 IBM Corporation
*
* Author: Arnd Bergmann <arndb@de.ibm.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include <linux/init.h>
#include <linux/ioctl32.h>
#include <linux/module.h>
#include <asm/ccwdev.h>
#include <asm/cmb.h>
#include "dasd_int.h"
static int
dasd_ioctl_cmf_enable(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
device = bdev->bd_disk->private_data;
if (!device)
return -EINVAL;
return enable_cmf(device->cdev);
}
static int
dasd_ioctl_cmf_disable(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
device = bdev->bd_disk->private_data;
if (!device)
return -EINVAL;
return disable_cmf(device->cdev);
}
static int
dasd_ioctl_readall_cmb(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
struct cmbdata __user *udata;
struct cmbdata data;
size_t size;
int ret;
device = bdev->bd_disk->private_data;
if (!device)
return -EINVAL;
udata = (void __user *) args;
size = _IOC_SIZE(no);
if (!access_ok(VERIFY_WRITE, udata, size))
return -EFAULT;
ret = cmf_readall(device->cdev, &data);
if (ret)
return ret;
if (copy_to_user(udata, &data, min(size, sizeof(*udata))))
return -EFAULT;
return 0;
}
/* module initialization below here. dasd already provides a mechanism
* to dynamically register ioctl functions, so we simply use this. */
static inline int
ioctl_reg(unsigned int no, dasd_ioctl_fn_t handler)
{
int ret;
ret = dasd_ioctl_no_register(THIS_MODULE, no, handler);
#ifdef CONFIG_COMPAT
if (ret)
return ret;
ret = register_ioctl32_conversion(no, NULL);
if (ret)
dasd_ioctl_no_unregister(THIS_MODULE, no, handler);
#endif
return ret;
}
static inline void
ioctl_unreg(unsigned int no, dasd_ioctl_fn_t handler)
{
dasd_ioctl_no_unregister(THIS_MODULE, no, handler);
#ifdef CONFIG_COMPAT
unregister_ioctl32_conversion(no);
#endif
}
static void
dasd_cmf_exit(void)
{
ioctl_unreg(BIODASDCMFENABLE, dasd_ioctl_cmf_enable);
ioctl_unreg(BIODASDCMFDISABLE, dasd_ioctl_cmf_disable);
ioctl_unreg(BIODASDREADALLCMB, dasd_ioctl_readall_cmb);
}
static int __init
dasd_cmf_init(void)
{
int ret;
ret = ioctl_reg (BIODASDCMFENABLE, dasd_ioctl_cmf_enable);
if (ret)
goto err;
ret = ioctl_reg (BIODASDCMFDISABLE, dasd_ioctl_cmf_disable);
if (ret)
goto err;
ret = ioctl_reg (BIODASDREADALLCMB, dasd_ioctl_readall_cmb);
if (ret)
goto err;
return 0;
err:
dasd_cmf_exit();
return ret;
}
module_init(dasd_cmf_init);
module_exit(dasd_cmf_exit);
MODULE_AUTHOR("Arnd Bergmann <[email protected]>");
MODULE_LICENSE("GPL");
MODULE_DESCRIPTION("channel measurement facility interface for dasd\n"
"Copyright 2003 IBM Corporation\n");

View file

@ -0,0 +1,772 @@
/*
* File...........: linux/drivers/s390/block/dasd_devmap.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Horst Hummel <Horst.Hummel@de.ibm.com>
* Carsten Otte <Cotte@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
*
* Device mapping and dasd= parameter parsing functions. All devmap
* functions may not be called from interrupt context. In particular
* dasd_get_device is a no-no from interrupt context.
*
* $Revision: 1.37 $
*/
#include <linux/config.h>
#include <linux/ctype.h>
#include <linux/init.h>
#include <asm/debug.h>
#include <asm/uaccess.h>
/* This is ugly... */
#define PRINTK_HEADER "dasd_devmap:"
#include "dasd_int.h"
kmem_cache_t *dasd_page_cache;
EXPORT_SYMBOL(dasd_page_cache);
/*
* dasd_devmap_t is used to store the features and the relation
* between device number and device index. To find a dasd_devmap_t
* that corresponds to a device number of a device index each
* dasd_devmap_t is added to two linked lists, one to search by
* the device number and one to search by the device index. As
* soon as big minor numbers are available the device index list
* can be removed since the device number will then be identical
* to the device index.
*/
struct dasd_devmap {
struct list_head list;
char bus_id[BUS_ID_SIZE];
unsigned int devindex;
unsigned short features;
struct dasd_device *device;
};
/*
* Parameter parsing functions for dasd= parameter. The syntax is:
* <devno> : (0x)?[0-9a-fA-F]+
* <busid> : [0-0a-f]\.[0-9a-f]\.(0x)?[0-9a-fA-F]+
* <feature> : ro
* <feature_list> : \(<feature>(:<feature>)*\)
* <devno-range> : <devno>(-<devno>)?<feature_list>?
* <busid-range> : <busid>(-<busid>)?<feature_list>?
* <devices> : <devno-range>|<busid-range>
* <dasd_module> : dasd_diag_mod|dasd_eckd_mod|dasd_fba_mod
*
* <dasd> : autodetect|probeonly|<devices>(,<devices>)*
*/
int dasd_probeonly = 0; /* is true, when probeonly mode is active */
int dasd_autodetect = 0; /* is true, when autodetection is active */
/*
* char *dasd[] is intended to hold the ranges supplied by the dasd= statement
* it is named 'dasd' to directly be filled by insmod with the comma separated
* strings when running as a module.
*/
static char *dasd[256];
/*
* Single spinlock to protect devmap structures and lists.
*/
static DEFINE_SPINLOCK(dasd_devmap_lock);
/*
* Hash lists for devmap structures.
*/
static struct list_head dasd_hashlists[256];
int dasd_max_devindex;
static struct dasd_devmap *dasd_add_busid(char *, int);
static inline int
dasd_hash_busid(char *bus_id)
{
int hash, i;
hash = 0;
for (i = 0; (i < BUS_ID_SIZE) && *bus_id; i++, bus_id++)
hash += *bus_id;
return hash & 0xff;
}
#ifndef MODULE
/*
* The parameter parsing functions for builtin-drivers are called
* before kmalloc works. Store the pointers to the parameters strings
* into dasd[] for later processing.
*/
static int __init
dasd_call_setup(char *str)
{
static int count = 0;
if (count < 256)
dasd[count++] = str;
return 1;
}
__setup ("dasd=", dasd_call_setup);
#endif /* #ifndef MODULE */
/*
* Read a device busid/devno from a string.
*/
static inline int
dasd_busid(char **str, int *id0, int *id1, int *devno)
{
int val, old_style;
/* check for leading '0x' */
old_style = 0;
if ((*str)[0] == '0' && (*str)[1] == 'x') {
*str += 2;
old_style = 1;
}
if (!isxdigit((*str)[0])) /* We require at least one hex digit */
return -EINVAL;
val = simple_strtoul(*str, str, 16);
if (old_style || (*str)[0] != '.') {
*id0 = *id1 = 0;
if (val < 0 || val > 0xffff)
return -EINVAL;
*devno = val;
return 0;
}
/* New style x.y.z busid */
if (val < 0 || val > 0xff)
return -EINVAL;
*id0 = val;
(*str)++;
if (!isxdigit((*str)[0])) /* We require at least one hex digit */
return -EINVAL;
val = simple_strtoul(*str, str, 16);
if (val < 0 || val > 0xff || (*str)++[0] != '.')
return -EINVAL;
*id1 = val;
if (!isxdigit((*str)[0])) /* We require at least one hex digit */
return -EINVAL;
val = simple_strtoul(*str, str, 16);
if (val < 0 || val > 0xffff)
return -EINVAL;
*devno = val;
return 0;
}
/*
* Read colon separated list of dasd features. Currently there is
* only one: "ro" for read-only devices. The default feature set
* is empty (value 0).
*/
static inline int
dasd_feature_list(char *str, char **endp)
{
int features, len, rc;
rc = 0;
if (*str != '(') {
*endp = str;
return DASD_FEATURE_DEFAULT;
}
str++;
features = 0;
while (1) {
for (len = 0;
str[len] && str[len] != ':' && str[len] != ')'; len++);
if (len == 2 && !strncmp(str, "ro", 2))
features |= DASD_FEATURE_READONLY;
else if (len == 4 && !strncmp(str, "diag", 4))
features |= DASD_FEATURE_USEDIAG;
else {
MESSAGE(KERN_WARNING,
"unsupported feature: %*s, "
"ignoring setting", len, str);
rc = -EINVAL;
}
str += len;
if (*str != ':')
break;
str++;
}
if (*str != ')') {
MESSAGE(KERN_WARNING, "%s",
"missing ')' in dasd parameter string\n");
rc = -EINVAL;
} else
str++;
*endp = str;
if (rc != 0)
return rc;
return features;
}
/*
* Try to match the first element on the comma separated parse string
* with one of the known keywords. If a keyword is found, take the approprate
* action and return a pointer to the residual string. If the first element
* could not be matched to any keyword then return an error code.
*/
static char *
dasd_parse_keyword( char *parsestring ) {
char *nextcomma, *residual_str;
int length;
nextcomma = strchr(parsestring,',');
if (nextcomma) {
length = nextcomma - parsestring;
residual_str = nextcomma + 1;
} else {
length = strlen(parsestring);
residual_str = parsestring + length;
}
if (strncmp ("autodetect", parsestring, length) == 0) {
dasd_autodetect = 1;
MESSAGE (KERN_INFO, "%s",
"turning to autodetection mode");
return residual_str;
}
if (strncmp ("probeonly", parsestring, length) == 0) {
dasd_probeonly = 1;
MESSAGE(KERN_INFO, "%s",
"turning to probeonly mode");
return residual_str;
}
if (strncmp ("fixedbuffers", parsestring, length) == 0) {
if (dasd_page_cache)
return residual_str;
dasd_page_cache =
kmem_cache_create("dasd_page_cache", PAGE_SIZE, 0,
SLAB_CACHE_DMA, NULL, NULL );
if (!dasd_page_cache)
MESSAGE(KERN_WARNING, "%s", "Failed to create slab, "
"fixed buffer mode disabled.");
else
MESSAGE (KERN_INFO, "%s",
"turning on fixed buffer mode");
return residual_str;
}
return ERR_PTR(-EINVAL);
}
/*
* Try to interprete the first element on the comma separated parse string
* as a device number or a range of devices. If the interpretation is
* successfull, create the matching dasd_devmap entries and return a pointer
* to the residual string.
* If interpretation fails or in case of an error, return an error code.
*/
static char *
dasd_parse_range( char *parsestring ) {
struct dasd_devmap *devmap;
int from, from_id0, from_id1;
int to, to_id0, to_id1;
int features, rc;
char bus_id[BUS_ID_SIZE+1], *str;
str = parsestring;
rc = dasd_busid(&str, &from_id0, &from_id1, &from);
if (rc == 0) {
to = from;
to_id0 = from_id0;
to_id1 = from_id1;
if (*str == '-') {
str++;
rc = dasd_busid(&str, &to_id0, &to_id1, &to);
}
}
if (rc == 0 &&
(from_id0 != to_id0 || from_id1 != to_id1 || from > to))
rc = -EINVAL;
if (rc) {
MESSAGE(KERN_ERR, "Invalid device range %s", parsestring);
return ERR_PTR(rc);
}
features = dasd_feature_list(str, &str);
if (features < 0)
return ERR_PTR(-EINVAL);
while (from <= to) {
sprintf(bus_id, "%01x.%01x.%04x",
from_id0, from_id1, from++);
devmap = dasd_add_busid(bus_id, features);
if (IS_ERR(devmap))
return (char *)devmap;
}
if (*str == ',')
return str + 1;
if (*str == '\0')
return str;
MESSAGE(KERN_WARNING,
"junk at end of dasd parameter string: %s\n", str);
return ERR_PTR(-EINVAL);
}
static inline char *
dasd_parse_next_element( char *parsestring ) {
char * residual_str;
residual_str = dasd_parse_keyword(parsestring);
if (!IS_ERR(residual_str))
return residual_str;
residual_str = dasd_parse_range(parsestring);
return residual_str;
}
/*
* Parse parameters stored in dasd[]
* The 'dasd=...' parameter allows to specify a comma separated list of
* keywords and device ranges. When the dasd driver is build into the kernel,
* the complete list will be stored as one element of the dasd[] array.
* When the dasd driver is build as a module, then the list is broken into
* it's elements and each dasd[] entry contains one element.
*/
int
dasd_parse(void)
{
int rc, i;
char *parsestring;
rc = 0;
for (i = 0; i < 256; i++) {
if (dasd[i] == NULL)
break;
parsestring = dasd[i];
/* loop over the comma separated list in the parsestring */
while (*parsestring) {
parsestring = dasd_parse_next_element(parsestring);
if(IS_ERR(parsestring)) {
rc = PTR_ERR(parsestring);
break;
}
}
if (rc) {
DBF_EVENT(DBF_ALERT, "%s", "invalid range found");
break;
}
}
return rc;
}
/*
* Add a devmap for the device specified by busid. It is possible that
* the devmap already exists (dasd= parameter). The order of the devices
* added through this function will define the kdevs for the individual
* devices.
*/
static struct dasd_devmap *
dasd_add_busid(char *bus_id, int features)
{
struct dasd_devmap *devmap, *new, *tmp;
int hash;
new = (struct dasd_devmap *)
kmalloc(sizeof(struct dasd_devmap), GFP_KERNEL);
if (!new)
return ERR_PTR(-ENOMEM);
spin_lock(&dasd_devmap_lock);
devmap = 0;
hash = dasd_hash_busid(bus_id);
list_for_each_entry(tmp, &dasd_hashlists[hash], list)
if (strncmp(tmp->bus_id, bus_id, BUS_ID_SIZE) == 0) {
devmap = tmp;
break;
}
if (!devmap) {
/* This bus_id is new. */
new->devindex = dasd_max_devindex++;
strncpy(new->bus_id, bus_id, BUS_ID_SIZE);
new->features = features;
new->device = 0;
list_add(&new->list, &dasd_hashlists[hash]);
devmap = new;
new = 0;
}
spin_unlock(&dasd_devmap_lock);
if (new)
kfree(new);
return devmap;
}
/*
* Find devmap for device with given bus_id.
*/
static struct dasd_devmap *
dasd_find_busid(char *bus_id)
{
struct dasd_devmap *devmap, *tmp;
int hash;
spin_lock(&dasd_devmap_lock);
devmap = ERR_PTR(-ENODEV);
hash = dasd_hash_busid(bus_id);
list_for_each_entry(tmp, &dasd_hashlists[hash], list) {
if (strncmp(tmp->bus_id, bus_id, BUS_ID_SIZE) == 0) {
devmap = tmp;
break;
}
}
spin_unlock(&dasd_devmap_lock);
return devmap;
}
/*
* Check if busid has been added to the list of dasd ranges.
*/
int
dasd_busid_known(char *bus_id)
{
return IS_ERR(dasd_find_busid(bus_id)) ? -ENOENT : 0;
}
/*
* Forget all about the device numbers added so far.
* This may only be called at module unload or system shutdown.
*/
static void
dasd_forget_ranges(void)
{
struct dasd_devmap *devmap, *n;
int i;
spin_lock(&dasd_devmap_lock);
for (i = 0; i < 256; i++) {
list_for_each_entry_safe(devmap, n, &dasd_hashlists[i], list) {
if (devmap->device != NULL)
BUG();
list_del(&devmap->list);
kfree(devmap);
}
}
spin_unlock(&dasd_devmap_lock);
}
/*
* Find the device struct by its device index.
*/
struct dasd_device *
dasd_device_from_devindex(int devindex)
{
struct dasd_devmap *devmap, *tmp;
struct dasd_device *device;
int i;
spin_lock(&dasd_devmap_lock);
devmap = 0;
for (i = 0; (i < 256) && !devmap; i++)
list_for_each_entry(tmp, &dasd_hashlists[i], list)
if (tmp->devindex == devindex) {
/* Found the devmap for the device. */
devmap = tmp;
break;
}
if (devmap && devmap->device) {
device = devmap->device;
dasd_get_device(device);
} else
device = ERR_PTR(-ENODEV);
spin_unlock(&dasd_devmap_lock);
return device;
}
/*
* Return devmap for cdev. If no devmap exists yet, create one and
* connect it to the cdev.
*/
static struct dasd_devmap *
dasd_devmap_from_cdev(struct ccw_device *cdev)
{
struct dasd_devmap *devmap;
devmap = dasd_find_busid(cdev->dev.bus_id);
if (IS_ERR(devmap))
devmap = dasd_add_busid(cdev->dev.bus_id,
DASD_FEATURE_DEFAULT);
return devmap;
}
/*
* Create a dasd device structure for cdev.
*/
struct dasd_device *
dasd_create_device(struct ccw_device *cdev)
{
struct dasd_devmap *devmap;
struct dasd_device *device;
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);
spin_lock(&dasd_devmap_lock);
if (!devmap->device) {
devmap->device = device;
device->devindex = devmap->devindex;
if (devmap->features & DASD_FEATURE_READONLY)
set_bit(DASD_FLAG_RO, &device->flags);
else
clear_bit(DASD_FLAG_RO, &device->flags);
if (devmap->features & DASD_FEATURE_USEDIAG)
set_bit(DASD_FLAG_USE_DIAG, &device->flags);
else
clear_bit(DASD_FLAG_USE_DIAG, &device->flags);
get_device(&cdev->dev);
device->cdev = cdev;
rc = 0;
} else
/* Someone else was faster. */
rc = -EBUSY;
spin_unlock(&dasd_devmap_lock);
if (rc) {
dasd_free_device(device);
return ERR_PTR(rc);
}
return device;
}
/*
* Wait queue for dasd_delete_device waits.
*/
static DECLARE_WAIT_QUEUE_HEAD(dasd_delete_wq);
/*
* Remove a dasd device structure. The passed referenced
* is destroyed.
*/
void
dasd_delete_device(struct dasd_device *device)
{
struct ccw_device *cdev;
struct dasd_devmap *devmap;
/* First remove device pointer from devmap. */
devmap = dasd_find_busid(device->cdev->dev.bus_id);
if (IS_ERR(devmap))
BUG();
spin_lock(&dasd_devmap_lock);
if (devmap->device != device) {
spin_unlock(&dasd_devmap_lock);
dasd_put_device(device);
return;
}
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);
/* Wait for reference counter to drop to zero. */
wait_event(dasd_delete_wq, atomic_read(&device->ref_count) == 0);
/* Disconnect dasd_device structure from ccw_device structure. */
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);
/* Now the device structure can be freed. */
dasd_free_device(device);
}
/*
* Reference counter dropped to zero. Wake up waiter
* in dasd_delete_device.
*/
void
dasd_put_device_wake(struct dasd_device *device)
{
wake_up(&dasd_delete_wq);
}
/*
* 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;
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);
return device;
}
/*
* SECTION: files in sysfs
*/
/*
* readonly controls the readonly status of a dasd
*/
static ssize_t
dasd_ro_show(struct device *dev, char *buf)
{
struct dasd_devmap *devmap;
int ro_flag;
devmap = dasd_find_busid(dev->bus_id);
if (!IS_ERR(devmap))
ro_flag = (devmap->features & DASD_FEATURE_READONLY) != 0;
else
ro_flag = (DASD_FEATURE_DEFAULT & DASD_FEATURE_READONLY) != 0;
return snprintf(buf, PAGE_SIZE, ro_flag ? "1\n" : "0\n");
}
static ssize_t
dasd_ro_store(struct device *dev, const char *buf, size_t count)
{
struct dasd_devmap *devmap;
int ro_flag;
devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
if (IS_ERR(devmap))
return PTR_ERR(devmap);
ro_flag = buf[0] == '1';
spin_lock(&dasd_devmap_lock);
if (ro_flag)
devmap->features |= DASD_FEATURE_READONLY;
else
devmap->features &= ~DASD_FEATURE_READONLY;
if (devmap->device) {
if (devmap->device->gdp)
set_disk_ro(devmap->device->gdp, ro_flag);
if (ro_flag)
set_bit(DASD_FLAG_RO, &devmap->device->flags);
else
clear_bit(DASD_FLAG_RO, &devmap->device->flags);
}
spin_unlock(&dasd_devmap_lock);
return count;
}
static DEVICE_ATTR(readonly, 0644, dasd_ro_show, dasd_ro_store);
/*
* use_diag controls whether the driver should use diag rather than ssch
* to talk to the device
*/
static ssize_t
dasd_use_diag_show(struct device *dev, char *buf)
{
struct dasd_devmap *devmap;
int use_diag;
devmap = dasd_find_busid(dev->bus_id);
if (!IS_ERR(devmap))
use_diag = (devmap->features & DASD_FEATURE_USEDIAG) != 0;
else
use_diag = (DASD_FEATURE_DEFAULT & DASD_FEATURE_USEDIAG) != 0;
return sprintf(buf, use_diag ? "1\n" : "0\n");
}
static ssize_t
dasd_use_diag_store(struct device *dev, const char *buf, size_t count)
{
struct dasd_devmap *devmap;
ssize_t rc;
int use_diag;
devmap = dasd_devmap_from_cdev(to_ccwdev(dev));
if (IS_ERR(devmap))
return PTR_ERR(devmap);
use_diag = buf[0] == '1';
spin_lock(&dasd_devmap_lock);
/* Changing diag discipline flag is only allowed in offline state. */
rc = count;
if (!devmap->device) {
if (use_diag)
devmap->features |= DASD_FEATURE_USEDIAG;
else
devmap->features &= ~DASD_FEATURE_USEDIAG;
} else
rc = -EPERM;
spin_unlock(&dasd_devmap_lock);
return rc;
}
static
DEVICE_ATTR(use_diag, 0644, dasd_use_diag_show, dasd_use_diag_store);
static ssize_t
dasd_discipline_show(struct device *dev, char *buf)
{
struct dasd_devmap *devmap;
char *dname;
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);
}
static DEVICE_ATTR(discipline, 0444, dasd_discipline_show, NULL);
static struct attribute * dasd_attrs[] = {
&dev_attr_readonly.attr,
&dev_attr_discipline.attr,
&dev_attr_use_diag.attr,
NULL,
};
static struct attribute_group dasd_attr_group = {
.attrs = dasd_attrs,
};
int
dasd_add_sysfs_files(struct ccw_device *cdev)
{
return sysfs_create_group(&cdev->dev.kobj, &dasd_attr_group);
}
void
dasd_remove_sysfs_files(struct ccw_device *cdev)
{
sysfs_remove_group(&cdev->dev.kobj, &dasd_attr_group);
}
int
dasd_devmap_init(void)
{
int i;
/* Initialize devmap structures. */
dasd_max_devindex = 0;
for (i = 0; i < 256; i++)
INIT_LIST_HEAD(&dasd_hashlists[i]);
return 0;
}
void
dasd_devmap_exit(void)
{
dasd_forget_ranges();
}

View file

@ -0,0 +1,541 @@
/*
* File...........: linux/drivers/s390/block/dasd_diag.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Based on.......: linux/drivers/s390/block/mdisk.c
* ...............: by Hartmunt Penner <hpenner@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
*
* $Revision: 1.42 $
*/
#include <linux/config.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/slab.h>
#include <linux/hdreg.h> /* HDIO_GETGEO */
#include <linux/bio.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/dasd.h>
#include <asm/debug.h>
#include <asm/ebcdic.h>
#include <asm/io.h>
#include <asm/s390_ext.h>
#include <asm/todclk.h>
#include "dasd_int.h"
#include "dasd_diag.h"
#ifdef PRINTK_HEADER
#undef PRINTK_HEADER
#endif /* PRINTK_HEADER */
#define PRINTK_HEADER "dasd(diag):"
MODULE_LICENSE("GPL");
struct dasd_discipline dasd_diag_discipline;
struct dasd_diag_private {
struct dasd_diag_characteristics rdc_data;
struct dasd_diag_rw_io iob;
struct dasd_diag_init_io iib;
unsigned int pt_block;
};
struct dasd_diag_req {
int block_count;
struct dasd_diag_bio bio[0];
};
static __inline__ int
dia250(void *iob, int cmd)
{
int rc;
__asm__ __volatile__(" lhi %0,3\n"
" lr 0,%2\n"
" diag 0,%1,0x250\n"
"0: ipm %0\n"
" srl %0,28\n"
" or %0,1\n"
"1:\n"
#ifndef CONFIG_ARCH_S390X
".section __ex_table,\"a\"\n"
" .align 4\n"
" .long 0b,1b\n"
".previous\n"
#else
".section __ex_table,\"a\"\n"
" .align 8\n"
" .quad 0b,1b\n"
".previous\n"
#endif
: "=&d" (rc)
: "d" (cmd), "d" ((void *) __pa(iob))
: "0", "1", "cc");
return rc;
}
static __inline__ int
mdsk_init_io(struct dasd_device * device, int blocksize, int offset, int size)
{
struct dasd_diag_private *private;
struct dasd_diag_init_io *iib;
int rc;
private = (struct dasd_diag_private *) device->private;
iib = &private->iib;
memset(iib, 0, sizeof (struct dasd_diag_init_io));
iib->dev_nr = _ccw_device_get_device_number(device->cdev);
iib->block_size = blocksize;
iib->offset = offset;
iib->start_block = 0;
iib->end_block = size;
rc = dia250(iib, INIT_BIO);
return rc & 3;
}
static __inline__ int
mdsk_term_io(struct dasd_device * device)
{
struct dasd_diag_private *private;
struct dasd_diag_init_io *iib;
int rc;
private = (struct dasd_diag_private *) device->private;
iib = &private->iib;
memset(iib, 0, sizeof (struct dasd_diag_init_io));
iib->dev_nr = _ccw_device_get_device_number(device->cdev);
rc = dia250(iib, TERM_BIO);
return rc & 3;
}
static int
dasd_start_diag(struct dasd_ccw_req * cqr)
{
struct dasd_device *device;
struct dasd_diag_private *private;
struct dasd_diag_req *dreq;
int rc;
device = cqr->device;
private = (struct dasd_diag_private *) device->private;
dreq = (struct dasd_diag_req *) cqr->data;
private->iob.dev_nr = _ccw_device_get_device_number(device->cdev);
private->iob.key = 0;
private->iob.flags = 2; /* do asynchronous io */
private->iob.block_count = dreq->block_count;
private->iob.interrupt_params = (u32)(addr_t) cqr;
private->iob.bio_list = __pa(dreq->bio);
cqr->startclk = get_clock();
rc = dia250(&private->iob, RW_BIO);
if (rc > 8) {
DEV_MESSAGE(KERN_WARNING, device, "dia250 returned CC %d", rc);
cqr->status = DASD_CQR_ERROR;
} else if (rc == 0) {
cqr->status = DASD_CQR_DONE;
dasd_schedule_bh(device);
} else {
cqr->status = DASD_CQR_IN_IO;
rc = 0;
}
return rc;
}
static void
dasd_ext_handler(struct pt_regs *regs, __u16 code)
{
struct dasd_ccw_req *cqr, *next;
struct dasd_device *device;
unsigned long long expires;
unsigned long flags;
char status;
int ip;
/*
* Get the external interruption subcode. VM stores
* this in the 'cpu address' field associated with
* the external interrupt. For diag 250 the subcode
* needs to be 3.
*/
if ((S390_lowcore.cpu_addr & 0xff00) != 0x0300)
return;
status = *((char *) &S390_lowcore.ext_params + 5);
ip = S390_lowcore.ext_params;
if (!ip) { /* no intparm: unsolicited interrupt */
MESSAGE(KERN_DEBUG, "%s", "caught unsolicited interrupt");
return;
}
cqr = (struct dasd_ccw_req *)(addr_t) ip;
device = (struct dasd_device *) cqr->device;
if (strncmp(device->discipline->ebcname, (char *) &cqr->magic, 4)) {
DEV_MESSAGE(KERN_WARNING, device,
" magic number of dasd_ccw_req 0x%08X doesn't"
" match discipline 0x%08X",
cqr->magic, *(int *) (&device->discipline->name));
return;
}
/* get irq lock to modify request queue */
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
cqr->stopclk = get_clock();
expires = 0;
if (status == 0) {
cqr->status = DASD_CQR_DONE;
/* Start first request on queue if possible -> fast_io. */
if (!list_empty(&device->ccw_queue)) {
next = list_entry(device->ccw_queue.next,
struct dasd_ccw_req, list);
if (next->status == DASD_CQR_QUEUED) {
if (dasd_start_diag(next) == 0)
expires = next->expires;
else
DEV_MESSAGE(KERN_WARNING, device, "%s",
"Interrupt fastpath "
"failed!");
}
}
} else
cqr->status = DASD_CQR_FAILED;
if (expires != 0)
dasd_set_timer(device, expires);
else
dasd_clear_timer(device);
dasd_schedule_bh(device);
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
}
static int
dasd_diag_check_device(struct dasd_device *device)
{
struct dasd_diag_private *private;
struct dasd_diag_characteristics *rdc_data;
struct dasd_diag_bio bio;
long *label;
int sb, bsize;
int rc;
private = (struct dasd_diag_private *) device->private;
if (private == NULL) {
private = kmalloc(sizeof(struct dasd_diag_private),GFP_KERNEL);
if (private == NULL) {
DEV_MESSAGE(KERN_WARNING, device, "%s",
"memory allocation failed for private data");
return -ENOMEM;
}
device->private = (void *) private;
}
/* Read Device Characteristics */
rdc_data = (void *) &(private->rdc_data);
rdc_data->dev_nr = _ccw_device_get_device_number(device->cdev);
rdc_data->rdc_len = sizeof (struct dasd_diag_characteristics);
rc = diag210((struct diag210 *) rdc_data);
if (rc)
return -ENOTSUPP;
/* Figure out position of label block */
switch (private->rdc_data.vdev_class) {
case DEV_CLASS_FBA:
private->pt_block = 1;
break;
case DEV_CLASS_ECKD:
private->pt_block = 2;
break;
default:
return -ENOTSUPP;
}
DBF_DEV_EVENT(DBF_INFO, device,
"%04X: %04X on real %04X/%02X",
rdc_data->dev_nr,
rdc_data->vdev_type,
rdc_data->rdev_type, rdc_data->rdev_model);
/* terminate all outstanding operations */
mdsk_term_io(device);
/* figure out blocksize of device */
label = (long *) get_zeroed_page(GFP_KERNEL);
if (label == NULL) {
DEV_MESSAGE(KERN_WARNING, device, "%s",
"No memory to allocate initialization request");
return -ENOMEM;
}
/* try all sizes - needed for ECKD devices */
for (bsize = 512; bsize <= PAGE_SIZE; bsize <<= 1) {
mdsk_init_io(device, bsize, 0, 64);
memset(&bio, 0, sizeof (struct dasd_diag_bio));
bio.type = MDSK_READ_REQ;
bio.block_number = private->pt_block + 1;
bio.buffer = __pa(label);
memset(&private->iob, 0, sizeof (struct dasd_diag_rw_io));
private->iob.dev_nr = rdc_data->dev_nr;
private->iob.key = 0;
private->iob.flags = 0; /* do synchronous io */
private->iob.block_count = 1;
private->iob.interrupt_params = 0;
private->iob.bio_list = __pa(&bio);
if (dia250(&private->iob, RW_BIO) == 0)
break;
mdsk_term_io(device);
}
if (bsize <= PAGE_SIZE && label[0] == 0xc3d4e2f1) {
/* get formatted blocksize from label block */
bsize = (int) label[3];
device->blocks = label[7];
device->bp_block = bsize;
device->s2b_shift = 0; /* bits to shift 512 to get a block */
for (sb = 512; sb < bsize; sb = sb << 1)
device->s2b_shift++;
DEV_MESSAGE(KERN_INFO, device,
"capacity (%dkB blks): %ldkB",
(device->bp_block >> 10),
(device->blocks << device->s2b_shift) >> 1);
rc = 0;
} else {
if (bsize > PAGE_SIZE)
DEV_MESSAGE(KERN_WARNING, device, "%s",
"DIAG access failed");
else
DEV_MESSAGE(KERN_WARNING, device, "%s",
"volume is not CMS formatted");
rc = -EMEDIUMTYPE;
}
free_page((long) label);
return rc;
}
static int
dasd_diag_fill_geometry(struct dasd_device *device, struct hd_geometry *geo)
{
if (dasd_check_blocksize(device->bp_block) != 0)
return -EINVAL;
geo->cylinders = (device->blocks << device->s2b_shift) >> 10;
geo->heads = 16;
geo->sectors = 128 >> device->s2b_shift;
return 0;
}
static dasd_era_t
dasd_diag_examine_error(struct dasd_ccw_req * cqr, struct irb * stat)
{
return dasd_era_fatal;
}
static dasd_erp_fn_t
dasd_diag_erp_action(struct dasd_ccw_req * cqr)
{
return dasd_default_erp_action;
}
static dasd_erp_fn_t
dasd_diag_erp_postaction(struct dasd_ccw_req * cqr)
{
return dasd_default_erp_postaction;
}
static struct dasd_ccw_req *
dasd_diag_build_cp(struct dasd_device * device, struct request *req)
{
struct dasd_ccw_req *cqr;
struct dasd_diag_req *dreq;
struct dasd_diag_bio *dbio;
struct bio *bio;
struct bio_vec *bv;
char *dst;
int count, datasize;
sector_t recid, first_rec, last_rec;
unsigned blksize, off;
unsigned char rw_cmd;
int i;
if (rq_data_dir(req) == READ)
rw_cmd = MDSK_READ_REQ;
else if (rq_data_dir(req) == WRITE)
rw_cmd = MDSK_WRITE_REQ;
else
return ERR_PTR(-EINVAL);
blksize = device->bp_block;
/* Calculate record id of first and last block. */
first_rec = req->sector >> device->s2b_shift;
last_rec = (req->sector + req->nr_sectors - 1) >> device->s2b_shift;
/* Check struct bio and count the number of blocks for the request. */
count = 0;
rq_for_each_bio(bio, req) {
bio_for_each_segment(bv, bio, i) {
if (bv->bv_len & (blksize - 1))
/* Fba can only do full blocks. */
return ERR_PTR(-EINVAL);
count += bv->bv_len >> (device->s2b_shift + 9);
}
}
/* Paranoia. */
if (count != last_rec - first_rec + 1)
return ERR_PTR(-EINVAL);
/* Build the request */
datasize = sizeof(struct dasd_diag_req) +
count*sizeof(struct dasd_diag_bio);
cqr = dasd_smalloc_request(dasd_diag_discipline.name, 0,
datasize, device);
if (IS_ERR(cqr))
return cqr;
dreq = (struct dasd_diag_req *) cqr->data;
dreq->block_count = count;
dbio = dreq->bio;
recid = first_rec;
rq_for_each_bio(bio, req) {
bio_for_each_segment(bv, bio, i) {
dst = page_address(bv->bv_page) + bv->bv_offset;
for (off = 0; off < bv->bv_len; off += blksize) {
memset(dbio, 0, sizeof (struct dasd_diag_bio));
dbio->type = rw_cmd;
dbio->block_number = recid + 1;
dbio->buffer = __pa(dst);
dbio++;
dst += blksize;
recid++;
}
}
}
cqr->buildclk = get_clock();
cqr->device = device;
cqr->expires = 50 * HZ; /* 50 seconds */
cqr->status = DASD_CQR_FILLED;
return cqr;
}
static int
dasd_diag_free_cp(struct dasd_ccw_req *cqr, struct request *req)
{
int status;
status = cqr->status == DASD_CQR_DONE;
dasd_sfree_request(cqr, cqr->device);
return status;
}
static int
dasd_diag_fill_info(struct dasd_device * device,
struct dasd_information2_t * info)
{
struct dasd_diag_private *private;
private = (struct dasd_diag_private *) device->private;
info->label_block = private->pt_block;
info->FBA_layout = 1;
info->format = DASD_FORMAT_LDL;
info->characteristics_size = sizeof (struct dasd_diag_characteristics);
memcpy(info->characteristics,
&((struct dasd_diag_private *) device->private)->rdc_data,
sizeof (struct dasd_diag_characteristics));
info->confdata_size = 0;
return 0;
}
static void
dasd_diag_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
struct irb *stat)
{
DEV_MESSAGE(KERN_ERR, device, "%s",
"dump sense not available for DIAG data");
}
/*
* max_blocks is dependent on the amount of storage that is available
* in the static io buffer for each device. Currently each device has
* 8192 bytes (=2 pages). dasd diag is only relevant for 31 bit.
* The struct dasd_ccw_req has 96 bytes, the struct dasd_diag_req has
* 8 bytes and the struct dasd_diag_bio for each block has 16 bytes.
* That makes:
* (8192 - 96 - 8) / 16 = 505.5 blocks at maximum.
* We want to fit two into the available memory so that we can immediately
* start the next request if one finishes off. That makes 252.75 blocks
* for one request. Give a little safety and the result is 240.
*/
struct dasd_discipline dasd_diag_discipline = {
.owner = THIS_MODULE,
.name = "DIAG",
.ebcname = "DIAG",
.max_blocks = 240,
.check_device = dasd_diag_check_device,
.fill_geometry = dasd_diag_fill_geometry,
.start_IO = dasd_start_diag,
.examine_error = dasd_diag_examine_error,
.erp_action = dasd_diag_erp_action,
.erp_postaction = dasd_diag_erp_postaction,
.build_cp = dasd_diag_build_cp,
.free_cp = dasd_diag_free_cp,
.dump_sense = dasd_diag_dump_sense,
.fill_info = dasd_diag_fill_info,
};
static int __init
dasd_diag_init(void)
{
if (!MACHINE_IS_VM) {
MESSAGE_LOG(KERN_INFO,
"Machine is not VM: %s "
"discipline not initializing",
dasd_diag_discipline.name);
return -EINVAL;
}
ASCEBC(dasd_diag_discipline.ebcname, 4);
ctl_set_bit(0, 9);
register_external_interrupt(0x2603, dasd_ext_handler);
dasd_diag_discipline_pointer = &dasd_diag_discipline;
return 0;
}
static void __exit
dasd_diag_cleanup(void)
{
if (!MACHINE_IS_VM) {
MESSAGE_LOG(KERN_INFO,
"Machine is not VM: %s "
"discipline not cleaned",
dasd_diag_discipline.name);
return;
}
unregister_external_interrupt(0x2603, dasd_ext_handler);
ctl_clear_bit(0, 9);
dasd_diag_discipline_pointer = NULL;
}
module_init(dasd_diag_init);
module_exit(dasd_diag_cleanup);
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: 1
* tab-width: 8
* End:
*/

View file

@ -0,0 +1,66 @@
/*
* File...........: linux/drivers/s390/block/dasd_diag.h
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Based on.......: linux/drivers/s390/block/mdisk.h
* ...............: by Hartmunt Penner <hpenner@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
*
* $Revision: 1.6 $
*/
#define MDSK_WRITE_REQ 0x01
#define MDSK_READ_REQ 0x02
#define INIT_BIO 0x00
#define RW_BIO 0x01
#define TERM_BIO 0x02
#define DEV_CLASS_FBA 0x01
#define DEV_CLASS_ECKD 0x04
struct dasd_diag_characteristics {
u16 dev_nr;
u16 rdc_len;
u8 vdev_class;
u8 vdev_type;
u8 vdev_status;
u8 vdev_flags;
u8 rdev_class;
u8 rdev_type;
u8 rdev_model;
u8 rdev_features;
} __attribute__ ((packed, aligned(4)));
struct dasd_diag_bio {
u8 type;
u8 status;
u16 spare1;
u32 block_number;
u32 alet;
u32 buffer;
} __attribute__ ((packed, aligned(8)));
struct dasd_diag_init_io {
u16 dev_nr;
u16 spare1[11];
u32 block_size;
u32 offset;
u32 start_block;
u32 end_block;
u32 spare2[6];
} __attribute__ ((packed, aligned(8)));
struct dasd_diag_rw_io {
u16 dev_nr;
u16 spare1[11];
u8 key;
u8 flags;
u16 spare2;
u32 block_count;
u32 alet;
u32 bio_list;
u32 interrupt_params;
u32 spare3[5];
} __attribute__ ((packed, aligned(8)));

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,346 @@
/*
* File...........: linux/drivers/s390/block/dasd_eckd.h
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Horst Hummel <Horst.Hummel@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
*
* $Revision: 1.10 $
*/
#ifndef DASD_ECKD_H
#define DASD_ECKD_H
/*****************************************************************************
* SECTION: CCW Definitions
****************************************************************************/
#define DASD_ECKD_CCW_WRITE 0x05
#define DASD_ECKD_CCW_READ 0x06
#define DASD_ECKD_CCW_WRITE_HOME_ADDRESS 0x09
#define DASD_ECKD_CCW_READ_HOME_ADDRESS 0x0a
#define DASD_ECKD_CCW_WRITE_KD 0x0d
#define DASD_ECKD_CCW_READ_KD 0x0e
#define DASD_ECKD_CCW_ERASE 0x11
#define DASD_ECKD_CCW_READ_COUNT 0x12
#define DASD_ECKD_CCW_SLCK 0x14
#define DASD_ECKD_CCW_WRITE_RECORD_ZERO 0x15
#define DASD_ECKD_CCW_READ_RECORD_ZERO 0x16
#define DASD_ECKD_CCW_WRITE_CKD 0x1d
#define DASD_ECKD_CCW_READ_CKD 0x1e
#define DASD_ECKD_CCW_PSF 0x27
#define DASD_ECKD_CCW_RSSD 0x3e
#define DASD_ECKD_CCW_LOCATE_RECORD 0x47
#define DASD_ECKD_CCW_DEFINE_EXTENT 0x63
#define DASD_ECKD_CCW_WRITE_MT 0x85
#define DASD_ECKD_CCW_READ_MT 0x86
#define DASD_ECKD_CCW_WRITE_KD_MT 0x8d
#define DASD_ECKD_CCW_READ_KD_MT 0x8e
#define DASD_ECKD_CCW_RELEASE 0x94
#define DASD_ECKD_CCW_READ_CKD_MT 0x9e
#define DASD_ECKD_CCW_WRITE_CKD_MT 0x9d
#define DASD_ECKD_CCW_RESERVE 0xB4
/*
*Perform Subsystem Function / Sub-Orders
*/
#define PSF_ORDER_PRSSD 0x18
/*****************************************************************************
* SECTION: Type Definitions
****************************************************************************/
struct eckd_count {
__u16 cyl;
__u16 head;
__u8 record;
__u8 kl;
__u16 dl;
} __attribute__ ((packed));
struct ch_t {
__u16 cyl;
__u16 head;
} __attribute__ ((packed));
struct chs_t {
__u16 cyl;
__u16 head;
__u32 sector;
} __attribute__ ((packed));
struct chr_t {
__u16 cyl;
__u16 head;
__u8 record;
} __attribute__ ((packed));
struct geom_t {
__u16 cyl;
__u16 head;
__u32 sector;
} __attribute__ ((packed));
struct eckd_home {
__u8 skip_control[14];
__u16 cell_number;
__u8 physical_addr[3];
__u8 flag;
struct ch_t track_addr;
__u8 reserved;
__u8 key_length;
__u8 reserved2[2];
} __attribute__ ((packed));
struct DE_eckd_data {
struct {
unsigned char perm:2; /* Permissions on this extent */
unsigned char reserved:1;
unsigned char seek:2; /* Seek control */
unsigned char auth:2; /* Access authorization */
unsigned char pci:1; /* PCI Fetch mode */
} __attribute__ ((packed)) mask;
struct {
unsigned char mode:2; /* Architecture mode */
unsigned char ckd:1; /* CKD Conversion */
unsigned char operation:3; /* Operation mode */
unsigned char cfw:1; /* Cache fast write */
unsigned char dfw:1; /* DASD fast write */
} __attribute__ ((packed)) attributes;
__u16 blk_size; /* Blocksize */
__u16 fast_write_id;
__u8 ga_additional; /* Global Attributes Additional */
__u8 ga_extended; /* Global Attributes Extended */
struct ch_t beg_ext;
struct ch_t end_ext;
unsigned long long ep_sys_time; /* Ext Parameter - System Time Stamp */
__u8 ep_format; /* Extended Parameter format byte */
__u8 ep_prio; /* Extended Parameter priority I/O byte */
__u8 ep_reserved[6]; /* Extended Parameter Reserved */
} __attribute__ ((packed));
struct LO_eckd_data {
struct {
unsigned char orientation:2;
unsigned char operation:6;
} __attribute__ ((packed)) operation;
struct {
unsigned char last_bytes_used:1;
unsigned char reserved:6;
unsigned char read_count_suffix:1;
} __attribute__ ((packed)) auxiliary;
__u8 unused;
__u8 count;
struct ch_t seek_addr;
struct chr_t search_arg;
__u8 sector;
__u16 length;
} __attribute__ ((packed));
struct dasd_eckd_characteristics {
__u16 cu_type;
struct {
unsigned char support:2;
unsigned char async:1;
unsigned char reserved:1;
unsigned char cache_info:1;
unsigned char model:3;
} __attribute__ ((packed)) cu_model;
__u16 dev_type;
__u8 dev_model;
struct {
unsigned char mult_burst:1;
unsigned char RT_in_LR:1;
unsigned char reserved1:1;
unsigned char RD_IN_LR:1;
unsigned char reserved2:4;
unsigned char reserved3:8;
unsigned char defect_wr:1;
unsigned char XRC_supported:1;
unsigned char reserved4:1;
unsigned char striping:1;
unsigned char reserved5:4;
unsigned char cfw:1;
unsigned char reserved6:2;
unsigned char cache:1;
unsigned char dual_copy:1;
unsigned char dfw:1;
unsigned char reset_alleg:1;
unsigned char sense_down:1;
} __attribute__ ((packed)) facilities;
__u8 dev_class;
__u8 unit_type;
__u16 no_cyl;
__u16 trk_per_cyl;
__u8 sec_per_trk;
__u8 byte_per_track[3];
__u16 home_bytes;
__u8 formula;
union {
struct {
__u8 f1;
__u16 f2;
__u16 f3;
} __attribute__ ((packed)) f_0x01;
struct {
__u8 f1;
__u8 f2;
__u8 f3;
__u8 f4;
__u8 f5;
} __attribute__ ((packed)) f_0x02;
} __attribute__ ((packed)) factors;
__u16 first_alt_trk;
__u16 no_alt_trk;
__u16 first_dia_trk;
__u16 no_dia_trk;
__u16 first_sup_trk;
__u16 no_sup_trk;
__u8 MDR_ID;
__u8 OBR_ID;
__u8 director;
__u8 rd_trk_set;
__u16 max_rec_zero;
__u8 reserved1;
__u8 RWANY_in_LR;
__u8 factor6;
__u8 factor7;
__u8 factor8;
__u8 reserved2[3];
__u8 reserved3[10];
} __attribute__ ((packed));
struct dasd_eckd_confdata {
struct {
struct {
unsigned char identifier:2;
unsigned char token_id:1;
unsigned char sno_valid:1;
unsigned char subst_sno:1;
unsigned char recNED:1;
unsigned char emuNED:1;
unsigned char reserved:1;
} __attribute__ ((packed)) flags;
__u8 descriptor;
__u8 dev_class;
__u8 reserved;
unsigned char dev_type[6];
unsigned char dev_model[3];
unsigned char HDA_manufacturer[3];
unsigned char HDA_location[2];
unsigned char HDA_seqno[12];
__u16 ID;
} __attribute__ ((packed)) ned1;
struct {
struct {
unsigned char identifier:2;
unsigned char token_id:1;
unsigned char sno_valid:1;
unsigned char subst_sno:1;
unsigned char recNED:1;
unsigned char emuNED:1;
unsigned char reserved:1;
} __attribute__ ((packed)) flags;
__u8 descriptor;
__u8 reserved[2];
unsigned char dev_type[6];
unsigned char dev_model[3];
unsigned char DASD_manufacturer[3];
unsigned char DASD_location[2];
unsigned char DASD_seqno[12];
__u16 ID;
} __attribute__ ((packed)) ned2;
struct {
struct {
unsigned char identifier:2;
unsigned char token_id:1;
unsigned char sno_valid:1;
unsigned char subst_sno:1;
unsigned char recNED:1;
unsigned char emuNED:1;
unsigned char reserved:1;
} __attribute__ ((packed)) flags;
__u8 descriptor;
__u8 reserved[2];
unsigned char cont_type[6];
unsigned char cont_model[3];
unsigned char cont_manufacturer[3];
unsigned char cont_location[2];
unsigned char cont_seqno[12];
__u16 ID;
} __attribute__ ((packed)) ned3;
struct {
struct {
unsigned char identifier:2;
unsigned char token_id:1;
unsigned char sno_valid:1;
unsigned char subst_sno:1;
unsigned char recNED:1;
unsigned char emuNED:1;
unsigned char reserved:1;
} __attribute__ ((packed)) flags;
__u8 descriptor;
__u8 reserved[2];
unsigned char cont_type[6];
unsigned char empty[3];
unsigned char cont_manufacturer[3];
unsigned char cont_location[2];
unsigned char cont_seqno[12];
__u16 ID;
} __attribute__ ((packed)) ned4;
unsigned char ned5[32];
unsigned char ned6[32];
unsigned char ned7[32];
struct {
struct {
unsigned char identifier:2;
unsigned char reserved:6;
} __attribute__ ((packed)) flags;
__u8 selector;
__u16 interfaceID;
__u32 reserved;
__u16 subsystemID;
struct {
unsigned char sp0:1;
unsigned char sp1:1;
unsigned char reserved:5;
unsigned char scluster:1;
} __attribute__ ((packed)) spathID;
__u8 unit_address;
__u8 dev_ID;
__u8 dev_address;
__u8 adapterID;
__u16 link_address;
struct {
unsigned char parallel:1;
unsigned char escon:1;
unsigned char reserved:1;
unsigned char ficon:1;
unsigned char reserved2:4;
} __attribute__ ((packed)) protocol_type;
struct {
unsigned char PID_in_236:1;
unsigned char reserved:7;
} __attribute__ ((packed)) format_flags;
__u8 log_dev_address;
unsigned char reserved2[12];
} __attribute__ ((packed)) neq;
} __attribute__ ((packed));
struct dasd_eckd_path {
__u8 opm;
__u8 ppm;
__u8 npm;
};
/*
* Perform Subsystem Function - Prepare for Read Subsystem Data
*/
struct dasd_psf_prssd_data {
unsigned char order;
unsigned char flags;
unsigned char reserved[4];
unsigned char suborder;
unsigned char varies[9];
} __attribute__ ((packed));
#endif /* DASD_ECKD_H */

View file

@ -0,0 +1,254 @@
/*
* File...........: linux/drivers/s390/block/dasd.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Horst Hummel <Horst.Hummel@de.ibm.com>
* Carsten Otte <Cotte@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
*
* $Revision: 1.14 $
*/
#include <linux/config.h>
#include <linux/ctype.h>
#include <linux/init.h>
#include <asm/debug.h>
#include <asm/ebcdic.h>
#include <asm/uaccess.h>
/* This is ugly... */
#define PRINTK_HEADER "dasd_erp:"
#include "dasd_int.h"
struct dasd_ccw_req *
dasd_alloc_erp_request(char *magic, int cplength, int datasize,
struct dasd_device * device)
{
unsigned long flags;
struct dasd_ccw_req *cqr;
char *data;
int size;
/* Sanity checks */
if ( magic == NULL || datasize > PAGE_SIZE ||
(cplength*sizeof(struct ccw1)) > PAGE_SIZE)
BUG();
size = (sizeof(struct dasd_ccw_req) + 7L) & -8L;
if (cplength > 0)
size += cplength * sizeof(struct ccw1);
if (datasize > 0)
size += datasize;
spin_lock_irqsave(&device->mem_lock, flags);
cqr = (struct dasd_ccw_req *)
dasd_alloc_chunk(&device->erp_chunks, size);
spin_unlock_irqrestore(&device->mem_lock, flags);
if (cqr == NULL)
return ERR_PTR(-ENOMEM);
memset(cqr, 0, sizeof(struct dasd_ccw_req));
data = (char *) cqr + ((sizeof(struct dasd_ccw_req) + 7L) & -8L);
cqr->cpaddr = NULL;
if (cplength > 0) {
cqr->cpaddr = (struct ccw1 *) data;
data += cplength*sizeof(struct ccw1);
memset(cqr->cpaddr, 0, cplength*sizeof(struct ccw1));
}
cqr->data = NULL;
if (datasize > 0) {
cqr->data = data;
memset(cqr->data, 0, datasize);
}
strncpy((char *) &cqr->magic, magic, 4);
ASCEBC((char *) &cqr->magic, 4);
set_bit(DASD_CQR_FLAGS_USE_ERP, &cqr->flags);
dasd_get_device(device);
return cqr;
}
void
dasd_free_erp_request(struct dasd_ccw_req * cqr, struct dasd_device * device)
{
unsigned long flags;
spin_lock_irqsave(&device->mem_lock, flags);
dasd_free_chunk(&device->erp_chunks, cqr);
spin_unlock_irqrestore(&device->mem_lock, flags);
atomic_dec(&device->ref_count);
}
/*
* dasd_default_erp_action just retries the current cqr
*/
struct dasd_ccw_req *
dasd_default_erp_action(struct dasd_ccw_req * cqr)
{
struct dasd_device *device;
device = cqr->device;
/* just retry - there is nothing to save ... I got no sense data.... */
if (cqr->retries > 0) {
DEV_MESSAGE (KERN_DEBUG, device,
"default ERP called (%i retries left)",
cqr->retries);
cqr->lpm = LPM_ANYPATH;
cqr->status = DASD_CQR_QUEUED;
} else {
DEV_MESSAGE (KERN_WARNING, device, "%s",
"default ERP called (NO retry left)");
cqr->status = DASD_CQR_FAILED;
cqr->stopclk = get_clock ();
}
return cqr;
} /* end dasd_default_erp_action */
/*
* DESCRIPTION
* Frees all ERPs of the current ERP Chain and set the status
* of the original CQR either to DASD_CQR_DONE if ERP was successful
* or to DASD_CQR_FAILED if ERP was NOT successful.
* NOTE: This function is only called if no discipline postaction
* is available
*
* PARAMETER
* erp current erp_head
*
* RETURN VALUES
* cqr pointer to the original CQR
*/
struct dasd_ccw_req *
dasd_default_erp_postaction(struct dasd_ccw_req * cqr)
{
struct dasd_device *device;
int success;
if (cqr->refers == NULL || cqr->function == NULL)
BUG();
device = cqr->device;
success = cqr->status == DASD_CQR_DONE;
/* free all ERPs - but NOT the original cqr */
while (cqr->refers != NULL) {
struct dasd_ccw_req *refers;
refers = cqr->refers;
/* remove the request from the device queue */
list_del(&cqr->list);
/* free the finished erp request */
dasd_free_erp_request(cqr, device);
cqr = refers;
}
/* set corresponding status to original cqr */
if (success)
cqr->status = DASD_CQR_DONE;
else {
cqr->status = DASD_CQR_FAILED;
cqr->stopclk = get_clock();
}
return cqr;
} /* end default_erp_postaction */
/*
* Print the hex dump of the memory used by a request. This includes
* all error recovery ccws that have been chained in from of the
* real request.
*/
static inline void
hex_dump_memory(struct dasd_device *device, void *data, int len)
{
int *pint;
pint = (int *) data;
while (len > 0) {
DEV_MESSAGE(KERN_ERR, device, "%p: %08x %08x %08x %08x",
pint, pint[0], pint[1], pint[2], pint[3]);
pint += 4;
len -= 16;
}
}
void
dasd_log_sense(struct dasd_ccw_req *cqr, struct irb *irb)
{
struct dasd_device *device;
device = cqr->device;
/* dump sense data */
if (device->discipline && device->discipline->dump_sense)
device->discipline->dump_sense(device, cqr, irb);
}
void
dasd_log_ccw(struct dasd_ccw_req * cqr, int caller, __u32 cpa)
{
struct dasd_device *device;
struct dasd_ccw_req *lcqr;
struct ccw1 *ccw;
int cplength;
device = cqr->device;
/* log the channel program */
for (lcqr = cqr; lcqr != NULL; lcqr = lcqr->refers) {
DEV_MESSAGE(KERN_ERR, device,
"(%s) ERP chain report for req: %p",
caller == 0 ? "EXAMINE" : "ACTION", lcqr);
hex_dump_memory(device, lcqr, sizeof(struct dasd_ccw_req));
cplength = 1;
ccw = lcqr->cpaddr;
while (ccw++->flags & (CCW_FLAG_DC | CCW_FLAG_CC))
cplength++;
if (cplength > 40) { /* log only parts of the CP */
DEV_MESSAGE(KERN_ERR, device, "%s",
"Start of channel program:");
hex_dump_memory(device, lcqr->cpaddr,
40*sizeof(struct ccw1));
DEV_MESSAGE(KERN_ERR, device, "%s",
"End of channel program:");
hex_dump_memory(device, lcqr->cpaddr + cplength - 10,
10*sizeof(struct ccw1));
} else { /* log the whole CP */
DEV_MESSAGE(KERN_ERR, device, "%s",
"Channel program (complete):");
hex_dump_memory(device, lcqr->cpaddr,
cplength*sizeof(struct ccw1));
}
if (lcqr != cqr)
continue;
/*
* Log bytes arround failed CCW but only if we did
* not log the whole CP of the CCW is outside the
* logged CP.
*/
if (cplength > 40 ||
((addr_t) cpa < (addr_t) lcqr->cpaddr &&
(addr_t) cpa > (addr_t) (lcqr->cpaddr + cplength + 4))) {
DEV_MESSAGE(KERN_ERR, device,
"Failed CCW (%p) (area):",
(void *) (long) cpa);
hex_dump_memory(device, cqr->cpaddr - 10,
20*sizeof(struct ccw1));
}
}
} /* end log_erp_chain */
EXPORT_SYMBOL(dasd_default_erp_action);
EXPORT_SYMBOL(dasd_default_erp_postaction);
EXPORT_SYMBOL(dasd_alloc_erp_request);
EXPORT_SYMBOL(dasd_free_erp_request);
EXPORT_SYMBOL(dasd_log_sense);
EXPORT_SYMBOL(dasd_log_ccw);

View file

@ -0,0 +1,607 @@
/*
* File...........: linux/drivers/s390/block/dasd_fba.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
*
* $Revision: 1.39 $
*/
#include <linux/config.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <asm/debug.h>
#include <linux/slab.h>
#include <linux/hdreg.h> /* HDIO_GETGEO */
#include <linux/bio.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/idals.h>
#include <asm/ebcdic.h>
#include <asm/io.h>
#include <asm/todclk.h>
#include <asm/ccwdev.h>
#include "dasd_int.h"
#include "dasd_fba.h"
#ifdef PRINTK_HEADER
#undef PRINTK_HEADER
#endif /* PRINTK_HEADER */
#define PRINTK_HEADER "dasd(fba):"
#define DASD_FBA_CCW_WRITE 0x41
#define DASD_FBA_CCW_READ 0x42
#define DASD_FBA_CCW_LOCATE 0x43
#define DASD_FBA_CCW_DEFINE_EXTENT 0x63
MODULE_LICENSE("GPL");
static struct dasd_discipline dasd_fba_discipline;
struct dasd_fba_private {
struct dasd_fba_characteristics rdc_data;
};
static struct ccw_device_id dasd_fba_ids[] = {
{ CCW_DEVICE_DEVTYPE (0x6310, 0, 0x9336, 0), driver_info: 0x1},
{ CCW_DEVICE_DEVTYPE (0x3880, 0, 0x3370, 0), driver_info: 0x2},
{ /* end of list */ },
};
MODULE_DEVICE_TABLE(ccw, dasd_fba_ids);
static struct ccw_driver dasd_fba_driver; /* see below */
static int
dasd_fba_probe(struct ccw_device *cdev)
{
int ret;
ret = dasd_generic_probe (cdev, &dasd_fba_discipline);
if (ret)
return ret;
ccw_device_set_options(cdev, CCWDEV_DO_PATHGROUP);
return 0;
}
static int
dasd_fba_set_online(struct ccw_device *cdev)
{
return dasd_generic_set_online (cdev, &dasd_fba_discipline);
}
static struct ccw_driver dasd_fba_driver = {
.name = "dasd-fba",
.owner = THIS_MODULE,
.ids = dasd_fba_ids,
.probe = dasd_fba_probe,
.remove = dasd_generic_remove,
.set_offline = dasd_generic_set_offline,
.set_online = dasd_fba_set_online,
.notify = dasd_generic_notify,
};
static inline void
define_extent(struct ccw1 * ccw, struct DE_fba_data *data, int rw,
int blksize, int beg, int nr)
{
ccw->cmd_code = DASD_FBA_CCW_DEFINE_EXTENT;
ccw->flags = 0;
ccw->count = 16;
ccw->cda = (__u32) __pa(data);
memset(data, 0, sizeof (struct DE_fba_data));
if (rw == WRITE)
(data->mask).perm = 0x0;
else if (rw == READ)
(data->mask).perm = 0x1;
else
data->mask.perm = 0x2;
data->blk_size = blksize;
data->ext_loc = beg;
data->ext_end = nr - 1;
}
static inline void
locate_record(struct ccw1 * ccw, struct LO_fba_data *data, int rw,
int block_nr, int block_ct)
{
ccw->cmd_code = DASD_FBA_CCW_LOCATE;
ccw->flags = 0;
ccw->count = 8;
ccw->cda = (__u32) __pa(data);
memset(data, 0, sizeof (struct LO_fba_data));
if (rw == WRITE)
data->operation.cmd = 0x5;
else if (rw == READ)
data->operation.cmd = 0x6;
else
data->operation.cmd = 0x8;
data->blk_nr = block_nr;
data->blk_ct = block_ct;
}
static int
dasd_fba_check_characteristics(struct dasd_device *device)
{
struct dasd_fba_private *private;
struct ccw_device *cdev = device->cdev;
void *rdc_data;
int rc;
private = (struct dasd_fba_private *) device->private;
if (private == NULL) {
private = kmalloc(sizeof(struct dasd_fba_private), GFP_KERNEL);
if (private == NULL) {
DEV_MESSAGE(KERN_WARNING, device, "%s",
"memory allocation failed for private "
"data");
return -ENOMEM;
}
device->private = (void *) private;
}
/* Read Device Characteristics */
rdc_data = (void *) &(private->rdc_data);
rc = read_dev_chars(device->cdev, &rdc_data, 32);
if (rc) {
DEV_MESSAGE(KERN_WARNING, device,
"Read device characteristics returned error %d",
rc);
return rc;
}
DEV_MESSAGE(KERN_INFO, device,
"%04X/%02X(CU:%04X/%02X) %dMB at(%d B/blk)",
cdev->id.dev_type,
cdev->id.dev_model,
cdev->id.cu_type,
cdev->id.cu_model,
((private->rdc_data.blk_bdsa *
(private->rdc_data.blk_size >> 9)) >> 11),
private->rdc_data.blk_size);
return 0;
}
static int
dasd_fba_do_analysis(struct dasd_device *device)
{
struct dasd_fba_private *private;
int sb, rc;
private = (struct dasd_fba_private *) device->private;
rc = dasd_check_blocksize(private->rdc_data.blk_size);
if (rc) {
DEV_MESSAGE(KERN_INFO, device, "unknown blocksize %d",
private->rdc_data.blk_size);
return rc;
}
device->blocks = private->rdc_data.blk_bdsa;
device->bp_block = private->rdc_data.blk_size;
device->s2b_shift = 0; /* bits to shift 512 to get a block */
for (sb = 512; sb < private->rdc_data.blk_size; sb = sb << 1)
device->s2b_shift++;
return 0;
}
static int
dasd_fba_fill_geometry(struct dasd_device *device, struct hd_geometry *geo)
{
if (dasd_check_blocksize(device->bp_block) != 0)
return -EINVAL;
geo->cylinders = (device->blocks << device->s2b_shift) >> 10;
geo->heads = 16;
geo->sectors = 128 >> device->s2b_shift;
return 0;
}
static dasd_era_t
dasd_fba_examine_error(struct dasd_ccw_req * cqr, struct irb * irb)
{
struct dasd_device *device;
struct ccw_device *cdev;
device = (struct dasd_device *) cqr->device;
if (irb->scsw.cstat == 0x00 &&
irb->scsw.dstat == (DEV_STAT_CHN_END | DEV_STAT_DEV_END))
return dasd_era_none;
cdev = device->cdev;
switch (cdev->id.dev_type) {
case 0x3370:
return dasd_3370_erp_examine(cqr, irb);
case 0x9336:
return dasd_9336_erp_examine(cqr, irb);
default:
return dasd_era_recover;
}
}
static dasd_erp_fn_t
dasd_fba_erp_action(struct dasd_ccw_req * cqr)
{
return dasd_default_erp_action;
}
static dasd_erp_fn_t
dasd_fba_erp_postaction(struct dasd_ccw_req * cqr)
{
if (cqr->function == dasd_default_erp_action)
return dasd_default_erp_postaction;
DEV_MESSAGE(KERN_WARNING, cqr->device, "unknown ERP action %p",
cqr->function);
return NULL;
}
static struct dasd_ccw_req *
dasd_fba_build_cp(struct dasd_device * device, struct request *req)
{
struct dasd_fba_private *private;
unsigned long *idaws;
struct LO_fba_data *LO_data;
struct dasd_ccw_req *cqr;
struct ccw1 *ccw;
struct bio *bio;
struct bio_vec *bv;
char *dst;
int count, cidaw, cplength, datasize;
sector_t recid, first_rec, last_rec;
unsigned int blksize, off;
unsigned char cmd;
int i;
private = (struct dasd_fba_private *) device->private;
if (rq_data_dir(req) == READ) {
cmd = DASD_FBA_CCW_READ;
} else if (rq_data_dir(req) == WRITE) {
cmd = DASD_FBA_CCW_WRITE;
} else
return ERR_PTR(-EINVAL);
blksize = device->bp_block;
/* Calculate record id of first and last block. */
first_rec = req->sector >> device->s2b_shift;
last_rec = (req->sector + req->nr_sectors - 1) >> device->s2b_shift;
/* Check struct bio and count the number of blocks for the request. */
count = 0;
cidaw = 0;
rq_for_each_bio(bio, req) {
bio_for_each_segment(bv, bio, i) {
if (bv->bv_len & (blksize - 1))
/* Fba can only do full blocks. */
return ERR_PTR(-EINVAL);
count += bv->bv_len >> (device->s2b_shift + 9);
#if defined(CONFIG_ARCH_S390X)
if (idal_is_needed (page_address(bv->bv_page),
bv->bv_len))
cidaw += bv->bv_len / blksize;
#endif
}
}
/* Paranoia. */
if (count != last_rec - first_rec + 1)
return ERR_PTR(-EINVAL);
/* 1x define extent + 1x locate record + number of blocks */
cplength = 2 + count;
/* 1x define extent + 1x locate record */
datasize = sizeof(struct DE_fba_data) + sizeof(struct LO_fba_data) +
cidaw * sizeof(unsigned long);
/*
* Find out number of additional locate record ccws if the device
* can't do data chaining.
*/
if (private->rdc_data.mode.bits.data_chain == 0) {
cplength += count - 1;
datasize += (count - 1)*sizeof(struct LO_fba_data);
}
/* Allocate the ccw request. */
cqr = dasd_smalloc_request(dasd_fba_discipline.name,
cplength, datasize, device);
if (IS_ERR(cqr))
return cqr;
ccw = cqr->cpaddr;
/* First ccw is define extent. */
define_extent(ccw++, cqr->data, rq_data_dir(req),
device->bp_block, req->sector, req->nr_sectors);
/* Build locate_record + read/write ccws. */
idaws = (unsigned long *) (cqr->data + sizeof(struct DE_fba_data));
LO_data = (struct LO_fba_data *) (idaws + cidaw);
/* Locate record for all blocks for smart devices. */
if (private->rdc_data.mode.bits.data_chain != 0) {
ccw[-1].flags |= CCW_FLAG_CC;
locate_record(ccw++, LO_data++, rq_data_dir(req), 0, count);
}
recid = first_rec;
rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) {
dst = page_address(bv->bv_page) + bv->bv_offset;
if (dasd_page_cache) {
char *copy = kmem_cache_alloc(dasd_page_cache,
SLAB_DMA | __GFP_NOWARN);
if (copy && rq_data_dir(req) == WRITE)
memcpy(copy + bv->bv_offset, dst, bv->bv_len);
if (copy)
dst = copy + bv->bv_offset;
}
for (off = 0; off < bv->bv_len; off += blksize) {
/* Locate record for stupid devices. */
if (private->rdc_data.mode.bits.data_chain == 0) {
ccw[-1].flags |= CCW_FLAG_CC;
locate_record(ccw, LO_data++,
rq_data_dir(req),
recid - first_rec, 1);
ccw->flags = CCW_FLAG_CC;
ccw++;
} else {
if (recid > first_rec)
ccw[-1].flags |= CCW_FLAG_DC;
else
ccw[-1].flags |= CCW_FLAG_CC;
}
ccw->cmd_code = cmd;
ccw->count = device->bp_block;
if (idal_is_needed(dst, blksize)) {
ccw->cda = (__u32)(addr_t) idaws;
ccw->flags = CCW_FLAG_IDA;
idaws = idal_create_words(idaws, dst, blksize);
} else {
ccw->cda = (__u32)(addr_t) dst;
ccw->flags = 0;
}
ccw++;
dst += blksize;
recid++;
}
}
cqr->device = device;
cqr->expires = 5 * 60 * HZ; /* 5 minutes */
cqr->status = DASD_CQR_FILLED;
return cqr;
}
static int
dasd_fba_free_cp(struct dasd_ccw_req *cqr, struct request *req)
{
struct dasd_fba_private *private;
struct ccw1 *ccw;
struct bio *bio;
struct bio_vec *bv;
char *dst, *cda;
unsigned int blksize, off;
int i, status;
if (!dasd_page_cache)
goto out;
private = (struct dasd_fba_private *) cqr->device->private;
blksize = cqr->device->bp_block;
ccw = cqr->cpaddr;
/* Skip over define extent & locate record. */
ccw++;
if (private->rdc_data.mode.bits.data_chain != 0)
ccw++;
rq_for_each_bio(bio, req) bio_for_each_segment(bv, bio, i) {
dst = page_address(bv->bv_page) + bv->bv_offset;
for (off = 0; off < bv->bv_len; off += blksize) {
/* Skip locate record. */
if (private->rdc_data.mode.bits.data_chain == 0)
ccw++;
if (dst) {
if (ccw->flags & CCW_FLAG_IDA)
cda = *((char **)((addr_t) ccw->cda));
else
cda = (char *)((addr_t) ccw->cda);
if (dst != cda) {
if (rq_data_dir(req) == READ)
memcpy(dst, cda, bv->bv_len);
kmem_cache_free(dasd_page_cache,
(void *)((addr_t)cda & PAGE_MASK));
}
dst = NULL;
}
ccw++;
}
}
out:
status = cqr->status == DASD_CQR_DONE;
dasd_sfree_request(cqr, cqr->device);
return status;
}
static int
dasd_fba_fill_info(struct dasd_device * device,
struct dasd_information2_t * info)
{
info->label_block = 1;
info->FBA_layout = 1;
info->format = DASD_FORMAT_LDL;
info->characteristics_size = sizeof(struct dasd_fba_characteristics);
memcpy(info->characteristics,
&((struct dasd_fba_private *) device->private)->rdc_data,
sizeof (struct dasd_fba_characteristics));
info->confdata_size = 0;
return 0;
}
static void
dasd_fba_dump_sense(struct dasd_device *device, struct dasd_ccw_req * req,
struct irb *irb)
{
char *page;
struct ccw1 *act, *end, *last;
int len, sl, sct, count;
page = (char *) get_zeroed_page(GFP_ATOMIC);
if (page == NULL) {
DEV_MESSAGE(KERN_ERR, device, " %s",
"No memory to dump sense data");
return;
}
len = sprintf(page, KERN_ERR PRINTK_HEADER
" I/O status report for device %s:\n",
device->cdev->dev.bus_id);
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
" in req: %p CS: 0x%02X DS: 0x%02X\n", req,
irb->scsw.cstat, irb->scsw.dstat);
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
" device %s: Failing CCW: %p\n",
device->cdev->dev.bus_id,
(void *) (addr_t) irb->scsw.cpa);
if (irb->esw.esw0.erw.cons) {
for (sl = 0; sl < 4; sl++) {
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
" Sense(hex) %2d-%2d:",
(8 * sl), ((8 * sl) + 7));
for (sct = 0; sct < 8; sct++) {
len += sprintf(page + len, " %02x",
irb->ecw[8 * sl + sct]);
}
len += sprintf(page + len, "\n");
}
} else {
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
" SORRY - NO VALID SENSE AVAILABLE\n");
}
MESSAGE_LOG(KERN_ERR, "%s",
page + sizeof(KERN_ERR PRINTK_HEADER));
/* dump the Channel Program */
/* print first CCWs (maximum 8) */
act = req->cpaddr;
for (last = act; last->flags & (CCW_FLAG_CC | CCW_FLAG_DC); last++);
end = min(act + 8, last);
len = sprintf(page, KERN_ERR PRINTK_HEADER
" Related CP in req: %p\n", req);
while (act <= end) {
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
" CCW %p: %08X %08X DAT:",
act, ((int *) act)[0], ((int *) act)[1]);
for (count = 0; count < 32 && count < act->count;
count += sizeof(int))
len += sprintf(page + len, " %08X",
((int *) (addr_t) act->cda)
[(count>>2)]);
len += sprintf(page + len, "\n");
act++;
}
MESSAGE_LOG(KERN_ERR, "%s",
page + sizeof(KERN_ERR PRINTK_HEADER));
/* print failing CCW area */
len = 0;
if (act < ((struct ccw1 *)(addr_t) irb->scsw.cpa) - 2) {
act = ((struct ccw1 *)(addr_t) irb->scsw.cpa) - 2;
len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n");
}
end = min((struct ccw1 *)(addr_t) irb->scsw.cpa + 2, last);
while (act <= end) {
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
" CCW %p: %08X %08X DAT:",
act, ((int *) act)[0], ((int *) act)[1]);
for (count = 0; count < 32 && count < act->count;
count += sizeof(int))
len += sprintf(page + len, " %08X",
((int *) (addr_t) act->cda)
[(count>>2)]);
len += sprintf(page + len, "\n");
act++;
}
/* print last CCWs */
if (act < last - 2) {
act = last - 2;
len += sprintf(page + len, KERN_ERR PRINTK_HEADER "......\n");
}
while (act <= last) {
len += sprintf(page + len, KERN_ERR PRINTK_HEADER
" CCW %p: %08X %08X DAT:",
act, ((int *) act)[0], ((int *) act)[1]);
for (count = 0; count < 32 && count < act->count;
count += sizeof(int))
len += sprintf(page + len, " %08X",
((int *) (addr_t) act->cda)
[(count>>2)]);
len += sprintf(page + len, "\n");
act++;
}
if (len > 0)
MESSAGE_LOG(KERN_ERR, "%s",
page + sizeof(KERN_ERR PRINTK_HEADER));
free_page((unsigned long) page);
}
/*
* max_blocks is dependent on the amount of storage that is available
* in the static io buffer for each device. Currently each device has
* 8192 bytes (=2 pages). For 64 bit one dasd_mchunkt_t structure has
* 24 bytes, the struct dasd_ccw_req has 136 bytes and each block can use
* up to 16 bytes (8 for the ccw and 8 for the idal pointer). In
* addition we have one define extent ccw + 16 bytes of data and a
* locate record ccw for each block (stupid devices!) + 16 bytes of data.
* That makes:
* (8192 - 24 - 136 - 8 - 16) / 40 = 200.2 blocks at maximum.
* We want to fit two into the available memory so that we can immediately
* start the next request if one finishes off. That makes 100.1 blocks
* for one request. Give a little safety and the result is 96.
*/
static struct dasd_discipline dasd_fba_discipline = {
.owner = THIS_MODULE,
.name = "FBA ",
.ebcname = "FBA ",
.max_blocks = 96,
.check_device = dasd_fba_check_characteristics,
.do_analysis = dasd_fba_do_analysis,
.fill_geometry = dasd_fba_fill_geometry,
.start_IO = dasd_start_IO,
.term_IO = dasd_term_IO,
.examine_error = dasd_fba_examine_error,
.erp_action = dasd_fba_erp_action,
.erp_postaction = dasd_fba_erp_postaction,
.build_cp = dasd_fba_build_cp,
.free_cp = dasd_fba_free_cp,
.dump_sense = dasd_fba_dump_sense,
.fill_info = dasd_fba_fill_info,
};
static int __init
dasd_fba_init(void)
{
int ret;
ASCEBC(dasd_fba_discipline.ebcname, 4);
ret = ccw_driver_register(&dasd_fba_driver);
if (ret)
return ret;
dasd_generic_auto_online(&dasd_fba_driver);
return 0;
}
static void __exit
dasd_fba_cleanup(void)
{
ccw_driver_unregister(&dasd_fba_driver);
}
module_init(dasd_fba_init);
module_exit(dasd_fba_cleanup);
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: 1
* tab-width: 8
* End:
*/

View file

@ -0,0 +1,73 @@
/*
* File...........: linux/drivers/s390/block/dasd_fba.h
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
*
* $Revision: 1.6 $
*/
#ifndef DASD_FBA_H
#define DASD_FBA_H
struct DE_fba_data {
struct {
unsigned char perm:2; /* Permissions on this extent */
unsigned char zero:2; /* Must be zero */
unsigned char da:1; /* usually zero */
unsigned char diag:1; /* allow diagnose */
unsigned char zero2:2; /* zero */
} __attribute__ ((packed)) mask;
__u8 zero; /* Must be zero */
__u16 blk_size; /* Blocksize */
__u32 ext_loc; /* Extent locator */
__u32 ext_beg; /* logical number of block 0 in extent */
__u32 ext_end; /* logocal number of last block in extent */
} __attribute__ ((packed));
struct LO_fba_data {
struct {
unsigned char zero:4;
unsigned char cmd:4;
} __attribute__ ((packed)) operation;
__u8 auxiliary;
__u16 blk_ct;
__u32 blk_nr;
} __attribute__ ((packed));
struct dasd_fba_characteristics {
union {
__u8 c;
struct {
unsigned char reserved:1;
unsigned char overrunnable:1;
unsigned char burst_byte:1;
unsigned char data_chain:1;
unsigned char zeros:4;
} __attribute__ ((packed)) bits;
} __attribute__ ((packed)) mode;
union {
__u8 c;
struct {
unsigned char zero0:1;
unsigned char removable:1;
unsigned char shared:1;
unsigned char zero1:1;
unsigned char mam:1;
unsigned char zeros:3;
} __attribute__ ((packed)) bits;
} __attribute__ ((packed)) features;
__u8 dev_class;
__u8 unit_type;
__u16 blk_size;
__u32 blk_per_cycl;
__u32 blk_per_bound;
__u32 blk_bdsa;
__u32 reserved0;
__u16 reserved1;
__u16 blk_ce;
__u32 reserved2;
__u16 reserved3;
} __attribute__ ((packed));
#endif /* DASD_FBA_H */

View file

@ -0,0 +1,185 @@
/*
* File...........: linux/drivers/s390/block/dasd_genhd.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Horst Hummel <Horst.Hummel@de.ibm.com>
* Carsten Otte <Cotte@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
*
* gendisk related functions for the dasd driver.
*
* $Revision: 1.48 $
*/
#include <linux/config.h>
#include <linux/interrupt.h>
#include <linux/fs.h>
#include <linux/blkpg.h>
#include <asm/uaccess.h>
/* This is ugly... */
#define PRINTK_HEADER "dasd_gendisk:"
#include "dasd_int.h"
/*
* Allocate and register gendisk structure for device.
*/
int
dasd_gendisk_alloc(struct dasd_device *device)
{
struct gendisk *gdp;
int len;
/* Make sure the minor for this device exists. */
if (device->devindex >= DASD_PER_MAJOR)
return -EBUSY;
gdp = alloc_disk(1 << DASD_PARTN_BITS);
if (!gdp)
return -ENOMEM;
/* Initialize gendisk structure. */
gdp->major = DASD_MAJOR;
gdp->first_minor = device->devindex << DASD_PARTN_BITS;
gdp->fops = &dasd_device_operations;
gdp->driverfs_dev = &device->cdev->dev;
/*
* Set device name.
* dasda - dasdz : 26 devices
* dasdaa - dasdzz : 676 devices, added up = 702
* dasdaaa - dasdzzz : 17576 devices, added up = 18278
* dasdaaaa - dasdzzzz : 456976 devices, added up = 475252
*/
len = sprintf(gdp->disk_name, "dasd");
if (device->devindex > 25) {
if (device->devindex > 701) {
if (device->devindex > 18277)
len += sprintf(gdp->disk_name + len, "%c",
'a'+(((device->devindex-18278)
/17576)%26));
len += sprintf(gdp->disk_name + len, "%c",
'a'+(((device->devindex-702)/676)%26));
}
len += sprintf(gdp->disk_name + len, "%c",
'a'+(((device->devindex-26)/26)%26));
}
len += sprintf(gdp->disk_name + len, "%c", 'a'+(device->devindex%26));
sprintf(gdp->devfs_name, "dasd/%s", device->cdev->dev.bus_id);
if (test_bit(DASD_FLAG_RO, &device->flags))
set_disk_ro(gdp, 1);
gdp->private_data = device;
gdp->queue = device->request_queue;
device->gdp = gdp;
set_capacity(device->gdp, 0);
add_disk(device->gdp);
return 0;
}
/*
* Unregister and free gendisk structure for device.
*/
void
dasd_gendisk_free(struct dasd_device *device)
{
del_gendisk(device->gdp);
device->gdp->queue = 0;
put_disk(device->gdp);
device->gdp = 0;
}
/*
* Trigger a partition detection.
*/
int
dasd_scan_partitions(struct dasd_device * device)
{
struct block_device *bdev;
/* Make the disk known. */
set_capacity(device->gdp, device->blocks << device->s2b_shift);
bdev = bdget_disk(device->gdp, 0);
if (!bdev || blkdev_get(bdev, FMODE_READ, 1) < 0)
return -ENODEV;
/*
* See fs/partition/check.c:register_disk,rescan_partitions
* Can't call rescan_partitions directly. Use ioctl.
*/
ioctl_by_bdev(bdev, BLKRRPART, 0);
/*
* Since the matching blkdev_put call to the blkdev_get in
* this function is not called before dasd_destroy_partitions
* the offline open_count limit needs to be increased from
* 0 to 1. This is done by setting device->bdev (see
* dasd_generic_set_offline). As long as the partition
* detection is running no offline should be allowed. That
* is why the assignment to device->bdev is done AFTER
* the BLKRRPART ioctl.
*/
device->bdev = bdev;
return 0;
}
/*
* Remove all inodes in the system for a device, delete the
* partitions and make device unusable by setting its size to zero.
*/
void
dasd_destroy_partitions(struct dasd_device * device)
{
/* The two structs have 168/176 byte on 31/64 bit. */
struct blkpg_partition bpart;
struct blkpg_ioctl_arg barg;
struct block_device *bdev;
/*
* Get the bdev pointer from the device structure and clear
* device->bdev to lower the offline open_count limit again.
*/
bdev = device->bdev;
device->bdev = 0;
/*
* See fs/partition/check.c:delete_partition
* Can't call delete_partitions directly. Use ioctl.
* The ioctl also does locking and invalidation.
*/
memset(&bpart, 0, sizeof(struct blkpg_partition));
memset(&barg, 0, sizeof(struct blkpg_ioctl_arg));
barg.data = &bpart;
barg.op = BLKPG_DEL_PARTITION;
for (bpart.pno = device->gdp->minors - 1; bpart.pno > 0; bpart.pno--)
ioctl_by_bdev(bdev, BLKPG, (unsigned long) &barg);
invalidate_partition(device->gdp, 0);
/* Matching blkdev_put to the blkdev_get in dasd_scan_partitions. */
blkdev_put(bdev);
set_capacity(device->gdp, 0);
}
int
dasd_gendisk_init(void)
{
int rc;
/* Register to static dasd major 94 */
rc = register_blkdev(DASD_MAJOR, "dasd");
if (rc != 0) {
MESSAGE(KERN_WARNING,
"Couldn't register successfully to "
"major no %d", DASD_MAJOR);
return rc;
}
return 0;
}
void
dasd_gendisk_exit(void)
{
unregister_blkdev(DASD_MAJOR, "dasd");
}

View file

@ -0,0 +1,576 @@
/*
* File...........: linux/drivers/s390/block/dasd_int.h
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Horst Hummel <Horst.Hummel@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999,2000
*
* $Revision: 1.63 $
*/
#ifndef DASD_INT_H
#define DASD_INT_H
#ifdef __KERNEL__
/* erp debugging in dasd.c and dasd_3990_erp.c */
#define ERP_DEBUG
/* we keep old device allocation scheme; IOW, minors are still in 0..255 */
#define DASD_PER_MAJOR (1U << (MINORBITS - DASD_PARTN_BITS))
#define DASD_PARTN_MASK ((1 << DASD_PARTN_BITS) - 1)
/*
* States a dasd device can have:
* new: the dasd_device structure is allocated.
* known: the discipline for the device is identified.
* basic: the device can do basic i/o.
* accept: the device is analysed (format is known).
* ready: partition detection is done and the device is can do block io.
* online: the device accepts requests from the block device queue.
*
* Things to do for startup state transitions:
* new -> known: find discipline for the device and create devfs entries.
* known -> basic: request irq line for the device.
* basic -> ready: do the initial analysis, e.g. format detection,
* do block device setup and detect partitions.
* ready -> online: schedule the device tasklet.
* Things to do for shutdown state transitions:
* online -> ready: just set the new device state.
* ready -> basic: flush requests from the block device layer, clear
* partition information and reset format information.
* basic -> known: terminate all requests and free irq.
* known -> new: remove devfs entries and forget discipline.
*/
#define DASD_STATE_NEW 0
#define DASD_STATE_KNOWN 1
#define DASD_STATE_BASIC 2
#define DASD_STATE_READY 3
#define DASD_STATE_ONLINE 4
#include <linux/module.h>
#include <linux/wait.h>
#include <linux/blkdev.h>
#include <linux/devfs_fs_kernel.h>
#include <linux/genhd.h>
#include <linux/hdreg.h>
#include <linux/interrupt.h>
#include <asm/ccwdev.h>
#include <linux/workqueue.h>
#include <asm/debug.h>
#include <asm/dasd.h>
#include <asm/idals.h>
/*
* SECTION: Type definitions
*/
struct dasd_device;
typedef int (*dasd_ioctl_fn_t) (struct block_device *bdev, int no, long args);
struct dasd_ioctl {
struct list_head list;
struct module *owner;
int no;
dasd_ioctl_fn_t handler;
};
typedef enum {
dasd_era_fatal = -1, /* no chance to recover */
dasd_era_none = 0, /* don't recover, everything alright */
dasd_era_msg = 1, /* don't recover, just report... */
dasd_era_recover = 2 /* recovery action recommended */
} dasd_era_t;
/* BIT DEFINITIONS FOR SENSE DATA */
#define DASD_SENSE_BIT_0 0x80
#define DASD_SENSE_BIT_1 0x40
#define DASD_SENSE_BIT_2 0x20
#define DASD_SENSE_BIT_3 0x10
/*
* SECTION: MACROs for klogd and s390 debug feature (dbf)
*/
#define DBF_DEV_EVENT(d_level, d_device, d_str, d_data...) \
do { \
debug_sprintf_event(d_device->debug_area, \
d_level, \
d_str "\n", \
d_data); \
} while(0)
#define DBF_DEV_EXC(d_level, d_device, d_str, d_data...) \
do { \
debug_sprintf_exception(d_device->debug_area, \
d_level, \
d_str "\n", \
d_data); \
} while(0)
#define DBF_EVENT(d_level, d_str, d_data...)\
do { \
debug_sprintf_event(dasd_debug_area, \
d_level,\
d_str "\n", \
d_data); \
} while(0)
#define DBF_EXC(d_level, d_str, d_data...)\
do { \
debug_sprintf_exception(dasd_debug_area, \
d_level,\
d_str "\n", \
d_data); \
} while(0)
/* definition of dbf debug levels */
#define DBF_EMERG 0 /* system is unusable */
#define DBF_ALERT 1 /* action must be taken immediately */
#define DBF_CRIT 2 /* critical conditions */
#define DBF_ERR 3 /* error conditions */
#define DBF_WARNING 4 /* warning conditions */
#define DBF_NOTICE 5 /* normal but significant condition */
#define DBF_INFO 6 /* informational */
#define DBF_DEBUG 6 /* debug-level messages */
/* messages to be written via klogd and dbf */
#define DEV_MESSAGE(d_loglevel,d_device,d_string,d_args...)\
do { \
printk(d_loglevel PRINTK_HEADER " %s: " d_string "\n", \
d_device->cdev->dev.bus_id, d_args); \
DBF_DEV_EVENT(DBF_ALERT, d_device, d_string, d_args); \
} while(0)
#define MESSAGE(d_loglevel,d_string,d_args...)\
do { \
printk(d_loglevel PRINTK_HEADER " " d_string "\n", d_args); \
DBF_EVENT(DBF_ALERT, d_string, d_args); \
} while(0)
/* messages to be written via klogd only */
#define DEV_MESSAGE_LOG(d_loglevel,d_device,d_string,d_args...)\
do { \
printk(d_loglevel PRINTK_HEADER " %s: " d_string "\n", \
d_device->cdev->dev.bus_id, d_args); \
} while(0)
#define MESSAGE_LOG(d_loglevel,d_string,d_args...)\
do { \
printk(d_loglevel PRINTK_HEADER " " d_string "\n", d_args); \
} while(0)
struct dasd_ccw_req {
unsigned int magic; /* Eye catcher */
struct list_head list; /* list_head for request queueing. */
/* Where to execute what... */
struct dasd_device *device; /* device the request is for */
struct ccw1 *cpaddr; /* address of channel program */
char status; /* status of this request */
short retries; /* A retry counter */
unsigned long flags; /* flags of this request */
/* ... and how */
unsigned long starttime; /* jiffies time of request start */
int expires; /* expiration period in jiffies */
char lpm; /* logical path mask */
void *data; /* pointer to data area */
/* these are important for recovering erroneous requests */
struct irb irb; /* device status in case of an error */
struct dasd_ccw_req *refers; /* ERP-chain queueing. */
void *function; /* originating ERP action */
/* these are for statistics only */
unsigned long long buildclk; /* TOD-clock of request generation */
unsigned long long startclk; /* TOD-clock of request start */
unsigned long long stopclk; /* TOD-clock of request interrupt */
unsigned long long endclk; /* TOD-clock of request termination */
/* Callback that is called after reaching final status. */
void (*callback)(struct dasd_ccw_req *, void *data);
void *callback_data;
};
/*
* dasd_ccw_req -> status can be:
*/
#define DASD_CQR_FILLED 0x00 /* request is ready to be processed */
#define DASD_CQR_QUEUED 0x01 /* request is queued to be processed */
#define DASD_CQR_IN_IO 0x02 /* request is currently in IO */
#define DASD_CQR_DONE 0x03 /* request is completed successfully */
#define DASD_CQR_ERROR 0x04 /* request is completed with error */
#define DASD_CQR_FAILED 0x05 /* request is finally failed */
#define DASD_CQR_CLEAR 0x06 /* request is clear pending */
/* per dasd_ccw_req flags */
#define DASD_CQR_FLAGS_USE_ERP 0 /* use ERP for this request */
/* Signature for error recovery functions. */
typedef struct dasd_ccw_req *(*dasd_erp_fn_t) (struct dasd_ccw_req *);
/*
* the struct dasd_discipline is
* sth like a table of virtual functions, if you think of dasd_eckd
* inheriting dasd...
* no, currently we are not planning to reimplement the driver in C++
*/
struct dasd_discipline {
struct module *owner;
char ebcname[8]; /* a name used for tagging and printks */
char name[8]; /* a name used for tagging and printks */
int max_blocks; /* maximum number of blocks to be chained */
struct list_head list; /* used for list of disciplines */
/*
* Device recognition functions. check_device is used to verify
* the sense data and the information returned by read device
* characteristics. It returns 0 if the discipline can be used
* for the device in question.
* do_analysis is used in the step from device state "basic" to
* state "accept". It returns 0 if the device can be made ready,
* it returns -EMEDIUMTYPE if the device can't be made ready or
* -EAGAIN if do_analysis started a ccw that needs to complete
* before the analysis may be repeated.
*/
int (*check_device)(struct dasd_device *);
int (*do_analysis) (struct dasd_device *);
/*
* Device operation functions. build_cp creates a ccw chain for
* a block device request, start_io starts the request and
* term_IO cancels it (e.g. in case of a timeout). format_device
* returns a ccw chain to be used to format the device.
*/
struct dasd_ccw_req *(*build_cp) (struct dasd_device *,
struct request *);
int (*start_IO) (struct dasd_ccw_req *);
int (*term_IO) (struct dasd_ccw_req *);
struct dasd_ccw_req *(*format_device) (struct dasd_device *,
struct format_data_t *);
int (*free_cp) (struct dasd_ccw_req *, struct request *);
/*
* Error recovery functions. examine_error() returns a value that
* indicates what to do for an error condition. If examine_error()
* returns 'dasd_era_recover' erp_action() is called to create a
* special error recovery ccw. erp_postaction() is called after
* an error recovery ccw has finished its execution. dump_sense
* is called for every error condition to print the sense data
* to the console.
*/
dasd_era_t(*examine_error) (struct dasd_ccw_req *, struct irb *);
dasd_erp_fn_t(*erp_action) (struct dasd_ccw_req *);
dasd_erp_fn_t(*erp_postaction) (struct dasd_ccw_req *);
void (*dump_sense) (struct dasd_device *, struct dasd_ccw_req *,
struct irb *);
/* i/o control functions. */
int (*fill_geometry) (struct dasd_device *, struct hd_geometry *);
int (*fill_info) (struct dasd_device *, struct dasd_information2_t *);
};
extern struct dasd_discipline *dasd_diag_discipline_pointer;
struct dasd_device {
/* Block device stuff. */
struct gendisk *gdp;
request_queue_t *request_queue;
spinlock_t request_queue_lock;
struct block_device *bdev;
unsigned int devindex;
unsigned long blocks; /* size of volume in blocks */
unsigned int bp_block; /* bytes per block */
unsigned int s2b_shift; /* log2 (bp_block/512) */
unsigned long flags; /* per device flags */
/* Device discipline stuff. */
struct dasd_discipline *discipline;
char *private;
/* Device state and target state. */
int state, target;
int stopped; /* device (ccw_device_start) was stopped */
/* Open and reference count. */
atomic_t ref_count;
atomic_t open_count;
/* ccw queue and memory for static ccw/erp buffers. */
struct list_head ccw_queue;
spinlock_t mem_lock;
void *ccw_mem;
void *erp_mem;
struct list_head ccw_chunks;
struct list_head erp_chunks;
atomic_t tasklet_scheduled;
struct tasklet_struct tasklet;
struct work_struct kick_work;
struct timer_list timer;
debug_info_t *debug_area;
struct ccw_device *cdev;
#ifdef CONFIG_DASD_PROFILE
struct dasd_profile_info_t profile;
#endif
};
/* reasons why device (ccw_device_start) was stopped */
#define DASD_STOPPED_NOT_ACC 1 /* not accessible */
#define DASD_STOPPED_QUIESCE 2 /* Quiesced */
#define DASD_STOPPED_PENDING 4 /* long busy */
#define DASD_STOPPED_DC_WAIT 8 /* disconnected, wait */
#define DASD_STOPPED_DC_EIO 16 /* disconnected, return -EIO */
/* per device flags */
#define DASD_FLAG_RO 0 /* device is read-only */
#define DASD_FLAG_USE_DIAG 1 /* use diag disciplnie */
#define DASD_FLAG_DSC_ERROR 2 /* return -EIO when disconnected */
#define DASD_FLAG_OFFLINE 3 /* device is in offline processing */
void dasd_put_device_wake(struct dasd_device *);
/*
* Reference count inliners
*/
static inline void
dasd_get_device(struct dasd_device *device)
{
atomic_inc(&device->ref_count);
}
static inline void
dasd_put_device(struct dasd_device *device)
{
if (atomic_dec_return(&device->ref_count) == 0)
dasd_put_device_wake(device);
}
/*
* The static memory in ccw_mem and erp_mem is managed by a sorted
* list of free memory chunks.
*/
struct dasd_mchunk
{
struct list_head list;
unsigned long size;
} __attribute__ ((aligned(8)));
static inline void
dasd_init_chunklist(struct list_head *chunk_list, void *mem,
unsigned long size)
{
struct dasd_mchunk *chunk;
INIT_LIST_HEAD(chunk_list);
chunk = (struct dasd_mchunk *) mem;
chunk->size = size - sizeof(struct dasd_mchunk);
list_add(&chunk->list, chunk_list);
}
static inline void *
dasd_alloc_chunk(struct list_head *chunk_list, unsigned long size)
{
struct dasd_mchunk *chunk, *tmp;
size = (size + 7L) & -8L;
list_for_each_entry(chunk, chunk_list, list) {
if (chunk->size < size)
continue;
if (chunk->size > size + sizeof(struct dasd_mchunk)) {
char *endaddr = (char *) (chunk + 1) + chunk->size;
tmp = (struct dasd_mchunk *) (endaddr - size) - 1;
tmp->size = size;
chunk->size -= size + sizeof(struct dasd_mchunk);
chunk = tmp;
} else
list_del(&chunk->list);
return (void *) (chunk + 1);
}
return NULL;
}
static inline void
dasd_free_chunk(struct list_head *chunk_list, void *mem)
{
struct dasd_mchunk *chunk, *tmp;
struct list_head *p, *left;
chunk = (struct dasd_mchunk *)
((char *) mem - sizeof(struct dasd_mchunk));
/* Find out the left neighbour in chunk_list. */
left = chunk_list;
list_for_each(p, chunk_list) {
if (list_entry(p, struct dasd_mchunk, list) > chunk)
break;
left = p;
}
/* Try to merge with right neighbour = next element from left. */
if (left->next != chunk_list) {
tmp = list_entry(left->next, struct dasd_mchunk, list);
if ((char *) (chunk + 1) + chunk->size == (char *) tmp) {
list_del(&tmp->list);
chunk->size += tmp->size + sizeof(struct dasd_mchunk);
}
}
/* Try to merge with left neighbour. */
if (left != chunk_list) {
tmp = list_entry(left, struct dasd_mchunk, list);
if ((char *) (tmp + 1) + tmp->size == (char *) chunk) {
tmp->size += chunk->size + sizeof(struct dasd_mchunk);
return;
}
}
__list_add(&chunk->list, left, left->next);
}
/*
* Check if bsize is in { 512, 1024, 2048, 4096 }
*/
static inline int
dasd_check_blocksize(int bsize)
{
if (bsize < 512 || bsize > 4096 || (bsize & (bsize - 1)) != 0)
return -EMEDIUMTYPE;
return 0;
}
/* externals in dasd.c */
#define DASD_PROFILE_ON 1
#define DASD_PROFILE_OFF 0
extern debug_info_t *dasd_debug_area;
extern struct dasd_profile_info_t dasd_global_profile;
extern unsigned int dasd_profile_level;
extern struct block_device_operations dasd_device_operations;
extern kmem_cache_t *dasd_page_cache;
struct dasd_ccw_req *
dasd_kmalloc_request(char *, int, int, struct dasd_device *);
struct dasd_ccw_req *
dasd_smalloc_request(char *, int, int, struct dasd_device *);
void dasd_kfree_request(struct dasd_ccw_req *, struct dasd_device *);
void dasd_sfree_request(struct dasd_ccw_req *, struct dasd_device *);
static inline int
dasd_kmalloc_set_cda(struct ccw1 *ccw, void *cda, struct dasd_device *device)
{
return set_normalized_cda(ccw, cda);
}
struct dasd_device *dasd_alloc_device(void);
void dasd_free_device(struct dasd_device *);
void dasd_enable_device(struct dasd_device *);
void dasd_set_target_state(struct dasd_device *, int);
void dasd_kick_device(struct dasd_device *);
void dasd_add_request_head(struct dasd_ccw_req *);
void dasd_add_request_tail(struct dasd_ccw_req *);
int dasd_start_IO(struct dasd_ccw_req *);
int dasd_term_IO(struct dasd_ccw_req *);
void dasd_schedule_bh(struct dasd_device *);
int dasd_sleep_on(struct dasd_ccw_req *);
int dasd_sleep_on_immediatly(struct dasd_ccw_req *);
int dasd_sleep_on_interruptible(struct dasd_ccw_req *);
void dasd_set_timer(struct dasd_device *, int);
void dasd_clear_timer(struct dasd_device *);
int dasd_cancel_req(struct dasd_ccw_req *);
int dasd_generic_probe (struct ccw_device *, struct dasd_discipline *);
void dasd_generic_remove (struct ccw_device *cdev);
int dasd_generic_set_online(struct ccw_device *, struct dasd_discipline *);
int dasd_generic_set_offline (struct ccw_device *cdev);
int dasd_generic_notify(struct ccw_device *, int);
void dasd_generic_auto_online (struct ccw_driver *);
/* externals in dasd_devmap.c */
extern int dasd_max_devindex;
extern int dasd_probeonly;
extern int dasd_autodetect;
int dasd_devmap_init(void);
void dasd_devmap_exit(void);
struct dasd_device *dasd_create_device(struct ccw_device *);
void dasd_delete_device(struct dasd_device *);
int dasd_add_sysfs_files(struct ccw_device *);
void dasd_remove_sysfs_files(struct ccw_device *);
struct dasd_device *dasd_device_from_cdev(struct ccw_device *);
struct dasd_device *dasd_device_from_devindex(int);
int dasd_parse(void);
int dasd_busid_known(char *);
/* externals in dasd_gendisk.c */
int dasd_gendisk_init(void);
void dasd_gendisk_exit(void);
int dasd_gendisk_alloc(struct dasd_device *);
void dasd_gendisk_free(struct dasd_device *);
int dasd_scan_partitions(struct dasd_device *);
void dasd_destroy_partitions(struct dasd_device *);
/* externals in dasd_ioctl.c */
int dasd_ioctl_init(void);
void dasd_ioctl_exit(void);
int dasd_ioctl_no_register(struct module *, int, dasd_ioctl_fn_t);
int dasd_ioctl_no_unregister(struct module *, int, dasd_ioctl_fn_t);
int dasd_ioctl(struct inode *, struct file *, unsigned int, unsigned long);
/* externals in dasd_proc.c */
int dasd_proc_init(void);
void dasd_proc_exit(void);
/* externals in dasd_erp.c */
struct dasd_ccw_req *dasd_default_erp_action(struct dasd_ccw_req *);
struct dasd_ccw_req *dasd_default_erp_postaction(struct dasd_ccw_req *);
struct dasd_ccw_req *dasd_alloc_erp_request(char *, int, int,
struct dasd_device *);
void dasd_free_erp_request(struct dasd_ccw_req *, struct dasd_device *);
void dasd_log_sense(struct dasd_ccw_req *, struct irb *);
void dasd_log_ccw(struct dasd_ccw_req *, int, __u32);
/* externals in dasd_3370_erp.c */
dasd_era_t dasd_3370_erp_examine(struct dasd_ccw_req *, struct irb *);
/* externals in dasd_3990_erp.c */
dasd_era_t dasd_3990_erp_examine(struct dasd_ccw_req *, struct irb *);
struct dasd_ccw_req *dasd_3990_erp_action(struct dasd_ccw_req *);
/* externals in dasd_9336_erp.c */
dasd_era_t dasd_9336_erp_examine(struct dasd_ccw_req *, struct irb *);
/* externals in dasd_9336_erp.c */
dasd_era_t dasd_9343_erp_examine(struct dasd_ccw_req *, struct irb *);
struct dasd_ccw_req *dasd_9343_erp_action(struct dasd_ccw_req *);
#endif /* __KERNEL__ */
#endif /* DASD_H */
/*
* Overrides for Emacs so that we follow Linus's tabbing style.
* Emacs will notice this stuff at the end of the file and automatically
* adjust the settings for this buffer only. This must remain at the end
* of the file.
* ---------------------------------------------------------------------------
* Local variables:
* c-indent-level: 4
* c-brace-imaginary-offset: 0
* c-brace-offset: -4
* c-argdecl-indent: 4
* c-label-offset: -4
* c-continued-statement-offset: 4
* c-continued-brace-offset: 0
* indent-tabs-mode: 1
* tab-width: 8
* End:
*/

View file

@ -0,0 +1,554 @@
/*
* File...........: linux/drivers/s390/block/dasd_ioctl.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Horst Hummel <Horst.Hummel@de.ibm.com>
* Carsten Otte <Cotte@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2001
*
* i/o controls for the dasd driver.
*/
#include <linux/config.h>
#include <linux/interrupt.h>
#include <linux/major.h>
#include <linux/fs.h>
#include <linux/blkpg.h>
#include <asm/ccwdev.h>
#include <asm/uaccess.h>
/* This is ugly... */
#define PRINTK_HEADER "dasd_ioctl:"
#include "dasd_int.h"
/*
* SECTION: ioctl functions.
*/
static struct list_head dasd_ioctl_list = LIST_HEAD_INIT(dasd_ioctl_list);
/*
* Find the ioctl with number no.
*/
static struct dasd_ioctl *
dasd_find_ioctl(int no)
{
struct dasd_ioctl *ioctl;
list_for_each_entry (ioctl, &dasd_ioctl_list, list)
if (ioctl->no == no)
return ioctl;
return NULL;
}
/*
* Register ioctl with number no.
*/
int
dasd_ioctl_no_register(struct module *owner, int no, dasd_ioctl_fn_t handler)
{
struct dasd_ioctl *new;
if (dasd_find_ioctl(no))
return -EBUSY;
new = kmalloc(sizeof (struct dasd_ioctl), GFP_KERNEL);
if (new == NULL)
return -ENOMEM;
new->owner = owner;
new->no = no;
new->handler = handler;
list_add(&new->list, &dasd_ioctl_list);
return 0;
}
/*
* Deregister ioctl with number no.
*/
int
dasd_ioctl_no_unregister(struct module *owner, int no, dasd_ioctl_fn_t handler)
{
struct dasd_ioctl *old = dasd_find_ioctl(no);
if (old == NULL)
return -ENOENT;
if (old->no != no || old->handler != handler || owner != old->owner)
return -EINVAL;
list_del(&old->list);
kfree(old);
return 0;
}
int
dasd_ioctl(struct inode *inp, struct file *filp,
unsigned int no, unsigned long data)
{
struct block_device *bdev = inp->i_bdev;
struct dasd_device *device = bdev->bd_disk->private_data;
struct dasd_ioctl *ioctl;
const char *dir;
int rc;
if ((_IOC_DIR(no) != _IOC_NONE) && (data == 0)) {
PRINT_DEBUG("empty data ptr");
return -EINVAL;
}
dir = _IOC_DIR (no) == _IOC_NONE ? "0" :
_IOC_DIR (no) == _IOC_READ ? "r" :
_IOC_DIR (no) == _IOC_WRITE ? "w" :
_IOC_DIR (no) == (_IOC_READ | _IOC_WRITE) ? "rw" : "u";
DBF_DEV_EVENT(DBF_DEBUG, device,
"ioctl 0x%08x %s'0x%x'%d(%d) with data %8lx", no,
dir, _IOC_TYPE(no), _IOC_NR(no), _IOC_SIZE(no), data);
/* Search for ioctl no in the ioctl list. */
list_for_each_entry(ioctl, &dasd_ioctl_list, list) {
if (ioctl->no == no) {
/* Found a matching ioctl. Call it. */
if (!try_module_get(ioctl->owner))
continue;
rc = ioctl->handler(bdev, no, data);
module_put(ioctl->owner);
return rc;
}
}
/* No ioctl with number no. */
DBF_DEV_EVENT(DBF_INFO, device,
"unknown ioctl 0x%08x=%s'0x%x'%d(%d) data %8lx", no,
dir, _IOC_TYPE(no), _IOC_NR(no), _IOC_SIZE(no), data);
return -EINVAL;
}
static int
dasd_ioctl_api_version(struct block_device *bdev, int no, long args)
{
int ver = DASD_API_VERSION;
return put_user(ver, (int __user *) args);
}
/*
* Enable device.
* used by dasdfmt after BIODASDDISABLE to retrigger blocksize detection
*/
static int
dasd_ioctl_enable(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
dasd_enable_device(device);
/* Formatting the dasd device can change the capacity. */
down(&bdev->bd_sem);
i_size_write(bdev->bd_inode, (loff_t)get_capacity(device->gdp) << 9);
up(&bdev->bd_sem);
return 0;
}
/*
* Disable device.
* Used by dasdfmt. Disable I/O operations but allow ioctls.
*/
static int
dasd_ioctl_disable(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
/*
* Man this is sick. We don't do a real disable but only downgrade
* the device to DASD_STATE_BASIC. The reason is that dasdfmt uses
* BIODASDDISABLE to disable accesses to the device via the block
* device layer but it still wants to do i/o on the device by
* using the BIODASDFMT ioctl. Therefore the correct state for the
* device is DASD_STATE_BASIC that allows to do basic i/o.
*/
dasd_set_target_state(device, DASD_STATE_BASIC);
/*
* Set i_size to zero, since read, write, etc. check against this
* value.
*/
down(&bdev->bd_sem);
i_size_write(bdev->bd_inode, 0);
up(&bdev->bd_sem);
return 0;
}
/*
* Quiesce device.
*/
static int
dasd_ioctl_quiesce(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
unsigned long flags;
if (!capable (CAP_SYS_ADMIN))
return -EACCES;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
DEV_MESSAGE (KERN_DEBUG, device, "%s",
"Quiesce IO on device");
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
device->stopped |= DASD_STOPPED_QUIESCE;
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
return 0;
}
/*
* Quiesce device.
*/
static int
dasd_ioctl_resume(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
unsigned long flags;
if (!capable (CAP_SYS_ADMIN))
return -EACCES;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
DEV_MESSAGE (KERN_DEBUG, device, "%s",
"resume IO on device");
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
device->stopped &= ~DASD_STOPPED_QUIESCE;
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev), flags);
dasd_schedule_bh (device);
return 0;
}
/*
* performs formatting of _device_ according to _fdata_
* Note: The discipline's format_function is assumed to deliver formatting
* commands to format a single unit of the device. In terms of the ECKD
* devices this means CCWs are generated to format a single track.
*/
static int
dasd_format(struct dasd_device * device, struct format_data_t * fdata)
{
struct dasd_ccw_req *cqr;
int rc;
if (device->discipline->format_device == NULL)
return -EPERM;
if (device->state != DASD_STATE_BASIC) {
DEV_MESSAGE(KERN_WARNING, device, "%s",
"dasd_format: device is not disabled! ");
return -EBUSY;
}
DBF_DEV_EVENT(DBF_NOTICE, device,
"formatting units %d to %d (%d B blocks) flags %d",
fdata->start_unit,
fdata->stop_unit, fdata->blksize, fdata->intensity);
/* Since dasdfmt keeps the device open after it was disabled,
* there still exists an inode for this device.
* We must update i_blkbits, otherwise we might get errors when
* enabling the device later.
*/
if (fdata->start_unit == 0) {
struct block_device *bdev = bdget_disk(device->gdp, 0);
bdev->bd_inode->i_blkbits = blksize_bits(fdata->blksize);
bdput(bdev);
}
while (fdata->start_unit <= fdata->stop_unit) {
cqr = device->discipline->format_device(device, fdata);
if (IS_ERR(cqr))
return PTR_ERR(cqr);
rc = dasd_sleep_on_interruptible(cqr);
dasd_sfree_request(cqr, cqr->device);
if (rc) {
if (rc != -ERESTARTSYS)
DEV_MESSAGE(KERN_ERR, device,
" Formatting of unit %d failed "
"with rc = %d",
fdata->start_unit, rc);
return rc;
}
fdata->start_unit++;
}
return 0;
}
/*
* Format device.
*/
static int
dasd_ioctl_format(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
struct format_data_t fdata;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (!args)
return -EINVAL;
/* fdata == NULL is no longer a valid arg to dasd_format ! */
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
if (test_bit(DASD_FLAG_RO, &device->flags))
return -EROFS;
if (copy_from_user(&fdata, (void __user *) args,
sizeof (struct format_data_t)))
return -EFAULT;
if (bdev != bdev->bd_contains) {
DEV_MESSAGE(KERN_WARNING, device, "%s",
"Cannot low-level format a partition");
return -EINVAL;
}
return dasd_format(device, &fdata);
}
#ifdef CONFIG_DASD_PROFILE
/*
* Reset device profile information
*/
static int
dasd_ioctl_reset_profile(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
memset(&device->profile, 0, sizeof (struct dasd_profile_info_t));
return 0;
}
/*
* Return device profile information
*/
static int
dasd_ioctl_read_profile(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
if (copy_to_user((long __user *) args, (long *) &device->profile,
sizeof (struct dasd_profile_info_t)))
return -EFAULT;
return 0;
}
#else
static int
dasd_ioctl_reset_profile(struct block_device *bdev, int no, long args)
{
return -ENOSYS;
}
static int
dasd_ioctl_read_profile(struct block_device *bdev, int no, long args)
{
return -ENOSYS;
}
#endif
/*
* Return dasd information. Used for BIODASDINFO and BIODASDINFO2.
*/
static int
dasd_ioctl_information(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
struct dasd_information2_t *dasd_info;
unsigned long flags;
int rc;
struct ccw_device *cdev;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
if (!device->discipline->fill_info)
return -EINVAL;
dasd_info = kmalloc(sizeof(struct dasd_information2_t), GFP_KERNEL);
if (dasd_info == NULL)
return -ENOMEM;
rc = device->discipline->fill_info(device, dasd_info);
if (rc) {
kfree(dasd_info);
return rc;
}
cdev = device->cdev;
dasd_info->devno = _ccw_device_get_device_number(device->cdev);
dasd_info->schid = _ccw_device_get_subchannel_number(device->cdev);
dasd_info->cu_type = cdev->id.cu_type;
dasd_info->cu_model = cdev->id.cu_model;
dasd_info->dev_type = cdev->id.dev_type;
dasd_info->dev_model = cdev->id.dev_model;
dasd_info->open_count = atomic_read(&device->open_count);
dasd_info->status = device->state;
/*
* check if device is really formatted
* LDL / CDL was returned by 'fill_info'
*/
if ((device->state < DASD_STATE_READY) ||
(dasd_check_blocksize(device->bp_block)))
dasd_info->format = DASD_FORMAT_NONE;
dasd_info->features |= test_bit(DASD_FLAG_RO, &device->flags) ?
DASD_FEATURE_READONLY : DASD_FEATURE_DEFAULT;
if (device->discipline)
memcpy(dasd_info->type, device->discipline->name, 4);
else
memcpy(dasd_info->type, "none", 4);
dasd_info->req_queue_len = 0;
dasd_info->chanq_len = 0;
if (device->request_queue->request_fn) {
struct list_head *l;
#ifdef DASD_EXTENDED_PROFILING
{
struct list_head *l;
spin_lock_irqsave(&device->lock, flags);
list_for_each(l, &device->request_queue->queue_head)
dasd_info->req_queue_len++;
spin_unlock_irqrestore(&device->lock, flags);
}
#endif /* DASD_EXTENDED_PROFILING */
spin_lock_irqsave(get_ccwdev_lock(device->cdev), flags);
list_for_each(l, &device->ccw_queue)
dasd_info->chanq_len++;
spin_unlock_irqrestore(get_ccwdev_lock(device->cdev),
flags);
}
rc = 0;
if (copy_to_user((long __user *) args, (long *) dasd_info,
((no == (unsigned int) BIODASDINFO2) ?
sizeof (struct dasd_information2_t) :
sizeof (struct dasd_information_t))))
rc = -EFAULT;
kfree(dasd_info);
return rc;
}
/*
* Set read only
*/
static int
dasd_ioctl_set_ro(struct block_device *bdev, int no, long args)
{
struct dasd_device *device;
int intval;
if (!capable(CAP_SYS_ADMIN))
return -EACCES;
if (bdev != bdev->bd_contains)
// ro setting is not allowed for partitions
return -EINVAL;
if (get_user(intval, (int __user *) args))
return -EFAULT;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
set_disk_ro(bdev->bd_disk, intval);
if (intval)
set_bit(DASD_FLAG_RO, &device->flags);
else
clear_bit(DASD_FLAG_RO, &device->flags);
return 0;
}
/*
* Return disk geometry.
*/
static int
dasd_ioctl_getgeo(struct block_device *bdev, int no, long args)
{
struct hd_geometry geo = { 0, };
struct dasd_device *device;
device = bdev->bd_disk->private_data;
if (device == NULL)
return -ENODEV;
if (device == NULL || device->discipline == NULL ||
device->discipline->fill_geometry == NULL)
return -EINVAL;
geo = (struct hd_geometry) {};
device->discipline->fill_geometry(device, &geo);
geo.start = get_start_sect(bdev) >> device->s2b_shift;
if (copy_to_user((struct hd_geometry __user *) args, &geo,
sizeof (struct hd_geometry)))
return -EFAULT;
return 0;
}
/*
* List of static ioctls.
*/
static struct { int no; dasd_ioctl_fn_t fn; } dasd_ioctls[] =
{
{ BIODASDDISABLE, dasd_ioctl_disable },
{ BIODASDENABLE, dasd_ioctl_enable },
{ BIODASDQUIESCE, dasd_ioctl_quiesce },
{ BIODASDRESUME, dasd_ioctl_resume },
{ BIODASDFMT, dasd_ioctl_format },
{ BIODASDINFO, dasd_ioctl_information },
{ BIODASDINFO2, dasd_ioctl_information },
{ BIODASDPRRD, dasd_ioctl_read_profile },
{ BIODASDPRRST, dasd_ioctl_reset_profile },
{ BLKROSET, dasd_ioctl_set_ro },
{ DASDAPIVER, dasd_ioctl_api_version },
{ HDIO_GETGEO, dasd_ioctl_getgeo },
{ -1, NULL }
};
int
dasd_ioctl_init(void)
{
int i;
for (i = 0; dasd_ioctls[i].no != -1; i++)
dasd_ioctl_no_register(NULL, dasd_ioctls[i].no,
dasd_ioctls[i].fn);
return 0;
}
void
dasd_ioctl_exit(void)
{
int i;
for (i = 0; dasd_ioctls[i].no != -1; i++)
dasd_ioctl_no_unregister(NULL, dasd_ioctls[i].no,
dasd_ioctls[i].fn);
}
EXPORT_SYMBOL(dasd_ioctl_no_register);
EXPORT_SYMBOL(dasd_ioctl_no_unregister);

View file

@ -0,0 +1,319 @@
/*
* File...........: linux/drivers/s390/block/dasd_proc.c
* Author(s)......: Holger Smolinski <Holger.Smolinski@de.ibm.com>
* Horst Hummel <Horst.Hummel@de.ibm.com>
* Carsten Otte <Cotte@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Bugreports.to..: <Linux390@de.ibm.com>
* (C) IBM Corporation, IBM Deutschland Entwicklung GmbH, 1999-2002
*
* /proc interface for the dasd driver.
*
* $Revision: 1.30 $
*/
#include <linux/config.h>
#include <linux/ctype.h>
#include <linux/seq_file.h>
#include <linux/vmalloc.h>
#include <asm/debug.h>
#include <asm/uaccess.h>
/* This is ugly... */
#define PRINTK_HEADER "dasd_proc:"
#include "dasd_int.h"
static struct proc_dir_entry *dasd_proc_root_entry = NULL;
static struct proc_dir_entry *dasd_devices_entry = NULL;
static struct proc_dir_entry *dasd_statistics_entry = NULL;
static inline char *
dasd_get_user_string(const char __user *user_buf, size_t user_len)
{
char *buffer;
buffer = kmalloc(user_len + 1, GFP_KERNEL);
if (buffer == NULL)
return ERR_PTR(-ENOMEM);
if (copy_from_user(buffer, user_buf, user_len) != 0) {
kfree(buffer);
return ERR_PTR(-EFAULT);
}
/* got the string, now strip linefeed. */
if (buffer[user_len - 1] == '\n')
buffer[user_len - 1] = 0;
else
buffer[user_len] = 0;
return buffer;
}
static int
dasd_devices_show(struct seq_file *m, void *v)
{
struct dasd_device *device;
char *substr;
device = dasd_device_from_devindex((unsigned long) v - 1);
if (IS_ERR(device))
return 0;
/* Print device number. */
seq_printf(m, "%s", device->cdev->dev.bus_id);
/* Print discipline string. */
if (device != NULL && device->discipline != NULL)
seq_printf(m, "(%s)", device->discipline->name);
else
seq_printf(m, "(none)");
/* Print kdev. */
if (device->gdp)
seq_printf(m, " at (%3d:%6d)",
device->gdp->major, device->gdp->first_minor);
else
seq_printf(m, " at (???:??????)");
/* Print device name. */
if (device->gdp)
seq_printf(m, " is %-8s", device->gdp->disk_name);
else
seq_printf(m, " is ????????");
/* Print devices features. */
substr = test_bit(DASD_FLAG_RO, &device->flags) ? "(ro)" : " ";
seq_printf(m, "%4s: ", substr);
/* Print device status information. */
switch ((device != NULL) ? device->state : -1) {
case -1:
seq_printf(m, "unknown");
break;
case DASD_STATE_NEW:
seq_printf(m, "new");
break;
case DASD_STATE_KNOWN:
seq_printf(m, "detected");
break;
case DASD_STATE_BASIC:
seq_printf(m, "basic");
break;
case DASD_STATE_READY:
case DASD_STATE_ONLINE:
seq_printf(m, "active ");
if (dasd_check_blocksize(device->bp_block))
seq_printf(m, "n/f ");
else
seq_printf(m,
"at blocksize: %d, %ld blocks, %ld MB",
device->bp_block, device->blocks,
((device->bp_block >> 9) *
device->blocks) >> 11);
break;
default:
seq_printf(m, "no stat");
break;
}
dasd_put_device(device);
if (dasd_probeonly)
seq_printf(m, "(probeonly)");
seq_printf(m, "\n");
return 0;
}
static void *dasd_devices_start(struct seq_file *m, loff_t *pos)
{
if (*pos >= dasd_max_devindex)
return NULL;
return (void *)((unsigned long) *pos + 1);
}
static void *dasd_devices_next(struct seq_file *m, void *v, loff_t *pos)
{
++*pos;
return dasd_devices_start(m, pos);
}
static void dasd_devices_stop(struct seq_file *m, void *v)
{
}
static struct seq_operations dasd_devices_seq_ops = {
.start = dasd_devices_start,
.next = dasd_devices_next,
.stop = dasd_devices_stop,
.show = dasd_devices_show,
};
static int dasd_devices_open(struct inode *inode, struct file *file)
{
return seq_open(file, &dasd_devices_seq_ops);
}
static struct file_operations dasd_devices_file_ops = {
.open = dasd_devices_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
static inline int
dasd_calc_metrics(char *page, char **start, off_t off,
int count, int *eof, int len)
{
len = (len > off) ? len - off : 0;
if (len > count)
len = count;
if (len < count)
*eof = 1;
*start = page + off;
return len;
}
static inline char *
dasd_statistics_array(char *str, int *array, int shift)
{
int i;
for (i = 0; i < 32; i++) {
str += sprintf(str, "%7d ", array[i] >> shift);
if (i == 15)
str += sprintf(str, "\n");
}
str += sprintf(str,"\n");
return str;
}
static int
dasd_statistics_read(char *page, char **start, off_t off,
int count, int *eof, void *data)
{
unsigned long len;
#ifdef CONFIG_DASD_PROFILE
struct dasd_profile_info_t *prof;
char *str;
int shift;
/* check for active profiling */
if (dasd_profile_level == DASD_PROFILE_OFF) {
len = sprintf(page, "Statistics are off - they might be "
"switched on using 'echo set on > "
"/proc/dasd/statistics'\n");
return dasd_calc_metrics(page, start, off, count, eof, len);
}
prof = &dasd_global_profile;
/* prevent couter 'overflow' on output */
for (shift = 0; (prof->dasd_io_reqs >> shift) > 9999999; shift++);
str = page;
str += sprintf(str, "%d dasd I/O requests\n", prof->dasd_io_reqs);
str += sprintf(str, "with %d sectors(512B each)\n",
prof->dasd_io_sects);
str += sprintf(str,
" __<4 ___8 __16 __32 __64 _128 "
" _256 _512 __1k __2k __4k __8k "
" _16k _32k _64k 128k\n");
str += sprintf(str,
" _256 _512 __1M __2M __4M __8M "
" _16M _32M _64M 128M 256M 512M "
" __1G __2G __4G " " _>4G\n");
str += sprintf(str, "Histogram of sizes (512B secs)\n");
str = dasd_statistics_array(str, prof->dasd_io_secs, shift);
str += sprintf(str, "Histogram of I/O times (microseconds)\n");
str = dasd_statistics_array(str, prof->dasd_io_times, shift);
str += sprintf(str, "Histogram of I/O times per sector\n");
str = dasd_statistics_array(str, prof->dasd_io_timps, shift);
str += sprintf(str, "Histogram of I/O time till ssch\n");
str = dasd_statistics_array(str, prof->dasd_io_time1, shift);
str += sprintf(str, "Histogram of I/O time between ssch and irq\n");
str = dasd_statistics_array(str, prof->dasd_io_time2, shift);
str += sprintf(str, "Histogram of I/O time between ssch "
"and irq per sector\n");
str = dasd_statistics_array(str, prof->dasd_io_time2ps, shift);
str += sprintf(str, "Histogram of I/O time between irq and end\n");
str = dasd_statistics_array(str, prof->dasd_io_time3, shift);
str += sprintf(str, "# of req in chanq at enqueuing (1..32) \n");
str = dasd_statistics_array(str, prof->dasd_io_nr_req, shift);
len = str - page;
#else
len = sprintf(page, "Statistics are not activated in this kernel\n");
#endif
return dasd_calc_metrics(page, start, off, count, eof, len);
}
static int
dasd_statistics_write(struct file *file, const char __user *user_buf,
unsigned long user_len, void *data)
{
#ifdef CONFIG_DASD_PROFILE
char *buffer, *str;
if (user_len > 65536)
user_len = 65536;
buffer = dasd_get_user_string(user_buf, user_len);
if (IS_ERR(buffer))
return PTR_ERR(buffer);
MESSAGE_LOG(KERN_INFO, "/proc/dasd/statictics: '%s'", buffer);
/* check for valid verbs */
for (str = buffer; isspace(*str); str++);
if (strncmp(str, "set", 3) == 0 && isspace(str[3])) {
/* 'set xxx' was given */
for (str = str + 4; isspace(*str); str++);
if (strcmp(str, "on") == 0) {
/* switch on statistics profiling */
dasd_profile_level = DASD_PROFILE_ON;
MESSAGE(KERN_INFO, "%s", "Statistics switched on");
} else if (strcmp(str, "off") == 0) {
/* switch off and reset statistics profiling */
memset(&dasd_global_profile,
0, sizeof (struct dasd_profile_info_t));
dasd_profile_level = DASD_PROFILE_OFF;
MESSAGE(KERN_INFO, "%s", "Statistics switched off");
} else
goto out_error;
} else if (strncmp(str, "reset", 5) == 0) {
/* reset the statistics */
memset(&dasd_global_profile, 0,
sizeof (struct dasd_profile_info_t));
MESSAGE(KERN_INFO, "%s", "Statistics reset");
} else
goto out_error;
kfree(buffer);
return user_len;
out_error:
MESSAGE(KERN_WARNING, "%s",
"/proc/dasd/statistics: only 'set on', 'set off' "
"and 'reset' are supported verbs");
kfree(buffer);
return -EINVAL;
#else
MESSAGE(KERN_WARNING, "%s",
"/proc/dasd/statistics: is not activated in this kernel");
return user_len;
#endif /* CONFIG_DASD_PROFILE */
}
int
dasd_proc_init(void)
{
dasd_proc_root_entry = proc_mkdir("dasd", &proc_root);
dasd_proc_root_entry->owner = THIS_MODULE;
dasd_devices_entry = create_proc_entry("devices",
S_IFREG | S_IRUGO | S_IWUSR,
dasd_proc_root_entry);
dasd_devices_entry->proc_fops = &dasd_devices_file_ops;
dasd_devices_entry->owner = THIS_MODULE;
dasd_statistics_entry = create_proc_entry("statistics",
S_IFREG | S_IRUGO | S_IWUSR,
dasd_proc_root_entry);
dasd_statistics_entry->read_proc = dasd_statistics_read;
dasd_statistics_entry->write_proc = dasd_statistics_write;
dasd_statistics_entry->owner = THIS_MODULE;
return 0;
}
void
dasd_proc_exit(void)
{
remove_proc_entry("devices", dasd_proc_root_entry);
remove_proc_entry("statistics", dasd_proc_root_entry);
remove_proc_entry("dasd", &proc_root);
}

View file

@ -0,0 +1,775 @@
/*
* dcssblk.c -- the S/390 block driver for dcss memory
*
* Authors: Carsten Otte, Stefan Weinhuber, Gerald Schaefer
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/ctype.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/blkdev.h>
#include <asm/extmem.h>
#include <asm/io.h>
#include <linux/completion.h>
#include <linux/interrupt.h>
#include <asm/ccwdev.h> // for s390_root_dev_(un)register()
//#define DCSSBLK_DEBUG /* Debug messages on/off */
#define DCSSBLK_NAME "dcssblk"
#define DCSSBLK_MINORS_PER_DISK 1
#define DCSSBLK_PARM_LEN 400
#ifdef DCSSBLK_DEBUG
#define PRINT_DEBUG(x...) printk(KERN_DEBUG DCSSBLK_NAME " debug: " x)
#else
#define PRINT_DEBUG(x...) do {} while (0)
#endif
#define PRINT_INFO(x...) printk(KERN_INFO DCSSBLK_NAME " info: " x)
#define PRINT_WARN(x...) printk(KERN_WARNING DCSSBLK_NAME " warning: " x)
#define PRINT_ERR(x...) printk(KERN_ERR DCSSBLK_NAME " error: " x)
static int dcssblk_open(struct inode *inode, struct file *filp);
static int dcssblk_release(struct inode *inode, struct file *filp);
static int dcssblk_make_request(struct request_queue *q, struct bio *bio);
static char dcssblk_segments[DCSSBLK_PARM_LEN] = "\0";
static int dcssblk_major;
static struct block_device_operations dcssblk_devops = {
.owner = THIS_MODULE,
.open = dcssblk_open,
.release = dcssblk_release,
};
static ssize_t dcssblk_add_store(struct device * dev, const char * buf,
size_t count);
static ssize_t dcssblk_remove_store(struct device * dev, const char * buf,
size_t count);
static ssize_t dcssblk_save_store(struct device * dev, const char * buf,
size_t count);
static ssize_t dcssblk_save_show(struct device *dev, char *buf);
static ssize_t dcssblk_shared_store(struct device * dev, const char * buf,
size_t count);
static ssize_t dcssblk_shared_show(struct device *dev, char *buf);
static DEVICE_ATTR(add, S_IWUSR, NULL, dcssblk_add_store);
static DEVICE_ATTR(remove, S_IWUSR, NULL, dcssblk_remove_store);
static DEVICE_ATTR(save, S_IWUSR | S_IRUGO, dcssblk_save_show,
dcssblk_save_store);
static DEVICE_ATTR(shared, S_IWUSR | S_IRUGO, dcssblk_shared_show,
dcssblk_shared_store);
static struct device *dcssblk_root_dev;
struct dcssblk_dev_info {
struct list_head lh;
struct device dev;
char segment_name[BUS_ID_SIZE];
atomic_t use_count;
struct gendisk *gd;
unsigned long start;
unsigned long end;
int segment_type;
unsigned char save_pending;
unsigned char is_shared;
struct request_queue *dcssblk_queue;
};
static struct list_head dcssblk_devices = LIST_HEAD_INIT(dcssblk_devices);
static struct rw_semaphore dcssblk_devices_sem;
/*
* release function for segment device.
*/
static void
dcssblk_release_segment(struct device *dev)
{
PRINT_DEBUG("segment release fn called for %s\n", dev->bus_id);
kfree(container_of(dev, struct dcssblk_dev_info, dev));
module_put(THIS_MODULE);
}
/*
* get a minor number. needs to be called with
* down_write(&dcssblk_devices_sem) and the
* device needs to be enqueued before the semaphore is
* freed.
*/
static inline int
dcssblk_assign_free_minor(struct dcssblk_dev_info *dev_info)
{
int minor, found;
struct dcssblk_dev_info *entry;
if (dev_info == NULL)
return -EINVAL;
for (minor = 0; minor < (1<<MINORBITS); minor++) {
found = 0;
// test if minor available
list_for_each_entry(entry, &dcssblk_devices, lh)
if (minor == entry->gd->first_minor)
found++;
if (!found) break; // got unused minor
}
if (found)
return -EBUSY;
dev_info->gd->first_minor = minor;
return 0;
}
/*
* get the struct dcssblk_dev_info from dcssblk_devices
* for the given name.
* down_read(&dcssblk_devices_sem) must be held.
*/
static struct dcssblk_dev_info *
dcssblk_get_device_by_name(char *name)
{
struct dcssblk_dev_info *entry;
list_for_each_entry(entry, &dcssblk_devices, lh) {
if (!strcmp(name, entry->segment_name)) {
return entry;
}
}
return NULL;
}
/*
* print appropriate error message for segment_load()/segment_type()
* return code
*/
static void
dcssblk_segment_warn(int rc, char* seg_name)
{
switch (rc) {
case -ENOENT:
PRINT_WARN("cannot load/query segment %s, does not exist\n",
seg_name);
break;
case -ENOSYS:
PRINT_WARN("cannot load/query segment %s, not running on VM\n",
seg_name);
break;
case -EIO:
PRINT_WARN("cannot load/query segment %s, hardware error\n",
seg_name);
break;
case -ENOTSUPP:
PRINT_WARN("cannot load/query segment %s, is a multi-part "
"segment\n", seg_name);
break;
case -ENOSPC:
PRINT_WARN("cannot load/query segment %s, overlaps with "
"storage\n", seg_name);
break;
case -EBUSY:
PRINT_WARN("cannot load/query segment %s, overlaps with "
"already loaded dcss\n", seg_name);
break;
case -EPERM:
PRINT_WARN("cannot load/query segment %s, already loaded in "
"incompatible mode\n", seg_name);
break;
case -ENOMEM:
PRINT_WARN("cannot load/query segment %s, out of memory\n",
seg_name);
break;
case -ERANGE:
PRINT_WARN("cannot load/query segment %s, exceeds kernel "
"mapping range\n", seg_name);
break;
default:
PRINT_WARN("cannot load/query segment %s, return value %i\n",
seg_name, rc);
break;
}
}
/*
* device attribute for switching shared/nonshared (exclusive)
* operation (show + store)
*/
static ssize_t
dcssblk_shared_show(struct device *dev, char *buf)
{
struct dcssblk_dev_info *dev_info;
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
return sprintf(buf, dev_info->is_shared ? "1\n" : "0\n");
}
static ssize_t
dcssblk_shared_store(struct device *dev, const char *inbuf, size_t count)
{
struct dcssblk_dev_info *dev_info;
int rc;
if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) {
PRINT_WARN("Invalid value, must be 0 or 1\n");
return -EINVAL;
}
down_write(&dcssblk_devices_sem);
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
if (atomic_read(&dev_info->use_count)) {
PRINT_ERR("share: segment %s is busy!\n",
dev_info->segment_name);
rc = -EBUSY;
goto out;
}
if (inbuf[0] == '1') {
// reload segment in shared mode
rc = segment_modify_shared(dev_info->segment_name,
SEGMENT_SHARED);
if (rc < 0) {
BUG_ON(rc == -EINVAL);
if (rc == -EIO || rc == -ENOENT)
goto removeseg;
} else {
dev_info->is_shared = 1;
switch (dev_info->segment_type) {
case SEG_TYPE_SR:
case SEG_TYPE_ER:
case SEG_TYPE_SC:
set_disk_ro(dev_info->gd,1);
}
}
} else if (inbuf[0] == '0') {
// reload segment in exclusive mode
if (dev_info->segment_type == SEG_TYPE_SC) {
PRINT_ERR("Segment type SC (%s) cannot be loaded in "
"non-shared mode\n", dev_info->segment_name);
rc = -EINVAL;
goto out;
}
rc = segment_modify_shared(dev_info->segment_name,
SEGMENT_EXCLUSIVE);
if (rc < 0) {
BUG_ON(rc == -EINVAL);
if (rc == -EIO || rc == -ENOENT)
goto removeseg;
} else {
dev_info->is_shared = 0;
set_disk_ro(dev_info->gd, 0);
}
} else {
PRINT_WARN("Invalid value, must be 0 or 1\n");
rc = -EINVAL;
goto out;
}
rc = count;
goto out;
removeseg:
PRINT_ERR("Could not reload segment %s, removing it now!\n",
dev_info->segment_name);
list_del(&dev_info->lh);
del_gendisk(dev_info->gd);
blk_put_queue(dev_info->dcssblk_queue);
dev_info->gd->queue = NULL;
put_disk(dev_info->gd);
device_unregister(dev);
put_device(dev);
out:
up_write(&dcssblk_devices_sem);
return rc;
}
/*
* device attribute for save operation on current copy
* of the segment. If the segment is busy, saving will
* become pending until it gets released, which can be
* undone by storing a non-true value to this entry.
* (show + store)
*/
static ssize_t
dcssblk_save_show(struct device *dev, char *buf)
{
struct dcssblk_dev_info *dev_info;
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
return sprintf(buf, dev_info->save_pending ? "1\n" : "0\n");
}
static ssize_t
dcssblk_save_store(struct device *dev, const char *inbuf, size_t count)
{
struct dcssblk_dev_info *dev_info;
if ((count > 1) && (inbuf[1] != '\n') && (inbuf[1] != '\0')) {
PRINT_WARN("Invalid value, must be 0 or 1\n");
return -EINVAL;
}
dev_info = container_of(dev, struct dcssblk_dev_info, dev);
down_write(&dcssblk_devices_sem);
if (inbuf[0] == '1') {
if (atomic_read(&dev_info->use_count) == 0) {
// device is idle => we save immediately
PRINT_INFO("Saving segment %s\n",
dev_info->segment_name);
segment_save(dev_info->segment_name);
} else {
// device is busy => we save it when it becomes
// idle in dcssblk_release
PRINT_INFO("Segment %s is currently busy, it will "
"be saved when it becomes idle...\n",
dev_info->segment_name);
dev_info->save_pending = 1;
}
} else if (inbuf[0] == '0') {
if (dev_info->save_pending) {
// device is busy & the user wants to undo his save
// request
dev_info->save_pending = 0;
PRINT_INFO("Pending save for segment %s deactivated\n",
dev_info->segment_name);
}
} else {
up_write(&dcssblk_devices_sem);
PRINT_WARN("Invalid value, must be 0 or 1\n");
return -EINVAL;
}
up_write(&dcssblk_devices_sem);
return count;
}
/*
* device attribute for adding devices
*/
static ssize_t
dcssblk_add_store(struct device *dev, const char *buf, size_t count)
{
int rc, i;
struct dcssblk_dev_info *dev_info;
char *local_buf;
unsigned long seg_byte_size;
dev_info = NULL;
if (dev != dcssblk_root_dev) {
rc = -EINVAL;
goto out_nobuf;
}
local_buf = kmalloc(count + 1, GFP_KERNEL);
if (local_buf == NULL) {
rc = -ENOMEM;
goto out_nobuf;
}
/*
* parse input
*/
for (i = 0; ((buf[i] != '\0') && (buf[i] != '\n') && i < count); i++) {
local_buf[i] = toupper(buf[i]);
}
local_buf[i] = '\0';
if ((i == 0) || (i > 8)) {
rc = -ENAMETOOLONG;
goto out;
}
/*
* already loaded?
*/
down_read(&dcssblk_devices_sem);
dev_info = dcssblk_get_device_by_name(local_buf);
up_read(&dcssblk_devices_sem);
if (dev_info != NULL) {
PRINT_WARN("Segment %s already loaded!\n", local_buf);
rc = -EEXIST;
goto out;
}
/*
* get a struct dcssblk_dev_info
*/
dev_info = kmalloc(sizeof(struct dcssblk_dev_info), GFP_KERNEL);
if (dev_info == NULL) {
rc = -ENOMEM;
goto out;
}
memset(dev_info, 0, sizeof(struct dcssblk_dev_info));
strcpy(dev_info->segment_name, local_buf);
strlcpy(dev_info->dev.bus_id, local_buf, BUS_ID_SIZE);
dev_info->dev.release = dcssblk_release_segment;
INIT_LIST_HEAD(&dev_info->lh);
dev_info->gd = alloc_disk(DCSSBLK_MINORS_PER_DISK);
if (dev_info->gd == NULL) {
rc = -ENOMEM;
goto free_dev_info;
}
dev_info->gd->major = dcssblk_major;
dev_info->gd->fops = &dcssblk_devops;
dev_info->dcssblk_queue = blk_alloc_queue(GFP_KERNEL);
dev_info->gd->queue = dev_info->dcssblk_queue;
dev_info->gd->private_data = dev_info;
dev_info->gd->driverfs_dev = &dev_info->dev;
/*
* load the segment
*/
rc = segment_load(local_buf, SEGMENT_SHARED,
&dev_info->start, &dev_info->end);
if (rc < 0) {
dcssblk_segment_warn(rc, dev_info->segment_name);
goto dealloc_gendisk;
}
seg_byte_size = (dev_info->end - dev_info->start + 1);
set_capacity(dev_info->gd, seg_byte_size >> 9); // size in sectors
PRINT_INFO("Loaded segment %s, size = %lu Byte, "
"capacity = %lu (512 Byte) sectors\n", local_buf,
seg_byte_size, seg_byte_size >> 9);
dev_info->segment_type = rc;
dev_info->save_pending = 0;
dev_info->is_shared = 1;
dev_info->dev.parent = dcssblk_root_dev;
/*
* get minor, add to list
*/
down_write(&dcssblk_devices_sem);
rc = dcssblk_assign_free_minor(dev_info);
if (rc) {
up_write(&dcssblk_devices_sem);
PRINT_ERR("No free minor number available! "
"Unloading segment...\n");
goto unload_seg;
}
sprintf(dev_info->gd->disk_name, "dcssblk%d",
dev_info->gd->first_minor);
list_add_tail(&dev_info->lh, &dcssblk_devices);
if (!try_module_get(THIS_MODULE)) {
rc = -ENODEV;
goto list_del;
}
/*
* register the device
*/
rc = device_register(&dev_info->dev);
if (rc) {
PRINT_ERR("Segment %s could not be registered RC=%d\n",
local_buf, rc);
module_put(THIS_MODULE);
goto list_del;
}
get_device(&dev_info->dev);
rc = device_create_file(&dev_info->dev, &dev_attr_shared);
if (rc)
goto unregister_dev;
rc = device_create_file(&dev_info->dev, &dev_attr_save);
if (rc)
goto unregister_dev;
add_disk(dev_info->gd);
blk_queue_make_request(dev_info->dcssblk_queue, dcssblk_make_request);
blk_queue_hardsect_size(dev_info->dcssblk_queue, 4096);
switch (dev_info->segment_type) {
case SEG_TYPE_SR:
case SEG_TYPE_ER:
case SEG_TYPE_SC:
set_disk_ro(dev_info->gd,1);
break;
default:
set_disk_ro(dev_info->gd,0);
break;
}
PRINT_DEBUG("Segment %s loaded successfully\n", local_buf);
up_write(&dcssblk_devices_sem);
rc = count;
goto out;
unregister_dev:
PRINT_ERR("device_create_file() failed!\n");
list_del(&dev_info->lh);
blk_put_queue(dev_info->dcssblk_queue);
dev_info->gd->queue = NULL;
put_disk(dev_info->gd);
device_unregister(&dev_info->dev);
segment_unload(dev_info->segment_name);
put_device(&dev_info->dev);
up_write(&dcssblk_devices_sem);
goto out;
list_del:
list_del(&dev_info->lh);
up_write(&dcssblk_devices_sem);
unload_seg:
segment_unload(local_buf);
dealloc_gendisk:
blk_put_queue(dev_info->dcssblk_queue);
dev_info->gd->queue = NULL;
put_disk(dev_info->gd);
free_dev_info:
kfree(dev_info);
out:
kfree(local_buf);
out_nobuf:
return rc;
}
/*
* device attribute for removing devices
*/
static ssize_t
dcssblk_remove_store(struct device *dev, const char *buf, size_t count)
{
struct dcssblk_dev_info *dev_info;
int rc, i;
char *local_buf;
if (dev != dcssblk_root_dev) {
return -EINVAL;
}
local_buf = kmalloc(count + 1, GFP_KERNEL);
if (local_buf == NULL) {
return -ENOMEM;
}
/*
* parse input
*/
for (i = 0; ((*(buf+i)!='\0') && (*(buf+i)!='\n') && i < count); i++) {
local_buf[i] = toupper(buf[i]);
}
local_buf[i] = '\0';
if ((i == 0) || (i > 8)) {
rc = -ENAMETOOLONG;
goto out_buf;
}
down_write(&dcssblk_devices_sem);
dev_info = dcssblk_get_device_by_name(local_buf);
if (dev_info == NULL) {
up_write(&dcssblk_devices_sem);
PRINT_WARN("Segment %s is not loaded!\n", local_buf);
rc = -ENODEV;
goto out_buf;
}
if (atomic_read(&dev_info->use_count) != 0) {
up_write(&dcssblk_devices_sem);
PRINT_WARN("Segment %s is in use!\n", local_buf);
rc = -EBUSY;
goto out_buf;
}
list_del(&dev_info->lh);
del_gendisk(dev_info->gd);
blk_put_queue(dev_info->dcssblk_queue);
dev_info->gd->queue = NULL;
put_disk(dev_info->gd);
device_unregister(&dev_info->dev);
segment_unload(dev_info->segment_name);
PRINT_DEBUG("Segment %s unloaded successfully\n",
dev_info->segment_name);
put_device(&dev_info->dev);
up_write(&dcssblk_devices_sem);
rc = count;
out_buf:
kfree(local_buf);
return rc;
}
static int
dcssblk_open(struct inode *inode, struct file *filp)
{
struct dcssblk_dev_info *dev_info;
int rc;
dev_info = inode->i_bdev->bd_disk->private_data;
if (NULL == dev_info) {
rc = -ENODEV;
goto out;
}
atomic_inc(&dev_info->use_count);
inode->i_bdev->bd_block_size = 4096;
rc = 0;
out:
return rc;
}
static int
dcssblk_release(struct inode *inode, struct file *filp)
{
struct dcssblk_dev_info *dev_info;
int rc;
dev_info = inode->i_bdev->bd_disk->private_data;
if (NULL == dev_info) {
rc = -ENODEV;
goto out;
}
down_write(&dcssblk_devices_sem);
if (atomic_dec_and_test(&dev_info->use_count)
&& (dev_info->save_pending)) {
PRINT_INFO("Segment %s became idle and is being saved now\n",
dev_info->segment_name);
segment_save(dev_info->segment_name);
dev_info->save_pending = 0;
}
up_write(&dcssblk_devices_sem);
rc = 0;
out:
return rc;
}
static int
dcssblk_make_request(request_queue_t *q, struct bio *bio)
{
struct dcssblk_dev_info *dev_info;
struct bio_vec *bvec;
unsigned long index;
unsigned long page_addr;
unsigned long source_addr;
unsigned long bytes_done;
int i;
bytes_done = 0;
dev_info = bio->bi_bdev->bd_disk->private_data;
if (dev_info == NULL)
goto fail;
if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
/* Request is not page-aligned. */
goto fail;
if (((bio->bi_size >> 9) + bio->bi_sector)
> get_capacity(bio->bi_bdev->bd_disk)) {
/* Request beyond end of DCSS segment. */
goto fail;
}
index = (bio->bi_sector >> 3);
bio_for_each_segment(bvec, bio, i) {
page_addr = (unsigned long)
page_address(bvec->bv_page) + bvec->bv_offset;
source_addr = dev_info->start + (index<<12) + bytes_done;
if (unlikely(page_addr & 4095) != 0 || (bvec->bv_len & 4095) != 0)
// More paranoia.
goto fail;
if (bio_data_dir(bio) == READ) {
memcpy((void*)page_addr, (void*)source_addr,
bvec->bv_len);
} else {
memcpy((void*)source_addr, (void*)page_addr,
bvec->bv_len);
}
bytes_done += bvec->bv_len;
}
bio_endio(bio, bytes_done, 0);
return 0;
fail:
bio_io_error(bio, bytes_done);
return 0;
}
static void
dcssblk_check_params(void)
{
int rc, i, j, k;
char buf[9];
struct dcssblk_dev_info *dev_info;
for (i = 0; (i < DCSSBLK_PARM_LEN) && (dcssblk_segments[i] != '\0');
i++) {
for (j = i; (dcssblk_segments[j] != ',') &&
(dcssblk_segments[j] != '\0') &&
(dcssblk_segments[j] != '(') &&
(j - i) < 8; j++)
{
buf[j-i] = dcssblk_segments[j];
}
buf[j-i] = '\0';
rc = dcssblk_add_store(dcssblk_root_dev, buf, j-i);
if ((rc >= 0) && (dcssblk_segments[j] == '(')) {
for (k = 0; buf[k] != '\0'; k++)
buf[k] = toupper(buf[k]);
if (!strncmp(&dcssblk_segments[j], "(local)", 7)) {
down_read(&dcssblk_devices_sem);
dev_info = dcssblk_get_device_by_name(buf);
up_read(&dcssblk_devices_sem);
if (dev_info)
dcssblk_shared_store(&dev_info->dev,
"0\n", 2);
}
}
while ((dcssblk_segments[j] != ',') &&
(dcssblk_segments[j] != '\0'))
{
j++;
}
if (dcssblk_segments[j] == '\0')
break;
i = j;
}
}
/*
* The init/exit functions.
*/
static void __exit
dcssblk_exit(void)
{
int rc;
PRINT_DEBUG("DCSSBLOCK EXIT...\n");
s390_root_dev_unregister(dcssblk_root_dev);
rc = unregister_blkdev(dcssblk_major, DCSSBLK_NAME);
if (rc) {
PRINT_ERR("unregister_blkdev() failed!\n");
}
PRINT_DEBUG("...finished!\n");
}
static int __init
dcssblk_init(void)
{
int rc;
PRINT_DEBUG("DCSSBLOCK INIT...\n");
dcssblk_root_dev = s390_root_dev_register("dcssblk");
if (IS_ERR(dcssblk_root_dev)) {
PRINT_ERR("device_register() failed!\n");
return PTR_ERR(dcssblk_root_dev);
}
rc = device_create_file(dcssblk_root_dev, &dev_attr_add);
if (rc) {
PRINT_ERR("device_create_file(add) failed!\n");
s390_root_dev_unregister(dcssblk_root_dev);
return rc;
}
rc = device_create_file(dcssblk_root_dev, &dev_attr_remove);
if (rc) {
PRINT_ERR("device_create_file(remove) failed!\n");
s390_root_dev_unregister(dcssblk_root_dev);
return rc;
}
rc = register_blkdev(0, DCSSBLK_NAME);
if (rc < 0) {
PRINT_ERR("Can't get dynamic major!\n");
s390_root_dev_unregister(dcssblk_root_dev);
return rc;
}
dcssblk_major = rc;
init_rwsem(&dcssblk_devices_sem);
dcssblk_check_params();
PRINT_DEBUG("...finished!\n");
return 0;
}
module_init(dcssblk_init);
module_exit(dcssblk_exit);
module_param_string(segments, dcssblk_segments, DCSSBLK_PARM_LEN, 0444);
MODULE_PARM_DESC(segments, "Name of DCSS segment(s) to be loaded, "
"comma-separated list, each name max. 8 chars.\n"
"Adding \"(local)\" to segment name equals echoing 0 to "
"/sys/devices/dcssblk/<segment name>/shared after loading "
"the segment - \n"
"e.g. segments=\"mydcss1,mydcss2,mydcss3(local)\"");
MODULE_LICENSE("GPL");

539
drivers/s390/block/xpram.c Normal file
View file

@ -0,0 +1,539 @@
/*
* Xpram.c -- the S/390 expanded memory RAM-disk
*
* significant parts of this code are based on
* the sbull device driver presented in
* A. Rubini: Linux Device Drivers
*
* Author of XPRAM specific coding: Reinhard Buendgen
* buendgen@de.ibm.com
* Rewrite for 2.5: Martin Schwidefsky <schwidefsky@de.ibm.com>
*
* External interfaces:
* Interfaces to linux kernel
* xpram_setup: read kernel parameters
* Device specific file operations
* xpram_iotcl
* xpram_open
*
* "ad-hoc" partitioning:
* the expanded memory can be partitioned among several devices
* (with different minors). The partitioning set up can be
* set by kernel or module parameters (int devs & int sizes[])
*
* Potential future improvements:
* generic hard disk support to replace ad-hoc partitioning
*/
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/ctype.h> /* isdigit, isxdigit */
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/blkdev.h>
#include <linux/blkpg.h>
#include <linux/hdreg.h> /* HDIO_GETGEO */
#include <linux/sysdev.h>
#include <linux/bio.h>
#include <linux/devfs_fs_kernel.h>
#include <asm/uaccess.h>
#define XPRAM_NAME "xpram"
#define XPRAM_DEVS 1 /* one partition */
#define XPRAM_MAX_DEVS 32 /* maximal number of devices (partitions) */
#define PRINT_DEBUG(x...) printk(KERN_DEBUG XPRAM_NAME " debug:" x)
#define PRINT_INFO(x...) printk(KERN_INFO XPRAM_NAME " info:" x)
#define PRINT_WARN(x...) printk(KERN_WARNING XPRAM_NAME " warning:" x)
#define PRINT_ERR(x...) printk(KERN_ERR XPRAM_NAME " error:" x)
static struct sysdev_class xpram_sysclass = {
set_kset_name("xpram"),
};
static struct sys_device xpram_sys_device = {
.id = 0,
.cls = &xpram_sysclass,
};
typedef struct {
unsigned int size; /* size of xpram segment in pages */
unsigned int offset; /* start page of xpram segment */
} xpram_device_t;
static xpram_device_t xpram_devices[XPRAM_MAX_DEVS];
static unsigned int xpram_sizes[XPRAM_MAX_DEVS];
static struct gendisk *xpram_disks[XPRAM_MAX_DEVS];
static unsigned int xpram_pages;
static int xpram_devs;
/*
* Parameter parsing functions.
*/
static int devs = XPRAM_DEVS;
static unsigned int sizes[XPRAM_MAX_DEVS];
module_param(devs, int, 0);
module_param_array(sizes, int, NULL, 0);
MODULE_PARM_DESC(devs, "number of devices (\"partitions\"), " \
"the default is " __MODULE_STRING(XPRAM_DEVS) "\n");
MODULE_PARM_DESC(sizes, "list of device (partition) sizes " \
"the defaults are 0s \n" \
"All devices with size 0 equally partition the "
"remaining space on the expanded strorage not "
"claimed by explicit sizes\n");
MODULE_LICENSE("GPL");
#ifndef MODULE
/*
* Parses the kernel parameters given in the kernel parameter line.
* The expected format is
* <number_of_partitions>[","<partition_size>]*
* where
* devices is a positive integer that initializes xpram_devs
* each size is a non-negative integer possibly followed by a
* magnitude (k,K,m,M,g,G), the list of sizes initialises
* xpram_sizes
*
* Arguments
* str: substring of kernel parameter line that contains xprams
* kernel parameters.
*
* Result 0 on success, -EINVAL else -- only for Version > 2.3
*
* Side effects
* the global variabls devs is set to the value of
* <number_of_partitions> and sizes[i] is set to the i-th
* partition size (if provided). A parsing error of a value
* results in this value being set to -EINVAL.
*/
static int __init xpram_setup (char *str)
{
char *cp;
int i;
devs = simple_strtoul(str, &cp, 10);
if (cp <= str || devs > XPRAM_MAX_DEVS)
return 0;
for (i = 0; (i < devs) && (*cp++ == ','); i++) {
sizes[i] = simple_strtoul(cp, &cp, 10);
if (*cp == 'g' || *cp == 'G') {
sizes[i] <<= 20;
cp++;
} else if (*cp == 'm' || *cp == 'M') {
sizes[i] <<= 10;
cp++;
} else if (*cp == 'k' || *cp == 'K')
cp++;
while (isspace(*cp)) cp++;
}
if (*cp == ',' && i >= devs)
PRINT_WARN("partition sizes list has too many entries.\n");
else if (*cp != 0)
PRINT_WARN("ignored '%s' at end of parameter string.\n", cp);
return 1;
}
__setup("xpram_parts=", xpram_setup);
#endif
/*
* Copy expanded memory page (4kB) into main memory
* Arguments
* page_addr: address of target page
* xpage_index: index of expandeded memory page
* Return value
* 0: if operation succeeds
* -EIO: if pgin failed
* -ENXIO: if xpram has vanished
*/
static int xpram_page_in (unsigned long page_addr, unsigned int xpage_index)
{
int cc;
__asm__ __volatile__ (
" lhi %0,2\n" /* return unused cc 2 if pgin traps */
" .insn rre,0xb22e0000,%1,%2\n" /* pgin %1,%2 */
"0: ipm %0\n"
" srl %0,28\n"
"1:\n"
#ifndef CONFIG_ARCH_S390X
".section __ex_table,\"a\"\n"
" .align 4\n"
" .long 0b,1b\n"
".previous"
#else
".section __ex_table,\"a\"\n"
" .align 8\n"
" .quad 0b,1b\n"
".previous"
#endif
: "=&d" (cc)
: "a" (__pa(page_addr)), "a" (xpage_index)
: "cc" );
if (cc == 3)
return -ENXIO;
if (cc == 2) {
PRINT_ERR("expanded storage lost!\n");
return -ENXIO;
}
if (cc == 1) {
PRINT_ERR("page in failed for page index %u.\n",
xpage_index);
return -EIO;
}
return 0;
}
/*
* Copy a 4kB page of main memory to an expanded memory page
* Arguments
* page_addr: address of source page
* xpage_index: index of expandeded memory page
* Return value
* 0: if operation succeeds
* -EIO: if pgout failed
* -ENXIO: if xpram has vanished
*/
static long xpram_page_out (unsigned long page_addr, unsigned int xpage_index)
{
int cc;
__asm__ __volatile__ (
" lhi %0,2\n" /* return unused cc 2 if pgout traps */
" .insn rre,0xb22f0000,%1,%2\n" /* pgout %1,%2 */
"0: ipm %0\n"
" srl %0,28\n"
"1:\n"
#ifndef CONFIG_ARCH_S390X
".section __ex_table,\"a\"\n"
" .align 4\n"
" .long 0b,1b\n"
".previous"
#else
".section __ex_table,\"a\"\n"
" .align 8\n"
" .quad 0b,1b\n"
".previous"
#endif
: "=&d" (cc)
: "a" (__pa(page_addr)), "a" (xpage_index)
: "cc" );
if (cc == 3)
return -ENXIO;
if (cc == 2) {
PRINT_ERR("expanded storage lost!\n");
return -ENXIO;
}
if (cc == 1) {
PRINT_ERR("page out failed for page index %u.\n",
xpage_index);
return -EIO;
}
return 0;
}
/*
* Check if xpram is available.
*/
static int __init xpram_present(void)
{
unsigned long mem_page;
int rc;
mem_page = (unsigned long) __get_free_page(GFP_KERNEL);
if (!mem_page)
return -ENOMEM;
rc = xpram_page_in(mem_page, 0);
free_page(mem_page);
return rc ? -ENXIO : 0;
}
/*
* Return index of the last available xpram page.
*/
static unsigned long __init xpram_highest_page_index(void)
{
unsigned int page_index, add_bit;
unsigned long mem_page;
mem_page = (unsigned long) __get_free_page(GFP_KERNEL);
if (!mem_page)
return 0;
page_index = 0;
add_bit = 1ULL << (sizeof(unsigned int)*8 - 1);
while (add_bit > 0) {
if (xpram_page_in(mem_page, page_index | add_bit) == 0)
page_index |= add_bit;
add_bit >>= 1;
}
free_page (mem_page);
return page_index;
}
/*
* Block device make request function.
*/
static int xpram_make_request(request_queue_t *q, struct bio *bio)
{
xpram_device_t *xdev = bio->bi_bdev->bd_disk->private_data;
struct bio_vec *bvec;
unsigned int index;
unsigned long page_addr;
unsigned long bytes;
int i;
if ((bio->bi_sector & 7) != 0 || (bio->bi_size & 4095) != 0)
/* Request is not page-aligned. */
goto fail;
if ((bio->bi_size >> 12) > xdev->size)
/* Request size is no page-aligned. */
goto fail;
if ((bio->bi_sector >> 3) > 0xffffffffU - xdev->offset)
goto fail;
index = (bio->bi_sector >> 3) + xdev->offset;
bio_for_each_segment(bvec, bio, i) {
page_addr = (unsigned long)
kmap(bvec->bv_page) + bvec->bv_offset;
bytes = bvec->bv_len;
if ((page_addr & 4095) != 0 || (bytes & 4095) != 0)
/* More paranoia. */
goto fail;
while (bytes > 0) {
if (bio_data_dir(bio) == READ) {
if (xpram_page_in(page_addr, index) != 0)
goto fail;
} else {
if (xpram_page_out(page_addr, index) != 0)
goto fail;
}
page_addr += 4096;
bytes -= 4096;
index++;
}
}
set_bit(BIO_UPTODATE, &bio->bi_flags);
bytes = bio->bi_size;
bio->bi_size = 0;
bio->bi_end_io(bio, bytes, 0);
return 0;
fail:
bio_io_error(bio, bio->bi_size);
return 0;
}
static int xpram_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct hd_geometry __user *geo;
unsigned long size;
if (cmd != HDIO_GETGEO)
return -EINVAL;
/*
* get geometry: we have to fake one... trim the size to a
* multiple of 64 (32k): tell we have 16 sectors, 4 heads,
* whatever cylinders. Tell also that data starts at sector. 4.
*/
geo = (struct hd_geometry __user *) arg;
size = (xpram_pages * 8) & ~0x3f;
put_user(size >> 6, &geo->cylinders);
put_user(4, &geo->heads);
put_user(16, &geo->sectors);
put_user(4, &geo->start);
return 0;
}
static struct block_device_operations xpram_devops =
{
.owner = THIS_MODULE,
.ioctl = xpram_ioctl,
};
/*
* Setup xpram_sizes array.
*/
static int __init xpram_setup_sizes(unsigned long pages)
{
unsigned long mem_needed;
unsigned long mem_auto;
int mem_auto_no;
int i;
/* Check number of devices. */
if (devs <= 0 || devs > XPRAM_MAX_DEVS) {
PRINT_ERR("invalid number %d of devices\n",devs);
return -EINVAL;
}
xpram_devs = devs;
/*
* Copy sizes array to xpram_sizes and align partition
* sizes to page boundary.
*/
mem_needed = 0;
mem_auto_no = 0;
for (i = 0; i < xpram_devs; i++) {
xpram_sizes[i] = (sizes[i] + 3) & -4UL;
if (xpram_sizes[i])
mem_needed += xpram_sizes[i];
else
mem_auto_no++;
}
PRINT_INFO(" number of devices (partitions): %d \n", xpram_devs);
for (i = 0; i < xpram_devs; i++) {
if (xpram_sizes[i])
PRINT_INFO(" size of partition %d: %u kB\n",
i, xpram_sizes[i]);
else
PRINT_INFO(" size of partition %d to be set "
"automatically\n",i);
}
PRINT_DEBUG(" memory needed (for sized partitions): %lu kB\n",
mem_needed);
PRINT_DEBUG(" partitions to be sized automatically: %d\n",
mem_auto_no);
if (mem_needed > pages * 4) {
PRINT_ERR("Not enough expanded memory available\n");
return -EINVAL;
}
/*
* partitioning:
* xpram_sizes[i] != 0; partition i has size xpram_sizes[i] kB
* else: ; all partitions with zero xpram_sizes[i]
* partition equally the remaining space
*/
if (mem_auto_no) {
mem_auto = ((pages - mem_needed / 4) / mem_auto_no) * 4;
PRINT_INFO(" automatically determined "
"partition size: %lu kB\n", mem_auto);
for (i = 0; i < xpram_devs; i++)
if (xpram_sizes[i] == 0)
xpram_sizes[i] = mem_auto;
}
return 0;
}
static struct request_queue *xpram_queue;
static int __init xpram_setup_blkdev(void)
{
unsigned long offset;
int i, rc = -ENOMEM;
for (i = 0; i < xpram_devs; i++) {
struct gendisk *disk = alloc_disk(1);
if (!disk)
goto out;
xpram_disks[i] = disk;
}
/*
* Register xpram major.
*/
rc = register_blkdev(XPRAM_MAJOR, XPRAM_NAME);
if (rc < 0)
goto out;
devfs_mk_dir("slram");
/*
* Assign the other needed values: make request function, sizes and
* hardsect size. All the minor devices feature the same value.
*/
xpram_queue = blk_alloc_queue(GFP_KERNEL);
if (!xpram_queue) {
rc = -ENOMEM;
goto out_unreg;
}
blk_queue_make_request(xpram_queue, xpram_make_request);
blk_queue_hardsect_size(xpram_queue, 4096);
/*
* Setup device structures.
*/
offset = 0;
for (i = 0; i < xpram_devs; i++) {
struct gendisk *disk = xpram_disks[i];
xpram_devices[i].size = xpram_sizes[i] / 4;
xpram_devices[i].offset = offset;
offset += xpram_devices[i].size;
disk->major = XPRAM_MAJOR;
disk->first_minor = i;
disk->fops = &xpram_devops;
disk->private_data = &xpram_devices[i];
disk->queue = xpram_queue;
sprintf(disk->disk_name, "slram%d", i);
sprintf(disk->devfs_name, "slram/%d", i);
set_capacity(disk, xpram_sizes[i] << 1);
add_disk(disk);
}
return 0;
out_unreg:
devfs_remove("slram");
unregister_blkdev(XPRAM_MAJOR, XPRAM_NAME);
out:
while (i--)
put_disk(xpram_disks[i]);
return rc;
}
/*
* Finally, the init/exit functions.
*/
static void __exit xpram_exit(void)
{
int i;
for (i = 0; i < xpram_devs; i++) {
del_gendisk(xpram_disks[i]);
put_disk(xpram_disks[i]);
}
unregister_blkdev(XPRAM_MAJOR, XPRAM_NAME);
devfs_remove("slram");
blk_cleanup_queue(xpram_queue);
sysdev_unregister(&xpram_sys_device);
sysdev_class_unregister(&xpram_sysclass);
}
static int __init xpram_init(void)
{
int rc;
/* Find out size of expanded memory. */
if (xpram_present() != 0) {
PRINT_WARN("No expanded memory available\n");
return -ENODEV;
}
xpram_pages = xpram_highest_page_index();
PRINT_INFO(" %u pages expanded memory found (%lu KB).\n",
xpram_pages, (unsigned long) xpram_pages*4);
rc = xpram_setup_sizes(xpram_pages);
if (rc)
return rc;
rc = sysdev_class_register(&xpram_sysclass);
if (rc)
return rc;
rc = sysdev_register(&xpram_sys_device);
if (rc) {
sysdev_class_unregister(&xpram_sysclass);
return rc;
}
rc = xpram_setup_blkdev();
if (rc)
sysdev_unregister(&xpram_sys_device);
return rc;
}
module_init(xpram_init);
module_exit(xpram_exit);

View file

@ -0,0 +1,28 @@
#
# S/390 character devices
#
obj-y += ctrlchar.o keyboard.o defkeymap.o
obj-$(CONFIG_TN3270) += raw3270.o
obj-$(CONFIG_TN3270_CONSOLE) += con3270.o
obj-$(CONFIG_TN3270_TTY) += tty3270.o
obj-$(CONFIG_TN3270_FS) += fs3270.o
obj-$(CONFIG_TN3215) += con3215.o
obj-$(CONFIG_SCLP) += sclp.o sclp_rw.o sclp_quiesce.o
obj-$(CONFIG_SCLP_TTY) += sclp_tty.o
obj-$(CONFIG_SCLP_CONSOLE) += sclp_con.o
obj-$(CONFIG_SCLP_VT220_TTY) += sclp_vt220.o
obj-$(CONFIG_SCLP_CPI) += sclp_cpi.o
obj-$(CONFIG_ZVM_WATCHDOG) += vmwatchdog.o
obj-$(CONFIG_VMLOGRDR) += vmlogrdr.o
tape-$(CONFIG_S390_TAPE_BLOCK) += tape_block.o
tape-$(CONFIG_PROC_FS) += tape_proc.o
tape-objs := tape_core.o tape_std.o tape_char.o $(tape-y)
obj-$(CONFIG_S390_TAPE) += tape.o tape_class.o
obj-$(CONFIG_S390_TAPE_34XX) += tape_34xx.o
obj-$(CONFIG_MONREADER) += monreader.o

1192
drivers/s390/char/con3215.c Normal file

File diff suppressed because it is too large Load diff

638
drivers/s390/char/con3270.c Normal file
View file

@ -0,0 +1,638 @@
/*
* drivers/s390/char/con3270.c
* IBM/3270 Driver - console view.
*
* Author(s):
* Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
* Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com>
* -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
*/
#include <linux/config.h>
#include <linux/bootmem.h>
#include <linux/console.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/types.h>
#include <asm/ccwdev.h>
#include <asm/cio.h>
#include <asm/cpcmd.h>
#include <asm/ebcdic.h>
#include "raw3270.h"
#include "ctrlchar.h"
#define CON3270_OUTPUT_BUFFER_SIZE 1024
#define CON3270_STRING_PAGES 4
static struct raw3270_fn con3270_fn;
/*
* Main 3270 console view data structure.
*/
struct con3270 {
struct raw3270_view view;
spinlock_t lock;
struct list_head freemem; /* list of free memory for strings. */
/* Output stuff. */
struct list_head lines; /* list of lines. */
struct list_head update; /* list of lines to update. */
int line_nr; /* line number for next update. */
int nr_lines; /* # lines in list. */
int nr_up; /* # lines up in history. */
unsigned long update_flags; /* Update indication bits. */
struct string *cline; /* current output line. */
struct string *status; /* last line of display. */
struct raw3270_request *write; /* single write request. */
struct timer_list timer;
/* Input stuff. */
struct string *input; /* input string for read request. */
struct raw3270_request *read; /* single read request. */
struct raw3270_request *kreset; /* single keyboard reset request. */
struct tasklet_struct readlet; /* tasklet to issue read request. */
};
static struct con3270 *condev;
/* con3270->update_flags. See con3270_update for details. */
#define CON_UPDATE_ERASE 1 /* Use EWRITEA instead of WRITE. */
#define CON_UPDATE_LIST 2 /* Update lines in tty3270->update. */
#define CON_UPDATE_STATUS 4 /* Update status line. */
#define CON_UPDATE_ALL 7
static void con3270_update(struct con3270 *);
/*
* Setup timeout for a device. On timeout trigger an update.
*/
void
con3270_set_timer(struct con3270 *cp, int expires)
{
if (expires == 0) {
if (timer_pending(&cp->timer))
del_timer(&cp->timer);
return;
}
if (timer_pending(&cp->timer) &&
mod_timer(&cp->timer, jiffies + expires))
return;
cp->timer.function = (void (*)(unsigned long)) con3270_update;
cp->timer.data = (unsigned long) cp;
cp->timer.expires = jiffies + expires;
add_timer(&cp->timer);
}
/*
* The status line is the last line of the screen. It shows the string
* "console view" in the lower left corner and "Running"/"More..."/"Holding"
* in the lower right corner of the screen.
*/
static void
con3270_update_status(struct con3270 *cp)
{
char *str;
str = (cp->nr_up != 0) ? "History" : "Running";
memcpy(cp->status->string + 24, str, 7);
codepage_convert(cp->view.ascebc, cp->status->string + 24, 7);
cp->update_flags |= CON_UPDATE_STATUS;
}
static void
con3270_create_status(struct con3270 *cp)
{
static const unsigned char blueprint[] =
{ TO_SBA, 0, 0, TO_SF,TF_LOG,TO_SA,TAT_COLOR, TAC_GREEN,
'c','o','n','s','o','l','e',' ','v','i','e','w',
TO_RA,0,0,0,'R','u','n','n','i','n','g',TO_SF,TF_LOG };
cp->status = alloc_string(&cp->freemem, sizeof(blueprint));
/* Copy blueprint to status line */
memcpy(cp->status->string, blueprint, sizeof(blueprint));
/* Set TO_RA addresses. */
raw3270_buffer_address(cp->view.dev, cp->status->string + 1,
cp->view.cols * (cp->view.rows - 1));
raw3270_buffer_address(cp->view.dev, cp->status->string + 21,
cp->view.cols * cp->view.rows - 8);
/* Convert strings to ebcdic. */
codepage_convert(cp->view.ascebc, cp->status->string + 8, 12);
codepage_convert(cp->view.ascebc, cp->status->string + 24, 7);
}
/*
* Set output offsets to 3270 datastream fragment of a console string.
*/
static void
con3270_update_string(struct con3270 *cp, struct string *s, int nr)
{
if (s->len >= cp->view.cols - 5)
return;
raw3270_buffer_address(cp->view.dev, s->string + s->len - 3,
cp->view.cols * (nr + 1));
}
/*
* Rebuild update list to print all lines.
*/
static void
con3270_rebuild_update(struct con3270 *cp)
{
struct string *s, *n;
int nr;
/*
* Throw away update list and create a new one,
* containing all lines that will fit on the screen.
*/
list_for_each_entry_safe(s, n, &cp->update, update)
list_del_init(&s->update);
nr = cp->view.rows - 2 + cp->nr_up;
list_for_each_entry_reverse(s, &cp->lines, list) {
if (nr < cp->view.rows - 1)
list_add(&s->update, &cp->update);
if (--nr < 0)
break;
}
cp->line_nr = 0;
cp->update_flags |= CON_UPDATE_LIST;
}
/*
* Alloc string for size bytes. Free strings from history if necessary.
*/
static struct string *
con3270_alloc_string(struct con3270 *cp, size_t size)
{
struct string *s, *n;
s = alloc_string(&cp->freemem, size);
if (s)
return s;
list_for_each_entry_safe(s, n, &cp->lines, list) {
list_del(&s->list);
if (!list_empty(&s->update))
list_del(&s->update);
cp->nr_lines--;
if (free_string(&cp->freemem, s) >= size)
break;
}
s = alloc_string(&cp->freemem, size);
BUG_ON(!s);
if (cp->nr_up != 0 && cp->nr_up + cp->view.rows > cp->nr_lines) {
cp->nr_up = cp->nr_lines - cp->view.rows + 1;
con3270_rebuild_update(cp);
con3270_update_status(cp);
}
return s;
}
/*
* Write completion callback.
*/
static void
con3270_write_callback(struct raw3270_request *rq, void *data)
{
raw3270_request_reset(rq);
xchg(&((struct con3270 *) rq->view)->write, rq);
}
/*
* Update console display.
*/
static void
con3270_update(struct con3270 *cp)
{
struct raw3270_request *wrq;
char wcc, prolog[6];
unsigned long flags;
unsigned long updated;
struct string *s, *n;
int rc;
wrq = xchg(&cp->write, 0);
if (!wrq) {
con3270_set_timer(cp, 1);
return;
}
spin_lock_irqsave(&cp->view.lock, flags);
updated = 0;
if (cp->update_flags & CON_UPDATE_ERASE) {
/* Use erase write alternate to initialize display. */
raw3270_request_set_cmd(wrq, TC_EWRITEA);
updated |= CON_UPDATE_ERASE;
} else
raw3270_request_set_cmd(wrq, TC_WRITE);
wcc = TW_NONE;
raw3270_request_add_data(wrq, &wcc, 1);
/*
* Update status line.
*/
if (cp->update_flags & CON_UPDATE_STATUS)
if (raw3270_request_add_data(wrq, cp->status->string,
cp->status->len) == 0)
updated |= CON_UPDATE_STATUS;
if (cp->update_flags & CON_UPDATE_LIST) {
prolog[0] = TO_SBA;
prolog[3] = TO_SA;
prolog[4] = TAT_COLOR;
prolog[5] = TAC_TURQ;
raw3270_buffer_address(cp->view.dev, prolog + 1,
cp->view.cols * cp->line_nr);
raw3270_request_add_data(wrq, prolog, 6);
/* Write strings in the update list to the screen. */
list_for_each_entry_safe(s, n, &cp->update, update) {
if (s != cp->cline)
con3270_update_string(cp, s, cp->line_nr);
if (raw3270_request_add_data(wrq, s->string,
s->len) != 0)
break;
list_del_init(&s->update);
if (s != cp->cline)
cp->line_nr++;
}
if (list_empty(&cp->update))
updated |= CON_UPDATE_LIST;
}
wrq->callback = con3270_write_callback;
rc = raw3270_start(&cp->view, wrq);
if (rc == 0) {
cp->update_flags &= ~updated;
if (cp->update_flags)
con3270_set_timer(cp, 1);
} else {
raw3270_request_reset(wrq);
xchg(&cp->write, wrq);
}
spin_unlock_irqrestore(&cp->view.lock, flags);
}
/*
* Read tasklet.
*/
static void
con3270_read_tasklet(struct raw3270_request *rrq)
{
static char kreset_data = TW_KR;
struct con3270 *cp;
unsigned long flags;
int nr_up, deactivate;
cp = (struct con3270 *) rrq->view;
spin_lock_irqsave(&cp->view.lock, flags);
nr_up = cp->nr_up;
deactivate = 0;
/* Check aid byte. */
switch (cp->input->string[0]) {
case 0x7d: /* enter: jump to bottom. */
nr_up = 0;
break;
case 0xf3: /* PF3: deactivate the console view. */
deactivate = 1;
break;
case 0x6d: /* clear: start from scratch. */
con3270_rebuild_update(cp);
cp->update_flags = CON_UPDATE_ALL;
con3270_set_timer(cp, 1);
break;
case 0xf7: /* PF7: do a page up in the console log. */
nr_up += cp->view.rows - 2;
if (nr_up + cp->view.rows - 1 > cp->nr_lines) {
nr_up = cp->nr_lines - cp->view.rows + 1;
if (nr_up < 0)
nr_up = 0;
}
break;
case 0xf8: /* PF8: do a page down in the console log. */
nr_up -= cp->view.rows - 2;
if (nr_up < 0)
nr_up = 0;
break;
}
if (nr_up != cp->nr_up) {
cp->nr_up = nr_up;
con3270_rebuild_update(cp);
con3270_update_status(cp);
con3270_set_timer(cp, 1);
}
spin_unlock_irqrestore(&cp->view.lock, flags);
/* Start keyboard reset command. */
raw3270_request_reset(cp->kreset);
raw3270_request_set_cmd(cp->kreset, TC_WRITE);
raw3270_request_add_data(cp->kreset, &kreset_data, 1);
raw3270_start(&cp->view, cp->kreset);
if (deactivate)
raw3270_deactivate_view(&cp->view);
raw3270_request_reset(rrq);
xchg(&cp->read, rrq);
raw3270_put_view(&cp->view);
}
/*
* Read request completion callback.
*/
static void
con3270_read_callback(struct raw3270_request *rq, void *data)
{
raw3270_get_view(rq->view);
/* Schedule tasklet to pass input to tty. */
tasklet_schedule(&((struct con3270 *) rq->view)->readlet);
}
/*
* Issue a read request. Called only from interrupt function.
*/
static void
con3270_issue_read(struct con3270 *cp)
{
struct raw3270_request *rrq;
int rc;
rrq = xchg(&cp->read, 0);
if (!rrq)
/* Read already scheduled. */
return;
rrq->callback = con3270_read_callback;
rrq->callback_data = cp;
raw3270_request_set_cmd(rrq, TC_READMOD);
raw3270_request_set_data(rrq, cp->input->string, cp->input->len);
/* Issue the read modified request. */
rc = raw3270_start_irq(&cp->view, rrq);
if (rc)
raw3270_request_reset(rrq);
}
/*
* Switch to the console view.
*/
static int
con3270_activate(struct raw3270_view *view)
{
unsigned long flags;
struct con3270 *cp;
cp = (struct con3270 *) view;
spin_lock_irqsave(&cp->view.lock, flags);
cp->nr_up = 0;
con3270_rebuild_update(cp);
con3270_update_status(cp);
cp->update_flags = CON_UPDATE_ALL;
con3270_set_timer(cp, 1);
spin_unlock_irqrestore(&cp->view.lock, flags);
return 0;
}
static void
con3270_deactivate(struct raw3270_view *view)
{
unsigned long flags;
struct con3270 *cp;
cp = (struct con3270 *) view;
spin_lock_irqsave(&cp->view.lock, flags);
del_timer(&cp->timer);
spin_unlock_irqrestore(&cp->view.lock, flags);
}
static int
con3270_irq(struct con3270 *cp, struct raw3270_request *rq, struct irb *irb)
{
/* Handle ATTN. Schedule tasklet to read aid. */
if (irb->scsw.dstat & DEV_STAT_ATTENTION)
con3270_issue_read(cp);
if (rq) {
if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
rq->rc = -EIO;
else
/* Normal end. Copy residual count. */
rq->rescnt = irb->scsw.count;
}
return RAW3270_IO_DONE;
}
/* Console view to a 3270 device. */
static struct raw3270_fn con3270_fn = {
.activate = con3270_activate,
.deactivate = con3270_deactivate,
.intv = (void *) con3270_irq
};
static inline void
con3270_cline_add(struct con3270 *cp)
{
if (!list_empty(&cp->cline->list))
/* Already added. */
return;
list_add_tail(&cp->cline->list, &cp->lines);
cp->nr_lines++;
con3270_rebuild_update(cp);
}
static inline void
con3270_cline_insert(struct con3270 *cp, unsigned char c)
{
cp->cline->string[cp->cline->len++] =
cp->view.ascebc[(c < ' ') ? ' ' : c];
if (list_empty(&cp->cline->update)) {
list_add_tail(&cp->cline->update, &cp->update);
cp->update_flags |= CON_UPDATE_LIST;
}
}
static inline void
con3270_cline_end(struct con3270 *cp)
{
struct string *s;
unsigned int size;
/* Copy cline. */
size = (cp->cline->len < cp->view.cols - 5) ?
cp->cline->len + 4 : cp->view.cols;
s = con3270_alloc_string(cp, size);
memcpy(s->string, cp->cline->string, cp->cline->len);
if (s->len < cp->view.cols - 5) {
s->string[s->len - 4] = TO_RA;
s->string[s->len - 1] = 0;
} else {
while (--size > cp->cline->len)
s->string[size] = cp->view.ascebc[' '];
}
/* Replace cline with allocated line s and reset cline. */
list_add(&s->list, &cp->cline->list);
list_del_init(&cp->cline->list);
if (!list_empty(&cp->cline->update)) {
list_add(&s->update, &cp->cline->update);
list_del_init(&cp->cline->update);
}
cp->cline->len = 0;
}
/*
* Write a string to the 3270 console
*/
static void
con3270_write(struct console *co, const char *str, unsigned int count)
{
struct con3270 *cp;
unsigned long flags;
unsigned char c;
cp = condev;
if (cp->view.dev)
raw3270_activate_view(&cp->view);
spin_lock_irqsave(&cp->view.lock, flags);
while (count-- > 0) {
c = *str++;
if (cp->cline->len == 0)
con3270_cline_add(cp);
if (c != '\n')
con3270_cline_insert(cp, c);
if (c == '\n' || cp->cline->len >= cp->view.cols)
con3270_cline_end(cp);
}
/* Setup timer to output current console buffer after 1/10 second */
if (cp->view.dev && !timer_pending(&cp->timer))
con3270_set_timer(cp, HZ/10);
spin_unlock_irqrestore(&cp->view.lock,flags);
}
extern struct tty_driver *tty3270_driver;
static struct tty_driver *
con3270_device(struct console *c, int *index)
{
*index = c->index;
return tty3270_driver;
}
/*
* Wait for end of write request.
*/
static void
con3270_wait_write(struct con3270 *cp)
{
while (!cp->write) {
raw3270_wait_cons_dev(cp->view.dev);
barrier();
}
}
/*
* panic() calls console_unblank before the system enters a
* disabled, endless loop.
*/
static void
con3270_unblank(void)
{
struct con3270 *cp;
unsigned long flags;
cp = condev;
if (!cp->view.dev)
return;
spin_lock_irqsave(&cp->view.lock, flags);
con3270_wait_write(cp);
cp->nr_up = 0;
con3270_rebuild_update(cp);
con3270_update_status(cp);
while (cp->update_flags != 0) {
spin_unlock_irqrestore(&cp->view.lock, flags);
con3270_update(cp);
spin_lock_irqsave(&cp->view.lock, flags);
con3270_wait_write(cp);
}
spin_unlock_irqrestore(&cp->view.lock, flags);
}
static int __init
con3270_consetup(struct console *co, char *options)
{
return 0;
}
/*
* The console structure for the 3270 console
*/
static struct console con3270 = {
.name = "tty3270",
.write = con3270_write,
.device = con3270_device,
.unblank = con3270_unblank,
.setup = con3270_consetup,
.flags = CON_PRINTBUFFER,
};
/*
* 3270 console initialization code called from console_init().
* NOTE: This is called before kmalloc is available.
*/
static int __init
con3270_init(void)
{
struct ccw_device *cdev;
struct raw3270 *rp;
void *cbuf;
int i;
/* Check if 3270 is to be the console */
if (!CONSOLE_IS_3270)
return -ENODEV;
/* Set the console mode for VM */
if (MACHINE_IS_VM) {
cpcmd("TERM CONMODE 3270", 0, 0);
cpcmd("TERM AUTOCR OFF", 0, 0);
}
cdev = ccw_device_probe_console();
if (!cdev)
return -ENODEV;
rp = raw3270_setup_console(cdev);
if (IS_ERR(rp))
return PTR_ERR(rp);
condev = (struct con3270 *) alloc_bootmem_low(sizeof(struct con3270));
memset(condev, 0, sizeof(struct con3270));
condev->view.dev = rp;
condev->read = raw3270_request_alloc_bootmem(0);
condev->read->callback = con3270_read_callback;
condev->read->callback_data = condev;
condev->write =
raw3270_request_alloc_bootmem(CON3270_OUTPUT_BUFFER_SIZE);
condev->kreset = raw3270_request_alloc_bootmem(1);
INIT_LIST_HEAD(&condev->lines);
INIT_LIST_HEAD(&condev->update);
init_timer(&condev->timer);
tasklet_init(&condev->readlet,
(void (*)(unsigned long)) con3270_read_tasklet,
(unsigned long) condev->read);
raw3270_add_view(&condev->view, &con3270_fn, 0);
INIT_LIST_HEAD(&condev->freemem);
for (i = 0; i < CON3270_STRING_PAGES; i++) {
cbuf = (void *) alloc_bootmem_low_pages(PAGE_SIZE);
add_string_memory(&condev->freemem, cbuf, PAGE_SIZE);
}
condev->cline = alloc_string(&condev->freemem, condev->view.cols);
condev->cline->len = 0;
con3270_create_status(condev);
condev->input = alloc_string(&condev->freemem, 80);
register_console(&con3270);
return 0;
}
console_initcall(con3270_init);

View file

@ -0,0 +1,75 @@
/*
* drivers/s390/char/ctrlchar.c
* Unified handling of special chars.
*
* Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Fritz Elfert <felfert@millenux.com> <elfert@de.ibm.com>
*
*/
#include <linux/config.h>
#include <linux/stddef.h>
#include <asm/errno.h>
#include <linux/sysrq.h>
#include <linux/ctype.h>
#include "ctrlchar.h"
#ifdef CONFIG_MAGIC_SYSRQ
static int ctrlchar_sysrq_key;
static void
ctrlchar_handle_sysrq(void *tty)
{
handle_sysrq(ctrlchar_sysrq_key, NULL, (struct tty_struct *) tty);
}
static DECLARE_WORK(ctrlchar_work, ctrlchar_handle_sysrq, 0);
#endif
/**
* Check for special chars at start of input.
*
* @param buf Console input buffer.
* @param len Length of valid data in buffer.
* @param tty The tty struct for this console.
* @return CTRLCHAR_NONE, if nothing matched,
* CTRLCHAR_SYSRQ, if sysrq was encountered
* otherwise char to be inserted logically or'ed
* with CTRLCHAR_CTRL
*/
unsigned int
ctrlchar_handle(const unsigned char *buf, int len, struct tty_struct *tty)
{
if ((len < 2) || (len > 3))
return CTRLCHAR_NONE;
/* hat is 0xb1 in codepage 037 (US etc.) and thus */
/* converted to 0x5e in ascii ('^') */
if ((buf[0] != '^') && (buf[0] != '\252'))
return CTRLCHAR_NONE;
#ifdef CONFIG_MAGIC_SYSRQ
/* racy */
if (len == 3 && buf[1] == '-') {
ctrlchar_sysrq_key = buf[2];
ctrlchar_work.data = tty;
schedule_work(&ctrlchar_work);
return CTRLCHAR_SYSRQ;
}
#endif
if (len != 2)
return CTRLCHAR_NONE;
switch (tolower(buf[1])) {
case 'c':
return INTR_CHAR(tty) | CTRLCHAR_CTRL;
case 'd':
return EOF_CHAR(tty) | CTRLCHAR_CTRL;
case 'z':
return SUSP_CHAR(tty) | CTRLCHAR_CTRL;
}
return CTRLCHAR_NONE;
}

View file

@ -0,0 +1,20 @@
/*
* drivers/s390/char/ctrlchar.c
* Unified handling of special chars.
*
* Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Fritz Elfert <felfert@millenux.com> <elfert@de.ibm.com>
*
*/
#include <linux/tty.h>
extern unsigned int
ctrlchar_handle(const unsigned char *buf, int len, struct tty_struct *tty);
#define CTRLCHAR_NONE (1 << 8)
#define CTRLCHAR_CTRL (2 << 8)
#define CTRLCHAR_SYSRQ (3 << 8)
#define CTRLCHAR_MASK (~0xffu)

View file

@ -0,0 +1,156 @@
/* Do not edit this file! It was automatically generated by */
/* loadkeys --mktable defkeymap.map > defkeymap.c */
#include <linux/types.h>
#include <linux/keyboard.h>
#include <linux/kd.h>
u_short plain_map[NR_KEYS] = {
0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000, 0xf000,
0xf020, 0xf000, 0xf0e2, 0xf0e4, 0xf0e0, 0xf0e1, 0xf0e3, 0xf0e5,
0xf0e7, 0xf0f1, 0xf0a2, 0xf02e, 0xf03c, 0xf028, 0xf02b, 0xf07c,
0xf026, 0xf0e9, 0xf0e2, 0xf0eb, 0xf0e8, 0xf0ed, 0xf0ee, 0xf0ef,
0xf0ec, 0xf0df, 0xf021, 0xf024, 0xf02a, 0xf029, 0xf03b, 0xf0ac,
0xf02d, 0xf02f, 0xf0c2, 0xf0c4, 0xf0c0, 0xf0c1, 0xf0c3, 0xf0c5,
0xf0c7, 0xf0d1, 0xf0a6, 0xf02c, 0xf025, 0xf05f, 0xf03e, 0xf03f,
0xf0f8, 0xf0c9, 0xf0ca, 0xf0cb, 0xf0c8, 0xf0cd, 0xf0ce, 0xf0cf,
0xf0cc, 0xf060, 0xf03a, 0xf023, 0xf040, 0xf027, 0xf03d, 0xf022,
};
static u_short shift_map[NR_KEYS] = {
0xf0d8, 0xf061, 0xf062, 0xf063, 0xf064, 0xf065, 0xf066, 0xf067,
0xf068, 0xf069, 0xf0ab, 0xf0bb, 0xf0f0, 0xf0fd, 0xf0fe, 0xf0b1,
0xf0b0, 0xf06a, 0xf06b, 0xf06c, 0xf06d, 0xf06e, 0xf06f, 0xf070,
0xf071, 0xf072, 0xf000, 0xf000, 0xf0e6, 0xf0b8, 0xf0c6, 0xf0a4,
0xf0b5, 0xf07e, 0xf073, 0xf074, 0xf075, 0xf076, 0xf077, 0xf078,
0xf079, 0xf07a, 0xf0a1, 0xf0bf, 0xf0d0, 0xf0dd, 0xf0de, 0xf0ae,
0xf402, 0xf0a3, 0xf0a5, 0xf0b7, 0xf0a9, 0xf0a7, 0xf0b6, 0xf0bc,
0xf0bd, 0xf0be, 0xf05b, 0xf05d, 0xf000, 0xf0a8, 0xf0b4, 0xf0d7,
0xf07b, 0xf041, 0xf042, 0xf043, 0xf044, 0xf045, 0xf046, 0xf047,
0xf048, 0xf049, 0xf000, 0xf0f4, 0xf0f6, 0xf0f2, 0xf0f3, 0xf0f5,
0xf07d, 0xf04a, 0xf04b, 0xf04c, 0xf04d, 0xf04e, 0xf04f, 0xf050,
0xf051, 0xf052, 0xf0b9, 0xf0fb, 0xf0fc, 0xf0f9, 0xf0fa, 0xf0ff,
0xf05c, 0xf0f7, 0xf053, 0xf054, 0xf055, 0xf056, 0xf057, 0xf058,
0xf059, 0xf05a, 0xf0b2, 0xf0d4, 0xf0d6, 0xf0d2, 0xf0d3, 0xf0d5,
0xf030, 0xf031, 0xf032, 0xf033, 0xf034, 0xf035, 0xf036, 0xf037,
0xf038, 0xf039, 0xf0b3, 0xf0db, 0xf0dc, 0xf0d9, 0xf0da, 0xf000,
};
static u_short ctrl_map[NR_KEYS] = {
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf11f, 0xf120, 0xf121, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf01a, 0xf003, 0xf212, 0xf004, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf109, 0xf10a, 0xf206, 0xf00a, 0xf200, 0xf200,
};
static u_short shift_ctrl_map[NR_KEYS] = {
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf10c, 0xf10d, 0xf10e, 0xf10f, 0xf110, 0xf111, 0xf112,
0xf113, 0xf11e, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
0xf200, 0xf100, 0xf101, 0xf211, 0xf103, 0xf104, 0xf105, 0xf20b,
0xf20a, 0xf108, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200, 0xf200,
};
ushort *key_maps[MAX_NR_KEYMAPS] = {
plain_map, shift_map, 0, 0,
ctrl_map, shift_ctrl_map, 0
};
unsigned int keymap_count = 4;
/*
* Philosophy: most people do not define more strings, but they who do
* often want quite a lot of string space. So, we statically allocate
* the default and allocate dynamically in chunks of 512 bytes.
*/
char func_buf[] = {
'\033', '[', '[', 'A', 0,
'\033', '[', '[', 'B', 0,
'\033', '[', '[', 'C', 0,
'\033', '[', '[', 'D', 0,
'\033', '[', '[', 'E', 0,
'\033', '[', '1', '7', '~', 0,
'\033', '[', '1', '8', '~', 0,
'\033', '[', '1', '9', '~', 0,
'\033', '[', '2', '0', '~', 0,
'\033', '[', '2', '1', '~', 0,
'\033', '[', '2', '3', '~', 0,
'\033', '[', '2', '4', '~', 0,
'\033', '[', '2', '5', '~', 0,
'\033', '[', '2', '6', '~', 0,
'\033', '[', '2', '8', '~', 0,
'\033', '[', '2', '9', '~', 0,
'\033', '[', '3', '1', '~', 0,
'\033', '[', '3', '2', '~', 0,
'\033', '[', '3', '3', '~', 0,
'\033', '[', '3', '4', '~', 0,
};
char *funcbufptr = func_buf;
int funcbufsize = sizeof(func_buf);
int funcbufleft = 0; /* space left */
char *func_table[MAX_NR_FUNC] = {
func_buf + 0,
func_buf + 5,
func_buf + 10,
func_buf + 15,
func_buf + 20,
func_buf + 25,
func_buf + 31,
func_buf + 37,
func_buf + 43,
func_buf + 49,
func_buf + 55,
func_buf + 61,
func_buf + 67,
func_buf + 73,
func_buf + 79,
func_buf + 85,
func_buf + 91,
func_buf + 97,
func_buf + 103,
func_buf + 109,
0,
};
struct kbdiacr accent_table[MAX_DIACR] = {
{'^', 'c', '\003'}, {'^', 'd', '\004'},
{'^', 'z', '\032'}, {'^', '\012', '\000'},
};
unsigned int accent_table_size = 4;

View file

@ -0,0 +1,191 @@
# Default keymap for 3270 (ebcdic codepage 037).
keymaps 0-1,4-5
keycode 0 = nul Oslash
keycode 1 = nul a
keycode 2 = nul b
keycode 3 = nul c
keycode 4 = nul d
keycode 5 = nul e
keycode 6 = nul f
keycode 7 = nul g
keycode 8 = nul h
keycode 9 = nul i
keycode 10 = nul guillemotleft
keycode 11 = nul guillemotright
keycode 12 = nul eth
keycode 13 = nul yacute
keycode 14 = nul thorn
keycode 15 = nul plusminus
keycode 16 = nul degree
keycode 17 = nul j
keycode 18 = nul k
keycode 19 = nul l
keycode 20 = nul m
keycode 21 = nul n
keycode 22 = nul o
keycode 23 = nul p
keycode 24 = nul q
keycode 25 = nul r
keycode 26 = nul nul
keycode 27 = nul nul
keycode 28 = nul ae
keycode 29 = nul cedilla
keycode 30 = nul AE
keycode 31 = nul currency
keycode 32 = nul mu
keycode 33 = nul tilde
keycode 34 = nul s
keycode 35 = nul t
keycode 36 = nul u
keycode 37 = nul v
keycode 38 = nul w
keycode 39 = nul x
keycode 40 = nul y
keycode 41 = nul z
keycode 42 = nul exclamdown
keycode 43 = nul questiondown
keycode 44 = nul ETH
keycode 45 = nul Yacute
keycode 46 = nul THORN
keycode 47 = nul registered
keycode 48 = nul dead_circumflex
keycode 49 = nul sterling
keycode 50 = nul yen
keycode 51 = nul periodcentered
keycode 52 = nul copyright
keycode 53 = nul section
keycode 54 = nul paragraph
keycode 55 = nul onequarter
keycode 56 = nul onehalf
keycode 57 = nul threequarters
keycode 58 = nul bracketleft
keycode 59 = nul bracketright
keycode 60 = nul nul
keycode 61 = nul diaeresis
keycode 62 = nul acute
keycode 63 = nul multiply
keycode 64 = space braceleft
keycode 65 = nul A
keycode 66 = acircumflex B
keycode 67 = adiaeresis C
keycode 68 = agrave D
keycode 69 = aacute E
keycode 70 = atilde F
keycode 71 = aring G
keycode 72 = ccedilla H
keycode 73 = ntilde I
keycode 74 = cent nul
keycode 75 = period ocircumflex
keycode 76 = less odiaeresis
keycode 77 = parenleft ograve
keycode 78 = plus oacute
keycode 79 = bar otilde
keycode 80 = ampersand braceright
keycode 81 = eacute J
keycode 82 = acircumflex K
keycode 83 = ediaeresis L
keycode 84 = egrave M
keycode 85 = iacute N
keycode 86 = icircumflex O
keycode 87 = idiaeresis P
keycode 88 = igrave Q
keycode 89 = ssharp R
keycode 90 = exclam onesuperior
keycode 91 = dollar ucircumflex
keycode 92 = asterisk udiaeresis
keycode 93 = parenright ugrave
keycode 94 = semicolon uacute
keycode 95 = notsign ydiaeresis
keycode 96 = minus backslash
keycode 97 = slash division
keycode 98 = Acircumflex S
keycode 99 = Adiaeresis T
keycode 100 = Agrave U
keycode 101 = Aacute V
keycode 102 = Atilde W
keycode 103 = Aring X
keycode 104 = Ccedilla Y
keycode 105 = Ntilde Z
keycode 106 = brokenbar twosuperior
keycode 107 = comma Ocircumflex
keycode 108 = percent Odiaeresis
keycode 109 = underscore Ograve
keycode 110 = greater Oacute
keycode 111 = question Otilde
keycode 112 = oslash zero
keycode 113 = Eacute one
keycode 114 = Ecircumflex two
keycode 115 = Ediaeresis three
keycode 116 = Egrave four
keycode 117 = Iacute five
keycode 118 = Icircumflex six
keycode 119 = Idiaeresis seven
keycode 120 = Igrave eight
keycode 121 = grave nine
keycode 122 = colon threesuperior
keycode 123 = numbersign Ucircumflex
keycode 124 = at Udiaeresis
keycode 125 = apostrophe Ugrave
keycode 126 = equal Uacute
keycode 127 = quotedbl nul
# AID keys
control keycode 74 = F22
control keycode 75 = F23
control keycode 76 = F24
control keycode 107 = Control_z # PA3
control keycode 108 = Control_c # PA1
control keycode 109 = KeyboardSignal # Clear
control keycode 110 = Control_d # PA2
control keycode 122 = F10
control keycode 123 = F11 # F11
control keycode 124 = Last_Console # F12
control keycode 125 = Linefeed
shift control keycode 65 = F13
shift control keycode 66 = F14
shift control keycode 67 = F15
shift control keycode 68 = F16
shift control keycode 69 = F17
shift control keycode 70 = F18
shift control keycode 71 = F19
shift control keycode 72 = F20
shift control keycode 73 = F21
shift control keycode 113 = F1
shift control keycode 114 = F2
shift control keycode 115 = Incr_Console
shift control keycode 116 = F4
shift control keycode 117 = F5
shift control keycode 118 = F6
shift control keycode 119 = Scroll_Backward
shift control keycode 120 = Scroll_Forward
shift control keycode 121 = F9
string F1 = "\033[[A"
string F2 = "\033[[B"
string F3 = "\033[[C"
string F4 = "\033[[D"
string F5 = "\033[[E"
string F6 = "\033[17~"
string F7 = "\033[18~"
string F8 = "\033[19~"
string F9 = "\033[20~"
string F10 = "\033[21~"
string F11 = "\033[23~"
string F12 = "\033[24~"
string F13 = "\033[25~"
string F14 = "\033[26~"
string F15 = "\033[28~"
string F16 = "\033[29~"
string F17 = "\033[31~"
string F18 = "\033[32~"
string F19 = "\033[33~"
string F20 = "\033[34~"
# string F21 ??
# string F22 ??
# string F23 ??
# string F24 ??
compose '^' 'c' to Control_c
compose '^' 'd' to Control_d
compose '^' 'z' to Control_z
compose '^' '\012' to nul

373
drivers/s390/char/fs3270.c Normal file
View file

@ -0,0 +1,373 @@
/*
* drivers/s390/char/fs3270.c
* IBM/3270 Driver - fullscreen driver.
*
* Author(s):
* Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
* Rewritten for 2.5/2.6 by Martin Schwidefsky <schwidefsky@de.ibm.com>
* -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
*/
#include <linux/config.h>
#include <linux/bootmem.h>
#include <linux/console.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <linux/list.h>
#include <linux/types.h>
#include <asm/ccwdev.h>
#include <asm/cio.h>
#include <asm/cpcmd.h>
#include <asm/ebcdic.h>
#include <asm/idals.h>
#include "raw3270.h"
#include "ctrlchar.h"
struct raw3270_fn fs3270_fn;
struct fs3270 {
struct raw3270_view view;
pid_t fs_pid; /* Pid of controlling program. */
int read_command; /* ccw command to use for reads. */
int write_command; /* ccw command to use for writes. */
int attention; /* Got attention. */
struct raw3270_request *clear; /* single clear request. */
wait_queue_head_t attn_wait; /* Attention wait queue. */
};
static void
fs3270_wake_up(struct raw3270_request *rq, void *data)
{
wake_up((wait_queue_head_t *) data);
}
static int
fs3270_do_io(struct raw3270_view *view, struct raw3270_request *rq)
{
wait_queue_head_t wq;
int rc;
init_waitqueue_head(&wq);
rq->callback = fs3270_wake_up;
rq->callback_data = &wq;
rc = raw3270_start(view, rq);
if (rc)
return rc;
/* Started sucessfully. Now wait for completion. */
wait_event(wq, raw3270_request_final(rq));
return rq->rc;
}
static void
fs3270_reset_callback(struct raw3270_request *rq, void *data)
{
raw3270_request_reset(rq);
}
/*
* Switch to the fullscreen view.
*/
static int
fs3270_activate(struct raw3270_view *view)
{
struct fs3270 *fp;
fp = (struct fs3270 *) view;
raw3270_request_set_cmd(fp->clear, TC_EWRITEA);
fp->clear->callback = fs3270_reset_callback;
return raw3270_start(view, fp->clear);
}
/*
* Shutdown fullscreen view.
*/
static void
fs3270_deactivate(struct raw3270_view *view)
{
// FIXME: is this a good idea? The user program using fullscreen 3270
// will die just because a console message appeared. On the other
// hand the fullscreen device is unoperational now.
struct fs3270 *fp;
fp = (struct fs3270 *) view;
if (fp->fs_pid != 0)
kill_proc(fp->fs_pid, SIGHUP, 1);
fp->fs_pid = 0;
}
static int
fs3270_irq(struct fs3270 *fp, struct raw3270_request *rq, struct irb *irb)
{
/* Handle ATTN. Set indication and wake waiters for attention. */
if (irb->scsw.dstat & DEV_STAT_ATTENTION) {
fp->attention = 1;
wake_up(&fp->attn_wait);
}
if (rq) {
if (irb->scsw.dstat & DEV_STAT_UNIT_CHECK)
rq->rc = -EIO;
else
/* Normal end. Copy residual count. */
rq->rescnt = irb->scsw.count;
}
return RAW3270_IO_DONE;
}
/*
* Process reads from fullscreen 3270.
*/
static ssize_t
fs3270_read(struct file *filp, char *data, size_t count, loff_t *off)
{
struct fs3270 *fp;
struct raw3270_request *rq;
struct idal_buffer *ib;
int rc;
if (count == 0 || count > 65535)
return -EINVAL;
fp = filp->private_data;
if (!fp)
return -ENODEV;
ib = idal_buffer_alloc(count, 0);
if (!ib)
return -ENOMEM;
rq = raw3270_request_alloc(0);
if (!IS_ERR(rq)) {
if (fp->read_command == 0 && fp->write_command != 0)
fp->read_command = 6;
raw3270_request_set_cmd(rq, fp->read_command ? : 2);
raw3270_request_set_idal(rq, ib);
wait_event(fp->attn_wait, fp->attention);
rc = fs3270_do_io(&fp->view, rq);
if (rc == 0 && idal_buffer_to_user(ib, data, count))
rc = -EFAULT;
raw3270_request_free(rq);
} else
rc = PTR_ERR(rq);
idal_buffer_free(ib);
return rc;
}
/*
* Process writes to fullscreen 3270.
*/
static ssize_t
fs3270_write(struct file *filp, const char *data, size_t count, loff_t *off)
{
struct fs3270 *fp;
struct raw3270_request *rq;
struct idal_buffer *ib;
int write_command;
int rc;
fp = filp->private_data;
if (!fp)
return -ENODEV;
ib = idal_buffer_alloc(count, 0);
if (!ib)
return -ENOMEM;
rq = raw3270_request_alloc(0);
if (!IS_ERR(rq)) {
if (idal_buffer_from_user(ib, data, count) == 0) {
write_command = fp->write_command ? : 1;
if (write_command == 5)
write_command = 13;
raw3270_request_set_cmd(rq, write_command);
raw3270_request_set_idal(rq, ib);
rc = fs3270_do_io(&fp->view, rq);
} else
rc = -EFAULT;
raw3270_request_free(rq);
} else
rc = PTR_ERR(rq);
idal_buffer_free(ib);
return rc;
}
/*
* process ioctl commands for the tube driver
*/
static int
fs3270_ioctl(struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
struct fs3270 *fp;
struct raw3270_iocb iocb;
int rc;
fp = filp->private_data;
if (!fp)
return -ENODEV;
rc = 0;
switch (cmd) {
case TUBICMD:
fp->read_command = arg;
break;
case TUBOCMD:
fp->write_command = arg;
break;
case TUBGETI:
rc = put_user(fp->read_command, (char *) arg);
break;
case TUBGETO:
rc = put_user(fp->write_command,(char *) arg);
break;
case TUBGETMOD:
iocb.model = fp->view.model;
iocb.line_cnt = fp->view.rows;
iocb.col_cnt = fp->view.cols;
iocb.pf_cnt = 24;
iocb.re_cnt = 20;
iocb.map = 0;
if (copy_to_user((char *) arg, &iocb,
sizeof(struct raw3270_iocb)))
rc = -EFAULT;
break;
}
return rc;
}
/*
* Allocate tty3270 structure.
*/
static struct fs3270 *
fs3270_alloc_view(void)
{
struct fs3270 *fp;
fp = (struct fs3270 *) kmalloc(sizeof(struct fs3270),GFP_KERNEL);
if (!fp)
return ERR_PTR(-ENOMEM);
memset(fp, 0, sizeof(struct fs3270));
fp->clear = raw3270_request_alloc(0);
if (!IS_ERR(fp->clear)) {
kfree(fp);
return ERR_PTR(-ENOMEM);
}
return fp;
}
/*
* Free tty3270 structure.
*/
static void
fs3270_free_view(struct raw3270_view *view)
{
raw3270_request_free(((struct fs3270 *) view)->clear);
kfree(view);
}
/*
* Unlink fs3270 data structure from filp.
*/
static void
fs3270_release(struct raw3270_view *view)
{
}
/* View to a 3270 device. Can be console, tty or fullscreen. */
struct raw3270_fn fs3270_fn = {
.activate = fs3270_activate,
.deactivate = fs3270_deactivate,
.intv = (void *) fs3270_irq,
.release = fs3270_release,
.free = fs3270_free_view
};
/*
* This routine is called whenever a 3270 fullscreen device is opened.
*/
static int
fs3270_open(struct inode *inode, struct file *filp)
{
struct fs3270 *fp;
int minor, rc;
if (imajor(filp->f_dentry->d_inode) != IBM_FS3270_MAJOR)
return -ENODEV;
minor = iminor(filp->f_dentry->d_inode);
/* Check if some other program is already using fullscreen mode. */
fp = (struct fs3270 *) raw3270_find_view(&fs3270_fn, minor);
if (!IS_ERR(fp)) {
raw3270_put_view(&fp->view);
return -EBUSY;
}
/* Allocate fullscreen view structure. */
fp = fs3270_alloc_view();
if (IS_ERR(fp))
return PTR_ERR(fp);
init_waitqueue_head(&fp->attn_wait);
fp->fs_pid = current->pid;
rc = raw3270_add_view(&fp->view, &fs3270_fn, minor);
if (rc) {
fs3270_free_view(&fp->view);
return rc;
}
rc = raw3270_activate_view(&fp->view);
if (rc) {
raw3270_del_view(&fp->view);
return rc;
}
filp->private_data = fp;
return 0;
}
/*
* This routine is called when the 3270 tty is closed. We wait
* for the remaining request to be completed. Then we clean up.
*/
static int
fs3270_close(struct inode *inode, struct file *filp)
{
struct fs3270 *fp;
fp = filp->private_data;
filp->private_data = 0;
if (fp)
raw3270_del_view(&fp->view);
return 0;
}
static struct file_operations fs3270_fops = {
.owner = THIS_MODULE, /* owner */
.read = fs3270_read, /* read */
.write = fs3270_write, /* write */
.ioctl = fs3270_ioctl, /* ioctl */
.open = fs3270_open, /* open */
.release = fs3270_close, /* release */
};
/*
* 3270 fullscreen driver initialization.
*/
static int __init
fs3270_init(void)
{
int rc;
rc = register_chrdev(IBM_FS3270_MAJOR, "fs3270", &fs3270_fops);
if (rc) {
printk(KERN_ERR "fs3270 can't get major number %d: errno %d\n",
IBM_FS3270_MAJOR, rc);
return rc;
}
return 0;
}
static void __exit
fs3270_exit(void)
{
unregister_chrdev(IBM_FS3270_MAJOR, "fs3270");
}
MODULE_LICENSE("GPL");
MODULE_ALIAS_CHARDEV_MAJOR(IBM_FS3270_MAJOR);
module_init(fs3270_init);
module_exit(fs3270_exit);

View file

@ -0,0 +1,519 @@
/*
* drivers/s390/char/keyboard.c
* ebcdic keycode functions for s390 console drivers
*
* S390 version
* Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/sched.h>
#include <linux/sysrq.h>
#include <linux/kbd_kern.h>
#include <linux/kbd_diacr.h>
#include <asm/uaccess.h>
#include "keyboard.h"
/*
* Handler Tables.
*/
#define K_HANDLERS\
k_self, k_fn, k_spec, k_ignore,\
k_dead, k_ignore, k_ignore, k_ignore,\
k_ignore, k_ignore, k_ignore, k_ignore,\
k_ignore, k_ignore, k_ignore, k_ignore
typedef void (k_handler_fn)(struct kbd_data *, unsigned char);
static k_handler_fn K_HANDLERS;
static k_handler_fn *k_handler[16] = { K_HANDLERS };
/* maximum values each key_handler can handle */
static const int kbd_max_vals[] = {
255, ARRAY_SIZE(func_table) - 1, NR_FN_HANDLER - 1, 0,
NR_DEAD - 1, 0, 0, 0, 0, 0, 0, 0, 0, 0
};
static const int KBD_NR_TYPES = ARRAY_SIZE(kbd_max_vals);
static unsigned char ret_diacr[NR_DEAD] = {
'`', '\'', '^', '~', '"', ','
};
/*
* Alloc/free of kbd_data structures.
*/
struct kbd_data *
kbd_alloc(void) {
struct kbd_data *kbd;
int i, len;
kbd = kmalloc(sizeof(struct kbd_data), GFP_KERNEL);
if (!kbd)
goto out;
memset(kbd, 0, sizeof(struct kbd_data));
kbd->key_maps = kmalloc(sizeof(key_maps), GFP_KERNEL);
if (!key_maps)
goto out_kbd;
memset(kbd->key_maps, 0, sizeof(key_maps));
for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
if (key_maps[i]) {
kbd->key_maps[i] =
kmalloc(sizeof(u_short)*NR_KEYS, GFP_KERNEL);
if (!kbd->key_maps[i])
goto out_maps;
memcpy(kbd->key_maps[i], key_maps[i],
sizeof(u_short)*NR_KEYS);
}
}
kbd->func_table = kmalloc(sizeof(func_table), GFP_KERNEL);
if (!kbd->func_table)
goto out_maps;
memset(kbd->func_table, 0, sizeof(func_table));
for (i = 0; i < ARRAY_SIZE(func_table); i++) {
if (func_table[i]) {
len = strlen(func_table[i]) + 1;
kbd->func_table[i] = kmalloc(len, GFP_KERNEL);
if (!kbd->func_table[i])
goto out_func;
memcpy(kbd->func_table[i], func_table[i], len);
}
}
kbd->fn_handler =
kmalloc(sizeof(fn_handler_fn *) * NR_FN_HANDLER, GFP_KERNEL);
if (!kbd->fn_handler)
goto out_func;
memset(kbd->fn_handler, 0, sizeof(fn_handler_fn *) * NR_FN_HANDLER);
kbd->accent_table =
kmalloc(sizeof(struct kbdiacr)*MAX_DIACR, GFP_KERNEL);
if (!kbd->accent_table)
goto out_fn_handler;
memcpy(kbd->accent_table, accent_table,
sizeof(struct kbdiacr)*MAX_DIACR);
kbd->accent_table_size = accent_table_size;
return kbd;
out_fn_handler:
kfree(kbd->fn_handler);
out_func:
for (i = 0; i < ARRAY_SIZE(func_table); i++)
if (kbd->func_table[i])
kfree(kbd->func_table[i]);
kfree(kbd->func_table);
out_maps:
for (i = 0; i < ARRAY_SIZE(key_maps); i++)
if (kbd->key_maps[i])
kfree(kbd->key_maps[i]);
kfree(kbd->key_maps);
out_kbd:
kfree(kbd);
out:
return 0;
}
void
kbd_free(struct kbd_data *kbd)
{
int i;
kfree(kbd->accent_table);
kfree(kbd->fn_handler);
for (i = 0; i < ARRAY_SIZE(func_table); i++)
if (kbd->func_table[i])
kfree(kbd->func_table[i]);
kfree(kbd->func_table);
for (i = 0; i < ARRAY_SIZE(key_maps); i++)
if (kbd->key_maps[i])
kfree(kbd->key_maps[i]);
kfree(kbd->key_maps);
kfree(kbd);
}
/*
* Generate ascii -> ebcdic translation table from kbd_data.
*/
void
kbd_ascebc(struct kbd_data *kbd, unsigned char *ascebc)
{
unsigned short *keymap, keysym;
int i, j, k;
memset(ascebc, 0x40, 256);
for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
keymap = kbd->key_maps[i];
if (!keymap)
continue;
for (j = 0; j < NR_KEYS; j++) {
k = ((i & 1) << 7) + j;
keysym = keymap[j];
if (KTYP(keysym) == (KT_LATIN | 0xf0) ||
KTYP(keysym) == (KT_LETTER | 0xf0))
ascebc[KVAL(keysym)] = k;
else if (KTYP(keysym) == (KT_DEAD | 0xf0))
ascebc[ret_diacr[KVAL(keysym)]] = k;
}
}
}
/*
* Generate ebcdic -> ascii translation table from kbd_data.
*/
void
kbd_ebcasc(struct kbd_data *kbd, unsigned char *ebcasc)
{
unsigned short *keymap, keysym;
int i, j, k;
memset(ebcasc, ' ', 256);
for (i = 0; i < ARRAY_SIZE(key_maps); i++) {
keymap = kbd->key_maps[i];
if (!keymap)
continue;
for (j = 0; j < NR_KEYS; j++) {
keysym = keymap[j];
k = ((i & 1) << 7) + j;
if (KTYP(keysym) == (KT_LATIN | 0xf0) ||
KTYP(keysym) == (KT_LETTER | 0xf0))
ebcasc[k] = KVAL(keysym);
else if (KTYP(keysym) == (KT_DEAD | 0xf0))
ebcasc[k] = ret_diacr[KVAL(keysym)];
}
}
}
/*
* We have a combining character DIACR here, followed by the character CH.
* If the combination occurs in the table, return the corresponding value.
* Otherwise, if CH is a space or equals DIACR, return DIACR.
* Otherwise, conclude that DIACR was not combining after all,
* queue it and return CH.
*/
static unsigned char
handle_diacr(struct kbd_data *kbd, unsigned char ch)
{
int i, d;
d = kbd->diacr;
kbd->diacr = 0;
for (i = 0; i < kbd->accent_table_size; i++) {
if (kbd->accent_table[i].diacr == d &&
kbd->accent_table[i].base == ch)
return kbd->accent_table[i].result;
}
if (ch == ' ' || ch == d)
return d;
kbd_put_queue(kbd->tty, d);
return ch;
}
/*
* Handle dead key.
*/
static void
k_dead(struct kbd_data *kbd, unsigned char value)
{
value = ret_diacr[value];
kbd->diacr = (kbd->diacr ? handle_diacr(kbd, value) : value);
}
/*
* Normal character handler.
*/
static void
k_self(struct kbd_data *kbd, unsigned char value)
{
if (kbd->diacr)
value = handle_diacr(kbd, value);
kbd_put_queue(kbd->tty, value);
}
/*
* Special key handlers
*/
static void
k_ignore(struct kbd_data *kbd, unsigned char value)
{
}
/*
* Function key handler.
*/
static void
k_fn(struct kbd_data *kbd, unsigned char value)
{
if (kbd->func_table[value])
kbd_puts_queue(kbd->tty, kbd->func_table[value]);
}
static void
k_spec(struct kbd_data *kbd, unsigned char value)
{
if (value >= NR_FN_HANDLER)
return;
if (kbd->fn_handler[value])
kbd->fn_handler[value](kbd);
}
/*
* Put utf8 character to tty flip buffer.
* UTF-8 is defined for words of up to 31 bits,
* but we need only 16 bits here
*/
static void
to_utf8(struct tty_struct *tty, ushort c)
{
if (c < 0x80)
/* 0******* */
kbd_put_queue(tty, c);
else if (c < 0x800) {
/* 110***** 10****** */
kbd_put_queue(tty, 0xc0 | (c >> 6));
kbd_put_queue(tty, 0x80 | (c & 0x3f));
} else {
/* 1110**** 10****** 10****** */
kbd_put_queue(tty, 0xe0 | (c >> 12));
kbd_put_queue(tty, 0x80 | ((c >> 6) & 0x3f));
kbd_put_queue(tty, 0x80 | (c & 0x3f));
}
}
/*
* Process keycode.
*/
void
kbd_keycode(struct kbd_data *kbd, unsigned int keycode)
{
unsigned short keysym;
unsigned char type, value;
if (!kbd || !kbd->tty)
return;
if (keycode >= 384)
keysym = kbd->key_maps[5][keycode - 384];
else if (keycode >= 256)
keysym = kbd->key_maps[4][keycode - 256];
else if (keycode >= 128)
keysym = kbd->key_maps[1][keycode - 128];
else
keysym = kbd->key_maps[0][keycode];
type = KTYP(keysym);
if (type >= 0xf0) {
type -= 0xf0;
if (type == KT_LETTER)
type = KT_LATIN;
value = KVAL(keysym);
#ifdef CONFIG_MAGIC_SYSRQ /* Handle the SysRq Hack */
if (kbd->sysrq) {
if (kbd->sysrq == K(KT_LATIN, '-')) {
kbd->sysrq = 0;
handle_sysrq(value, 0, kbd->tty);
return;
}
if (value == '-') {
kbd->sysrq = K(KT_LATIN, '-');
return;
}
/* Incomplete sysrq sequence. */
(*k_handler[KTYP(kbd->sysrq)])(kbd, KVAL(kbd->sysrq));
kbd->sysrq = 0;
} else if ((type == KT_LATIN && value == '^') ||
(type == KT_DEAD && ret_diacr[value] == '^')) {
kbd->sysrq = K(type, value);
return;
}
#endif
(*k_handler[type])(kbd, value);
} else
to_utf8(kbd->tty, keysym);
}
/*
* Ioctl stuff.
*/
static int
do_kdsk_ioctl(struct kbd_data *kbd, struct kbentry __user *user_kbe,
int cmd, int perm)
{
struct kbentry tmp;
ushort *key_map, val, ov;
if (copy_from_user(&tmp, user_kbe, sizeof(struct kbentry)))
return -EFAULT;
#if NR_KEYS < 256
if (tmp.kb_index >= NR_KEYS)
return -EINVAL;
#endif
#if MAX_NR_KEYMAPS < 256
if (tmp.kb_table >= MAX_NR_KEYMAPS)
return -EINVAL;
#endif
switch (cmd) {
case KDGKBENT:
key_map = kbd->key_maps[tmp.kb_table];
if (key_map) {
val = U(key_map[tmp.kb_index]);
if (KTYP(val) >= KBD_NR_TYPES)
val = K_HOLE;
} else
val = (tmp.kb_index ? K_HOLE : K_NOSUCHMAP);
return put_user(val, &user_kbe->kb_value);
case KDSKBENT:
if (!perm)
return -EPERM;
if (!tmp.kb_index && tmp.kb_value == K_NOSUCHMAP) {
/* disallocate map */
key_map = kbd->key_maps[tmp.kb_table];
if (key_map) {
kbd->key_maps[tmp.kb_table] = 0;
kfree(key_map);
}
break;
}
if (KTYP(tmp.kb_value) >= KBD_NR_TYPES)
return -EINVAL;
if (KVAL(tmp.kb_value) > kbd_max_vals[KTYP(tmp.kb_value)])
return -EINVAL;
if (!(key_map = kbd->key_maps[tmp.kb_table])) {
int j;
key_map = (ushort *) kmalloc(sizeof(plain_map),
GFP_KERNEL);
if (!key_map)
return -ENOMEM;
kbd->key_maps[tmp.kb_table] = key_map;
for (j = 0; j < NR_KEYS; j++)
key_map[j] = U(K_HOLE);
}
ov = U(key_map[tmp.kb_index]);
if (tmp.kb_value == ov)
break; /* nothing to do */
/*
* Attention Key.
*/
if (((ov == K_SAK) || (tmp.kb_value == K_SAK)) &&
!capable(CAP_SYS_ADMIN))
return -EPERM;
key_map[tmp.kb_index] = U(tmp.kb_value);
break;
}
return 0;
}
static int
do_kdgkb_ioctl(struct kbd_data *kbd, struct kbsentry __user *u_kbs,
int cmd, int perm)
{
unsigned char kb_func;
char *p;
int len;
/* Get u_kbs->kb_func. */
if (get_user(kb_func, &u_kbs->kb_func))
return -EFAULT;
#if MAX_NR_FUNC < 256
if (kb_func >= MAX_NR_FUNC)
return -EINVAL;
#endif
switch (cmd) {
case KDGKBSENT:
p = kbd->func_table[kb_func];
if (p) {
len = strlen(p);
if (len >= sizeof(u_kbs->kb_string))
len = sizeof(u_kbs->kb_string) - 1;
if (copy_to_user(u_kbs->kb_string, p, len))
return -EFAULT;
} else
len = 0;
if (put_user('\0', u_kbs->kb_string + len))
return -EFAULT;
break;
case KDSKBSENT:
if (!perm)
return -EPERM;
len = strnlen_user(u_kbs->kb_string,
sizeof(u_kbs->kb_string) - 1);
p = kmalloc(len, GFP_KERNEL);
if (!p)
return -ENOMEM;
if (copy_from_user(p, u_kbs->kb_string, len)) {
kfree(p);
return -EFAULT;
}
p[len] = 0;
if (kbd->func_table[kb_func])
kfree(kbd->func_table[kb_func]);
kbd->func_table[kb_func] = p;
break;
}
return 0;
}
int
kbd_ioctl(struct kbd_data *kbd, struct file *file,
unsigned int cmd, unsigned long arg)
{
struct kbdiacrs __user *a;
void __user *argp;
int ct, perm;
argp = (void __user *)arg;
/*
* To have permissions to do most of the vt ioctls, we either have
* to be the owner of the tty, or have CAP_SYS_TTY_CONFIG.
*/
perm = current->signal->tty == kbd->tty || capable(CAP_SYS_TTY_CONFIG);
switch (cmd) {
case KDGKBTYPE:
return put_user(KB_101, (char __user *)argp);
case KDGKBENT:
case KDSKBENT:
return do_kdsk_ioctl(kbd, argp, cmd, perm);
case KDGKBSENT:
case KDSKBSENT:
return do_kdgkb_ioctl(kbd, argp, cmd, perm);
case KDGKBDIACR:
a = argp;
if (put_user(kbd->accent_table_size, &a->kb_cnt))
return -EFAULT;
ct = kbd->accent_table_size;
if (copy_to_user(a->kbdiacr, kbd->accent_table,
ct * sizeof(struct kbdiacr)))
return -EFAULT;
return 0;
case KDSKBDIACR:
a = argp;
if (!perm)
return -EPERM;
if (get_user(ct, &a->kb_cnt))
return -EFAULT;
if (ct >= MAX_DIACR)
return -EINVAL;
kbd->accent_table_size = ct;
if (copy_from_user(kbd->accent_table, a->kbdiacr,
ct * sizeof(struct kbdiacr)))
return -EFAULT;
return 0;
default:
return -ENOIOCTLCMD;
}
}
EXPORT_SYMBOL(kbd_ioctl);
EXPORT_SYMBOL(kbd_ascebc);
EXPORT_SYMBOL(kbd_free);
EXPORT_SYMBOL(kbd_alloc);
EXPORT_SYMBOL(kbd_keycode);

View file

@ -0,0 +1,57 @@
/*
* drivers/s390/char/keyboard.h
* ebcdic keycode functions for s390 console drivers
*
* Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com),
*/
#include <linux/tty.h>
#include <linux/tty_flip.h>
#include <linux/keyboard.h>
#define NR_FN_HANDLER 20
struct kbd_data;
typedef void (fn_handler_fn)(struct kbd_data *);
/*
* FIXME: explain key_maps tricks.
*/
struct kbd_data {
struct tty_struct *tty;
unsigned short **key_maps;
char **func_table;
fn_handler_fn **fn_handler;
struct kbdiacr *accent_table;
unsigned int accent_table_size;
unsigned char diacr;
unsigned short sysrq;
};
struct kbd_data *kbd_alloc(void);
void kbd_free(struct kbd_data *);
void kbd_ascebc(struct kbd_data *, unsigned char *);
void kbd_keycode(struct kbd_data *, unsigned int);
int kbd_ioctl(struct kbd_data *, struct file *, unsigned int, unsigned long);
/*
* Helper Functions.
*/
extern inline void
kbd_put_queue(struct tty_struct *tty, int ch)
{
tty_insert_flip_char(tty, ch, 0);
tty_schedule_flip(tty);
}
extern inline void
kbd_puts_queue(struct tty_struct *tty, char *cp)
{
while (*cp)
tty_insert_flip_char(tty, *cp++, 0);
tty_schedule_flip(tty);
}

View file

@ -0,0 +1,662 @@
/*
* drivers/s390/char/monreader.c
*
* Character device driver for reading z/VM *MONITOR service records.
*
* Copyright (C) 2004 IBM Corporation, IBM Deutschland Entwicklung GmbH.
*
* Author: Gerald Schaefer <geraldsc@de.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/spinlock.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include <asm/ebcdic.h>
#include <asm/extmem.h>
#include <linux/poll.h>
#include "../net/iucv.h"
//#define MON_DEBUG /* Debug messages on/off */
#define MON_NAME "monreader"
#define P_INFO(x...) printk(KERN_INFO MON_NAME " info: " x)
#define P_ERROR(x...) printk(KERN_ERR MON_NAME " error: " x)
#define P_WARNING(x...) printk(KERN_WARNING MON_NAME " warning: " x)
#ifdef MON_DEBUG
#define P_DEBUG(x...) printk(KERN_DEBUG MON_NAME " debug: " x)
#else
#define P_DEBUG(x...) do {} while (0)
#endif
#define MON_COLLECT_SAMPLE 0x80
#define MON_COLLECT_EVENT 0x40
#define MON_SERVICE "*MONITOR"
#define MON_IN_USE 0x01
#define MON_MSGLIM 255
static char mon_dcss_name[9] = "MONDCSS\0";
struct mon_msg {
u32 pos;
u32 mca_offset;
iucv_MessagePending local_eib;
char msglim_reached;
char replied_msglim;
};
struct mon_private {
u16 pathid;
iucv_handle_t iucv_handle;
struct mon_msg *msg_array[MON_MSGLIM];
unsigned int write_index;
unsigned int read_index;
atomic_t msglim_count;
atomic_t read_ready;
atomic_t iucv_connected;
atomic_t iucv_severed;
};
static unsigned long mon_in_use = 0;
static unsigned long mon_dcss_start;
static unsigned long mon_dcss_end;
static DECLARE_WAIT_QUEUE_HEAD(mon_read_wait_queue);
static DECLARE_WAIT_QUEUE_HEAD(mon_conn_wait_queue);
static u8 iucv_host[8] = {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00};
static u8 user_data_connect[16] = {
/* Version code, must be 0x01 for shared mode */
0x01,
/* what to collect */
MON_COLLECT_SAMPLE | MON_COLLECT_EVENT,
/* DCSS name in EBCDIC, 8 bytes padded with blanks */
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
static u8 user_data_sever[16] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
};
/******************************************************************************
* helper functions *
*****************************************************************************/
/*
* Create the 8 bytes EBCDIC DCSS segment name from
* an ASCII name, incl. padding
*/
static inline void
dcss_mkname(char *ascii_name, char *ebcdic_name)
{
int i;
for (i = 0; i < 8; i++) {
if (ascii_name[i] == '\0')
break;
ebcdic_name[i] = toupper(ascii_name[i]);
};
for (; i < 8; i++)
ebcdic_name[i] = ' ';
ASCEBC(ebcdic_name, 8);
}
/*
* print appropriate error message for segment_load()/segment_type()
* return code
*/
static void
mon_segment_warn(int rc, char* seg_name)
{
switch (rc) {
case -ENOENT:
P_WARNING("cannot load/query segment %s, does not exist\n",
seg_name);
break;
case -ENOSYS:
P_WARNING("cannot load/query segment %s, not running on VM\n",
seg_name);
break;
case -EIO:
P_WARNING("cannot load/query segment %s, hardware error\n",
seg_name);
break;
case -ENOTSUPP:
P_WARNING("cannot load/query segment %s, is a multi-part "
"segment\n", seg_name);
break;
case -ENOSPC:
P_WARNING("cannot load/query segment %s, overlaps with "
"storage\n", seg_name);
break;
case -EBUSY:
P_WARNING("cannot load/query segment %s, overlaps with "
"already loaded dcss\n", seg_name);
break;
case -EPERM:
P_WARNING("cannot load/query segment %s, already loaded in "
"incompatible mode\n", seg_name);
break;
case -ENOMEM:
P_WARNING("cannot load/query segment %s, out of memory\n",
seg_name);
break;
case -ERANGE:
P_WARNING("cannot load/query segment %s, exceeds kernel "
"mapping range\n", seg_name);
break;
default:
P_WARNING("cannot load/query segment %s, return value %i\n",
seg_name, rc);
break;
}
}
static inline unsigned long
mon_mca_start(struct mon_msg *monmsg)
{
return monmsg->local_eib.ln1msg1.iprmmsg1_u32;
}
static inline unsigned long
mon_mca_end(struct mon_msg *monmsg)
{
return monmsg->local_eib.ln1msg2.ipbfln1f;
}
static inline u8
mon_mca_type(struct mon_msg *monmsg, u8 index)
{
return *((u8 *) mon_mca_start(monmsg) + monmsg->mca_offset + index);
}
static inline u32
mon_mca_size(struct mon_msg *monmsg)
{
return mon_mca_end(monmsg) - mon_mca_start(monmsg) + 1;
}
static inline u32
mon_rec_start(struct mon_msg *monmsg)
{
return *((u32 *) (mon_mca_start(monmsg) + monmsg->mca_offset + 4));
}
static inline u32
mon_rec_end(struct mon_msg *monmsg)
{
return *((u32 *) (mon_mca_start(monmsg) + monmsg->mca_offset + 8));
}
static inline int
mon_check_mca(struct mon_msg *monmsg)
{
if ((mon_rec_end(monmsg) <= mon_rec_start(monmsg)) ||
(mon_rec_start(monmsg) < mon_dcss_start) ||
(mon_rec_end(monmsg) > mon_dcss_end) ||
(mon_mca_type(monmsg, 0) == 0) ||
(mon_mca_size(monmsg) % 12 != 0) ||
(mon_mca_end(monmsg) <= mon_mca_start(monmsg)) ||
(mon_mca_end(monmsg) > mon_dcss_end) ||
(mon_mca_start(monmsg) < mon_dcss_start) ||
((mon_mca_type(monmsg, 1) == 0) && (mon_mca_type(monmsg, 2) == 0)))
{
P_DEBUG("READ, IGNORED INVALID MCA\n\n");
return -EINVAL;
}
return 0;
}
static inline int
mon_send_reply(struct mon_msg *monmsg, struct mon_private *monpriv)
{
u8 prmmsg[8];
int rc;
P_DEBUG("read, REPLY: pathid = 0x%04X, msgid = 0x%08X, trgcls = "
"0x%08X\n\n",
monmsg->local_eib.ippathid, monmsg->local_eib.ipmsgid,
monmsg->local_eib.iptrgcls);
rc = iucv_reply_prmmsg(monmsg->local_eib.ippathid,
monmsg->local_eib.ipmsgid,
monmsg->local_eib.iptrgcls,
0, prmmsg);
atomic_dec(&monpriv->msglim_count);
if (likely(!monmsg->msglim_reached)) {
monmsg->pos = 0;
monmsg->mca_offset = 0;
monpriv->read_index = (monpriv->read_index + 1) %
MON_MSGLIM;
atomic_dec(&monpriv->read_ready);
} else
monmsg->replied_msglim = 1;
if (rc) {
P_ERROR("read, IUCV reply failed with rc = %i\n\n", rc);
return -EIO;
}
return 0;
}
static inline struct mon_private *
mon_alloc_mem(void)
{
int i,j;
struct mon_private *monpriv;
monpriv = kmalloc(sizeof(struct mon_private), GFP_KERNEL);
if (!monpriv) {
P_ERROR("no memory for monpriv\n");
return NULL;
}
memset(monpriv, 0, sizeof(struct mon_private));
for (i = 0; i < MON_MSGLIM; i++) {
monpriv->msg_array[i] = kmalloc(sizeof(struct mon_msg),
GFP_KERNEL);
if (!monpriv->msg_array[i]) {
P_ERROR("open, no memory for msg_array\n");
for (j = 0; j < i; j++)
kfree(monpriv->msg_array[j]);
return NULL;
}
memset(monpriv->msg_array[i], 0, sizeof(struct mon_msg));
}
return monpriv;
}
static inline void
mon_read_debug(struct mon_msg *monmsg, struct mon_private *monpriv)
{
#ifdef MON_DEBUG
u8 msg_type[2], mca_type;
unsigned long records_len;
records_len = mon_rec_end(monmsg) - mon_rec_start(monmsg) + 1;
memcpy(msg_type, &monmsg->local_eib.iptrgcls, 2);
EBCASC(msg_type, 2);
mca_type = mon_mca_type(monmsg, 0);
EBCASC(&mca_type, 1);
P_DEBUG("read, mon_read_index = %i, mon_write_index = %i\n",
monpriv->read_index, monpriv->write_index);
P_DEBUG("read, pathid = 0x%04X, msgid = 0x%08X, trgcls = 0x%08X\n",
monmsg->local_eib.ippathid, monmsg->local_eib.ipmsgid,
monmsg->local_eib.iptrgcls);
P_DEBUG("read, msg_type = '%c%c', mca_type = '%c' / 0x%X / 0x%X\n",
msg_type[0], msg_type[1], mca_type ? mca_type : 'X',
mon_mca_type(monmsg, 1), mon_mca_type(monmsg, 2));
P_DEBUG("read, MCA: start = 0x%lX, end = 0x%lX\n",
mon_mca_start(monmsg), mon_mca_end(monmsg));
P_DEBUG("read, REC: start = 0x%X, end = 0x%X, len = %lu\n\n",
mon_rec_start(monmsg), mon_rec_end(monmsg), records_len);
if (mon_mca_size(monmsg) > 12)
P_DEBUG("READ, MORE THAN ONE MCA\n\n");
#endif
}
static inline void
mon_next_mca(struct mon_msg *monmsg)
{
if (likely((mon_mca_size(monmsg) - monmsg->mca_offset) == 12))
return;
P_DEBUG("READ, NEXT MCA\n\n");
monmsg->mca_offset += 12;
monmsg->pos = 0;
}
static inline struct mon_msg *
mon_next_message(struct mon_private *monpriv)
{
struct mon_msg *monmsg;
if (!atomic_read(&monpriv->read_ready))
return NULL;
monmsg = monpriv->msg_array[monpriv->read_index];
if (unlikely(monmsg->replied_msglim)) {
monmsg->replied_msglim = 0;
monmsg->msglim_reached = 0;
monmsg->pos = 0;
monmsg->mca_offset = 0;
P_WARNING("read, message limit reached\n");
monpriv->read_index = (monpriv->read_index + 1) %
MON_MSGLIM;
atomic_dec(&monpriv->read_ready);
return ERR_PTR(-EOVERFLOW);
}
return monmsg;
}
/******************************************************************************
* IUCV handler *
*****************************************************************************/
static void
mon_iucv_ConnectionComplete(iucv_ConnectionComplete *eib, void *pgm_data)
{
struct mon_private *monpriv = (struct mon_private *) pgm_data;
P_DEBUG("IUCV connection completed\n");
P_DEBUG("IUCV ACCEPT (from *MONITOR): Version = 0x%02X, Event = "
"0x%02X, Sample = 0x%02X\n",
eib->ipuser[0], eib->ipuser[1], eib->ipuser[2]);
atomic_set(&monpriv->iucv_connected, 1);
wake_up(&mon_conn_wait_queue);
}
static void
mon_iucv_ConnectionSevered(iucv_ConnectionSevered *eib, void *pgm_data)
{
struct mon_private *monpriv = (struct mon_private *) pgm_data;
P_ERROR("IUCV connection severed with rc = 0x%X\n",
(u8) eib->ipuser[0]);
atomic_set(&monpriv->iucv_severed, 1);
wake_up(&mon_conn_wait_queue);
wake_up_interruptible(&mon_read_wait_queue);
}
static void
mon_iucv_MessagePending(iucv_MessagePending *eib, void *pgm_data)
{
struct mon_private *monpriv = (struct mon_private *) pgm_data;
P_DEBUG("IUCV message pending\n");
memcpy(&monpriv->msg_array[monpriv->write_index]->local_eib, eib,
sizeof(iucv_MessagePending));
if (atomic_inc_return(&monpriv->msglim_count) == MON_MSGLIM) {
P_WARNING("IUCV message pending, message limit (%i) reached\n",
MON_MSGLIM);
monpriv->msg_array[monpriv->write_index]->msglim_reached = 1;
}
monpriv->write_index = (monpriv->write_index + 1) % MON_MSGLIM;
atomic_inc(&monpriv->read_ready);
wake_up_interruptible(&mon_read_wait_queue);
}
static iucv_interrupt_ops_t mon_iucvops = {
.ConnectionComplete = mon_iucv_ConnectionComplete,
.ConnectionSevered = mon_iucv_ConnectionSevered,
.MessagePending = mon_iucv_MessagePending,
};
/******************************************************************************
* file operations *
*****************************************************************************/
static int
mon_open(struct inode *inode, struct file *filp)
{
int rc, i;
struct mon_private *monpriv;
/*
* only one user allowed
*/
if (test_and_set_bit(MON_IN_USE, &mon_in_use))
return -EBUSY;
monpriv = mon_alloc_mem();
if (!monpriv)
return -ENOMEM;
/*
* Register with IUCV and connect to *MONITOR service
*/
monpriv->iucv_handle = iucv_register_program("my_monreader ",
MON_SERVICE,
NULL,
&mon_iucvops,
monpriv);
if (!monpriv->iucv_handle) {
P_ERROR("failed to register with iucv driver\n");
rc = -EIO;
goto out_error;
}
P_INFO("open, registered with IUCV\n");
rc = iucv_connect(&monpriv->pathid, MON_MSGLIM, user_data_connect,
MON_SERVICE, iucv_host, IPRMDATA, NULL, NULL,
monpriv->iucv_handle, NULL);
if (rc) {
P_ERROR("iucv connection to *MONITOR failed with "
"IPUSER SEVER code = %i\n", rc);
rc = -EIO;
goto out_unregister;
}
/*
* Wait for connection confirmation
*/
wait_event(mon_conn_wait_queue,
atomic_read(&monpriv->iucv_connected) ||
atomic_read(&monpriv->iucv_severed));
if (atomic_read(&monpriv->iucv_severed)) {
atomic_set(&monpriv->iucv_severed, 0);
atomic_set(&monpriv->iucv_connected, 0);
rc = -EIO;
goto out_unregister;
}
P_INFO("open, established connection to *MONITOR service\n\n");
filp->private_data = monpriv;
return nonseekable_open(inode, filp);
out_unregister:
iucv_unregister_program(monpriv->iucv_handle);
out_error:
for (i = 0; i < MON_MSGLIM; i++)
kfree(monpriv->msg_array[i]);
kfree(monpriv);
clear_bit(MON_IN_USE, &mon_in_use);
return rc;
}
static int
mon_close(struct inode *inode, struct file *filp)
{
int rc, i;
struct mon_private *monpriv = filp->private_data;
/*
* Close IUCV connection and unregister
*/
rc = iucv_sever(monpriv->pathid, user_data_sever);
if (rc)
P_ERROR("close, iucv_sever failed with rc = %i\n", rc);
else
P_INFO("close, terminated connection to *MONITOR service\n");
rc = iucv_unregister_program(monpriv->iucv_handle);
if (rc)
P_ERROR("close, iucv_unregister failed with rc = %i\n", rc);
else
P_INFO("close, unregistered with IUCV\n");
atomic_set(&monpriv->iucv_severed, 0);
atomic_set(&monpriv->iucv_connected, 0);
atomic_set(&monpriv->read_ready, 0);
atomic_set(&monpriv->msglim_count, 0);
monpriv->write_index = 0;
monpriv->read_index = 0;
for (i = 0; i < MON_MSGLIM; i++)
kfree(monpriv->msg_array[i]);
kfree(monpriv);
clear_bit(MON_IN_USE, &mon_in_use);
return 0;
}
static ssize_t
mon_read(struct file *filp, char __user *data, size_t count, loff_t *ppos)
{
struct mon_private *monpriv = filp->private_data;
struct mon_msg *monmsg;
int ret;
u32 mce_start;
monmsg = mon_next_message(monpriv);
if (IS_ERR(monmsg))
return PTR_ERR(monmsg);
if (!monmsg) {
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
ret = wait_event_interruptible(mon_read_wait_queue,
atomic_read(&monpriv->read_ready) ||
atomic_read(&monpriv->iucv_severed));
if (ret)
return ret;
if (unlikely(atomic_read(&monpriv->iucv_severed)))
return -EIO;
monmsg = monpriv->msg_array[monpriv->read_index];
}
if (!monmsg->pos) {
monmsg->pos = mon_mca_start(monmsg) + monmsg->mca_offset;
mon_read_debug(monmsg, monpriv);
}
if (mon_check_mca(monmsg))
goto reply;
/* read monitor control element (12 bytes) first */
mce_start = mon_mca_start(monmsg) + monmsg->mca_offset;
if ((monmsg->pos >= mce_start) && (monmsg->pos < mce_start + 12)) {
count = min(count, (size_t) mce_start + 12 - monmsg->pos);
ret = copy_to_user(data, (void *) (unsigned long) monmsg->pos,
count);
if (ret)
return -EFAULT;
monmsg->pos += count;
if (monmsg->pos == mce_start + 12)
monmsg->pos = mon_rec_start(monmsg);
goto out_copy;
}
/* read records */
if (monmsg->pos <= mon_rec_end(monmsg)) {
count = min(count, (size_t) mon_rec_end(monmsg) - monmsg->pos
+ 1);
ret = copy_to_user(data, (void *) (unsigned long) monmsg->pos,
count);
if (ret)
return -EFAULT;
monmsg->pos += count;
if (monmsg->pos > mon_rec_end(monmsg))
mon_next_mca(monmsg);
goto out_copy;
}
reply:
ret = mon_send_reply(monmsg, monpriv);
return ret;
out_copy:
*ppos += count;
return count;
}
static unsigned int
mon_poll(struct file *filp, struct poll_table_struct *p)
{
struct mon_private *monpriv = filp->private_data;
poll_wait(filp, &mon_read_wait_queue, p);
if (unlikely(atomic_read(&monpriv->iucv_severed)))
return POLLERR;
if (atomic_read(&monpriv->read_ready))
return POLLIN | POLLRDNORM;
return 0;
}
static struct file_operations mon_fops = {
.owner = THIS_MODULE,
.open = &mon_open,
.release = &mon_close,
.read = &mon_read,
.poll = &mon_poll,
};
static struct miscdevice mon_dev = {
.name = "monreader",
.devfs_name = "monreader",
.fops = &mon_fops,
.minor = MISC_DYNAMIC_MINOR,
};
/******************************************************************************
* module init/exit *
*****************************************************************************/
static int __init
mon_init(void)
{
int rc;
if (!MACHINE_IS_VM) {
P_ERROR("not running under z/VM, driver not loaded\n");
return -ENODEV;
}
rc = segment_type(mon_dcss_name);
if (rc < 0) {
mon_segment_warn(rc, mon_dcss_name);
return rc;
}
if (rc != SEG_TYPE_SC) {
P_ERROR("segment %s has unsupported type, should be SC\n",
mon_dcss_name);
return -EINVAL;
}
rc = segment_load(mon_dcss_name, SEGMENT_SHARED,
&mon_dcss_start, &mon_dcss_end);
if (rc < 0) {
mon_segment_warn(rc, mon_dcss_name);
return -EINVAL;
}
dcss_mkname(mon_dcss_name, &user_data_connect[8]);
rc = misc_register(&mon_dev);
if (rc < 0 ) {
P_ERROR("misc_register failed, rc = %i\n", rc);
goto out;
}
P_INFO("Loaded segment %s from %p to %p, size = %lu Byte\n",
mon_dcss_name, (void *) mon_dcss_start, (void *) mon_dcss_end,
mon_dcss_end - mon_dcss_start + 1);
return 0;
out:
segment_unload(mon_dcss_name);
return rc;
}
static void __exit
mon_exit(void)
{
segment_unload(mon_dcss_name);
WARN_ON(misc_deregister(&mon_dev) != 0);
return;
}
module_init(mon_init);
module_exit(mon_exit);
module_param_string(mondcss, mon_dcss_name, 9, 0444);
MODULE_PARM_DESC(mondcss, "Name of DCSS segment to be used for *MONITOR "
"service, max. 8 chars. Default is MONDCSS");
MODULE_AUTHOR("Gerald Schaefer <[email protected]>");
MODULE_DESCRIPTION("Character device driver for reading z/VM "
"monitor service records.");
MODULE_LICENSE("GPL");

1335
drivers/s390/char/raw3270.c Normal file

File diff suppressed because it is too large Load diff

274
drivers/s390/char/raw3270.h Normal file
View file

@ -0,0 +1,274 @@
/*
* drivers/s390/char/raw3270.h
* IBM/3270 Driver
*
* Author(s):
* Original 3270 Code for 2.4 written by Richard Hitt (UTS Global)
* Rewritten for 2.5 by Martin Schwidefsky <schwidefsky@de.ibm.com>
* -- Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
*/
#include <asm/idals.h>
#include <asm/ioctl.h>
/* ioctls for fullscreen 3270 */
#define TUBICMD _IO('3', 3) /* set ccw command for fs reads. */
#define TUBOCMD _IO('3', 4) /* set ccw command for fs writes. */
#define TUBGETI _IO('3', 7) /* get ccw command for fs reads. */
#define TUBGETO _IO('3', 8) /* get ccw command for fs writes. */
#define TUBSETMOD _IO('3',12) /* FIXME: what does it do ?*/
#define TUBGETMOD _IO('3',13) /* FIXME: what does it do ?*/
/* Local Channel Commands */
#define TC_WRITE 0x01 /* Write */
#define TC_EWRITE 0x05 /* Erase write */
#define TC_READMOD 0x06 /* Read modified */
#define TC_EWRITEA 0x0d /* Erase write alternate */
#define TC_WRITESF 0x11 /* Write structured field */
/* Buffer Control Orders */
#define TO_SF 0x1d /* Start field */
#define TO_SBA 0x11 /* Set buffer address */
#define TO_IC 0x13 /* Insert cursor */
#define TO_PT 0x05 /* Program tab */
#define TO_RA 0x3c /* Repeat to address */
#define TO_SFE 0x29 /* Start field extended */
#define TO_EUA 0x12 /* Erase unprotected to address */
#define TO_MF 0x2c /* Modify field */
#define TO_SA 0x28 /* Set attribute */
/* Field Attribute Bytes */
#define TF_INPUT 0x40 /* Visible input */
#define TF_INPUTN 0x4c /* Invisible input */
#define TF_INMDT 0xc1 /* Visible, Set-MDT */
#define TF_LOG 0x60
/* Character Attribute Bytes */
#define TAT_RESET 0x00
#define TAT_FIELD 0xc0
#define TAT_EXTHI 0x41
#define TAT_COLOR 0x42
#define TAT_CHARS 0x43
#define TAT_TRANS 0x46
/* Extended-Highlighting Bytes */
#define TAX_RESET 0x00
#define TAX_BLINK 0xf1
#define TAX_REVER 0xf2
#define TAX_UNDER 0xf4
/* Reset value */
#define TAR_RESET 0x00
/* Color values */
#define TAC_RESET 0x00
#define TAC_BLUE 0xf1
#define TAC_RED 0xf2
#define TAC_PINK 0xf3
#define TAC_GREEN 0xf4
#define TAC_TURQ 0xf5
#define TAC_YELLOW 0xf6
#define TAC_WHITE 0xf7
#define TAC_DEFAULT 0x00
/* Write Control Characters */
#define TW_NONE 0x40 /* No particular action */
#define TW_KR 0xc2 /* Keyboard restore */
#define TW_PLUSALARM 0x04 /* Add this bit for alarm */
#define RAW3270_MAXDEVS 256
/* For TUBGETMOD and TUBSETMOD. Should include. */
struct raw3270_iocb {
short model;
short line_cnt;
short col_cnt;
short pf_cnt;
short re_cnt;
short map;
};
struct raw3270;
struct raw3270_view;
/* 3270 CCW request */
struct raw3270_request {
struct list_head list; /* list head for request queueing. */
struct raw3270_view *view; /* view of this request */
struct ccw1 ccw; /* single ccw. */
void *buffer; /* output buffer. */
size_t size; /* size of output buffer. */
int rescnt; /* residual count from devstat. */
int rc; /* return code for this request. */
/* Callback for delivering final status. */
void (*callback)(struct raw3270_request *, void *);
void *callback_data;
};
struct raw3270_request *raw3270_request_alloc(size_t size);
struct raw3270_request *raw3270_request_alloc_bootmem(size_t size);
void raw3270_request_free(struct raw3270_request *);
void raw3270_request_reset(struct raw3270_request *);
void raw3270_request_set_cmd(struct raw3270_request *, u8 cmd);
int raw3270_request_add_data(struct raw3270_request *, void *, size_t);
void raw3270_request_set_data(struct raw3270_request *, void *, size_t);
void raw3270_request_set_idal(struct raw3270_request *, struct idal_buffer *);
static inline int
raw3270_request_final(struct raw3270_request *rq)
{
return list_empty(&rq->list);
}
void raw3270_buffer_address(struct raw3270 *, char *, unsigned short);
/* Return value of *intv (see raw3270_fn below) can be one of the following: */
#define RAW3270_IO_DONE 0 /* request finished */
#define RAW3270_IO_BUSY 1 /* request still active */
#define RAW3270_IO_RETRY 2 /* retry current request */
#define RAW3270_IO_STOP 3 /* kill current request */
/*
* Functions of a 3270 view.
*/
struct raw3270_fn {
int (*activate)(struct raw3270_view *);
void (*deactivate)(struct raw3270_view *);
int (*intv)(struct raw3270_view *,
struct raw3270_request *, struct irb *);
void (*release)(struct raw3270_view *);
void (*free)(struct raw3270_view *);
};
/*
* View structure chaining. The raw3270_view structure is meant to
* be embedded at the start of the real view data structure, e.g.:
* struct example {
* struct raw3270_view view;
* ...
* };
*/
struct raw3270_view {
struct list_head list;
spinlock_t lock;
atomic_t ref_count;
struct raw3270 *dev;
struct raw3270_fn *fn;
unsigned int model;
unsigned int rows, cols; /* # of rows & colums of the view */
unsigned char *ascebc; /* ascii -> ebcdic table */
};
int raw3270_add_view(struct raw3270_view *, struct raw3270_fn *, int);
int raw3270_activate_view(struct raw3270_view *);
void raw3270_del_view(struct raw3270_view *);
void raw3270_deactivate_view(struct raw3270_view *);
struct raw3270_view *raw3270_find_view(struct raw3270_fn *, int);
int raw3270_start(struct raw3270_view *, struct raw3270_request *);
int raw3270_start_irq(struct raw3270_view *, struct raw3270_request *);
/* Reference count inliner for view structures. */
static inline void
raw3270_get_view(struct raw3270_view *view)
{
atomic_inc(&view->ref_count);
}
extern wait_queue_head_t raw3270_wait_queue;
static inline void
raw3270_put_view(struct raw3270_view *view)
{
if (atomic_dec_return(&view->ref_count) == 0)
wake_up(&raw3270_wait_queue);
}
struct raw3270 *raw3270_setup_console(struct ccw_device *cdev);
void raw3270_wait_cons_dev(struct raw3270 *);
/* Notifier for device addition/removal */
int raw3270_register_notifier(void (*notifier)(int, int));
void raw3270_unregister_notifier(void (*notifier)(int, int));
/*
* Little memory allocator for string objects.
*/
struct string
{
struct list_head list;
struct list_head update;
unsigned long size;
unsigned long len;
char string[0];
} __attribute__ ((aligned(8)));
static inline struct string *
alloc_string(struct list_head *free_list, unsigned long len)
{
struct string *cs, *tmp;
unsigned long size;
size = (len + 7L) & -8L;
list_for_each_entry(cs, free_list, list) {
if (cs->size < size)
continue;
if (cs->size > size + sizeof(struct string)) {
char *endaddr = (char *) (cs + 1) + cs->size;
tmp = (struct string *) (endaddr - size) - 1;
tmp->size = size;
cs->size -= size + sizeof(struct string);
cs = tmp;
} else
list_del(&cs->list);
cs->len = len;
INIT_LIST_HEAD(&cs->list);
INIT_LIST_HEAD(&cs->update);
return cs;
}
return 0;
}
static inline unsigned long
free_string(struct list_head *free_list, struct string *cs)
{
struct string *tmp;
struct list_head *p, *left;
/* Find out the left neighbour in free memory list. */
left = free_list;
list_for_each(p, free_list) {
if (list_entry(p, struct string, list) > cs)
break;
left = p;
}
/* Try to merge with right neighbour = next element from left. */
if (left->next != free_list) {
tmp = list_entry(left->next, struct string, list);
if ((char *) (cs + 1) + cs->size == (char *) tmp) {
list_del(&tmp->list);
cs->size += tmp->size + sizeof(struct string);
}
}
/* Try to merge with left neighbour. */
if (left != free_list) {
tmp = list_entry(left, struct string, list);
if ((char *) (tmp + 1) + tmp->size == (char *) cs) {
tmp->size += cs->size + sizeof(struct string);
return tmp->size;
}
}
__list_add(&cs->list, left, left->next);
return cs->size;
}
static inline void
add_string_memory(struct list_head *free_list, void *mem, unsigned long size)
{
struct string *cs;
cs = (struct string *) mem;
cs->size = size - sizeof(struct string);
free_string(free_list, cs);
}

915
drivers/s390/char/sclp.c Normal file
View file

@ -0,0 +1,915 @@
/*
* drivers/s390/char/sclp.c
* core function to access sclp interface
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#include <linux/module.h>
#include <linux/err.h>
#include <linux/spinlock.h>
#include <linux/interrupt.h>
#include <linux/timer.h>
#include <linux/reboot.h>
#include <linux/jiffies.h>
#include <asm/types.h>
#include <asm/s390_ext.h>
#include "sclp.h"
#define SCLP_HEADER "sclp: "
/* Structure for register_early_external_interrupt. */
static ext_int_info_t ext_int_info_hwc;
/* Lock to protect internal data consistency. */
static DEFINE_SPINLOCK(sclp_lock);
/* Mask of events that we can receive from the sclp interface. */
static sccb_mask_t sclp_receive_mask;
/* Mask of events that we can send to the sclp interface. */
static sccb_mask_t sclp_send_mask;
/* List of registered event listeners and senders. */
static struct list_head sclp_reg_list;
/* List of queued requests. */
static struct list_head sclp_req_queue;
/* Data for read and and init requests. */
static struct sclp_req sclp_read_req;
static struct sclp_req sclp_init_req;
static char sclp_read_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
static char sclp_init_sccb[PAGE_SIZE] __attribute__((__aligned__(PAGE_SIZE)));
/* Timer for request retries. */
static struct timer_list sclp_request_timer;
/* Internal state: is the driver initialized? */
static volatile enum sclp_init_state_t {
sclp_init_state_uninitialized,
sclp_init_state_initializing,
sclp_init_state_initialized
} sclp_init_state = sclp_init_state_uninitialized;
/* Internal state: is a request active at the sclp? */
static volatile enum sclp_running_state_t {
sclp_running_state_idle,
sclp_running_state_running
} sclp_running_state = sclp_running_state_idle;
/* Internal state: is a read request pending? */
static volatile enum sclp_reading_state_t {
sclp_reading_state_idle,
sclp_reading_state_reading
} sclp_reading_state = sclp_reading_state_idle;
/* Internal state: is the driver currently serving requests? */
static volatile enum sclp_activation_state_t {
sclp_activation_state_active,
sclp_activation_state_deactivating,
sclp_activation_state_inactive,
sclp_activation_state_activating
} sclp_activation_state = sclp_activation_state_active;
/* Internal state: is an init mask request pending? */
static volatile enum sclp_mask_state_t {
sclp_mask_state_idle,
sclp_mask_state_initializing
} sclp_mask_state = sclp_mask_state_idle;
/* Maximum retry counts */
#define SCLP_INIT_RETRY 3
#define SCLP_MASK_RETRY 3
#define SCLP_REQUEST_RETRY 3
/* Timeout intervals in seconds.*/
#define SCLP_BUSY_INTERVAL 2
#define SCLP_RETRY_INTERVAL 5
static void sclp_process_queue(void);
static int sclp_init_mask(int calculate);
static int sclp_init(void);
/* Perform service call. Return 0 on success, non-zero otherwise. */
static int
service_call(sclp_cmdw_t command, void *sccb)
{
int cc;
__asm__ __volatile__(
" .insn rre,0xb2200000,%1,%2\n" /* servc %1,%2 */
" ipm %0\n"
" srl %0,28"
: "=&d" (cc)
: "d" (command), "a" (__pa(sccb))
: "cc", "memory" );
if (cc == 3)
return -EIO;
if (cc == 2)
return -EBUSY;
return 0;
}
/* Request timeout handler. Restart the request queue. If DATA is non-zero,
* force restart of running request. */
static void
sclp_request_timeout(unsigned long data)
{
unsigned long flags;
if (data) {
spin_lock_irqsave(&sclp_lock, flags);
sclp_running_state = sclp_running_state_idle;
spin_unlock_irqrestore(&sclp_lock, flags);
}
sclp_process_queue();
}
/* Set up request retry timer. Called while sclp_lock is locked. */
static inline void
__sclp_set_request_timer(unsigned long time, void (*function)(unsigned long),
unsigned long data)
{
del_timer(&sclp_request_timer);
sclp_request_timer.function = function;
sclp_request_timer.data = data;
sclp_request_timer.expires = jiffies + time;
add_timer(&sclp_request_timer);
}
/* Try to start a request. Return zero if the request was successfully
* started or if it will be started at a later time. Return non-zero otherwise.
* Called while sclp_lock is locked. */
static int
__sclp_start_request(struct sclp_req *req)
{
int rc;
if (sclp_running_state != sclp_running_state_idle)
return 0;
del_timer(&sclp_request_timer);
if (req->start_count <= SCLP_REQUEST_RETRY) {
rc = service_call(req->command, req->sccb);
req->start_count++;
} else
rc = -EIO;
if (rc == 0) {
/* Sucessfully started request */
req->status = SCLP_REQ_RUNNING;
sclp_running_state = sclp_running_state_running;
__sclp_set_request_timer(SCLP_RETRY_INTERVAL * HZ,
sclp_request_timeout, 1);
return 0;
} else if (rc == -EBUSY) {
/* Try again later */
__sclp_set_request_timer(SCLP_BUSY_INTERVAL * HZ,
sclp_request_timeout, 0);
return 0;
}
/* Request failed */
req->status = SCLP_REQ_FAILED;
return rc;
}
/* Try to start queued requests. */
static void
sclp_process_queue(void)
{
struct sclp_req *req;
int rc;
unsigned long flags;
spin_lock_irqsave(&sclp_lock, flags);
if (sclp_running_state != sclp_running_state_idle) {
spin_unlock_irqrestore(&sclp_lock, flags);
return;
}
del_timer(&sclp_request_timer);
while (!list_empty(&sclp_req_queue)) {
req = list_entry(sclp_req_queue.next, struct sclp_req, list);
rc = __sclp_start_request(req);
if (rc == 0)
break;
/* Request failed. */
list_del(&req->list);
if (req->callback) {
spin_unlock_irqrestore(&sclp_lock, flags);
req->callback(req, req->callback_data);
spin_lock_irqsave(&sclp_lock, flags);
}
}
spin_unlock_irqrestore(&sclp_lock, flags);
}
/* Queue a new request. Return zero on success, non-zero otherwise. */
int
sclp_add_request(struct sclp_req *req)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
if ((sclp_init_state != sclp_init_state_initialized ||
sclp_activation_state != sclp_activation_state_active) &&
req != &sclp_init_req) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EIO;
}
req->status = SCLP_REQ_QUEUED;
req->start_count = 0;
list_add_tail(&req->list, &sclp_req_queue);
rc = 0;
/* Start if request is first in list */
if (req->list.prev == &sclp_req_queue) {
rc = __sclp_start_request(req);
if (rc)
list_del(&req->list);
}
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
EXPORT_SYMBOL(sclp_add_request);
/* Dispatch events found in request buffer to registered listeners. Return 0
* if all events were dispatched, non-zero otherwise. */
static int
sclp_dispatch_evbufs(struct sccb_header *sccb)
{
unsigned long flags;
struct evbuf_header *evbuf;
struct list_head *l;
struct sclp_register *reg;
int offset;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
rc = 0;
for (offset = sizeof(struct sccb_header); offset < sccb->length;
offset += evbuf->length) {
/* Search for event handler */
evbuf = (struct evbuf_header *) ((addr_t) sccb + offset);
reg = NULL;
list_for_each(l, &sclp_reg_list) {
reg = list_entry(l, struct sclp_register, list);
if (reg->receive_mask & (1 << (32 - evbuf->type)))
break;
else
reg = NULL;
}
if (reg && reg->receiver_fn) {
spin_unlock_irqrestore(&sclp_lock, flags);
reg->receiver_fn(evbuf);
spin_lock_irqsave(&sclp_lock, flags);
} else if (reg == NULL)
rc = -ENOSYS;
}
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Read event data request callback. */
static void
sclp_read_cb(struct sclp_req *req, void *data)
{
unsigned long flags;
struct sccb_header *sccb;
sccb = (struct sccb_header *) req->sccb;
if (req->status == SCLP_REQ_DONE && (sccb->response_code == 0x20 ||
sccb->response_code == 0x220))
sclp_dispatch_evbufs(sccb);
spin_lock_irqsave(&sclp_lock, flags);
sclp_reading_state = sclp_reading_state_idle;
spin_unlock_irqrestore(&sclp_lock, flags);
}
/* Prepare read event data request. Called while sclp_lock is locked. */
static inline void
__sclp_make_read_req(void)
{
struct sccb_header *sccb;
sccb = (struct sccb_header *) sclp_read_sccb;
clear_page(sccb);
memset(&sclp_read_req, 0, sizeof(struct sclp_req));
sclp_read_req.command = SCLP_CMDW_READDATA;
sclp_read_req.status = SCLP_REQ_QUEUED;
sclp_read_req.start_count = 0;
sclp_read_req.callback = sclp_read_cb;
sclp_read_req.sccb = sccb;
sccb->length = PAGE_SIZE;
sccb->function_code = 0;
sccb->control_mask[2] = 0x80;
}
/* Search request list for request with matching sccb. Return request if found,
* NULL otherwise. Called while sclp_lock is locked. */
static inline struct sclp_req *
__sclp_find_req(u32 sccb)
{
struct list_head *l;
struct sclp_req *req;
list_for_each(l, &sclp_req_queue) {
req = list_entry(l, struct sclp_req, list);
if (sccb == (u32) (addr_t) req->sccb)
return req;
}
return NULL;
}
/* Handler for external interruption. Perform request post-processing.
* Prepare read event data request if necessary. Start processing of next
* request on queue. */
static void
sclp_interrupt_handler(struct pt_regs *regs, __u16 code)
{
struct sclp_req *req;
u32 finished_sccb;
u32 evbuf_pending;
spin_lock(&sclp_lock);
finished_sccb = S390_lowcore.ext_params & 0xfffffff8;
evbuf_pending = S390_lowcore.ext_params & 0x3;
if (finished_sccb) {
req = __sclp_find_req(finished_sccb);
if (req) {
/* Request post-processing */
list_del(&req->list);
req->status = SCLP_REQ_DONE;
if (req->callback) {
spin_unlock(&sclp_lock);
req->callback(req, req->callback_data);
spin_lock(&sclp_lock);
}
}
sclp_running_state = sclp_running_state_idle;
}
if (evbuf_pending && sclp_receive_mask != 0 &&
sclp_reading_state == sclp_reading_state_idle &&
sclp_activation_state == sclp_activation_state_active ) {
sclp_reading_state = sclp_reading_state_reading;
__sclp_make_read_req();
/* Add request to head of queue */
list_add(&sclp_read_req.list, &sclp_req_queue);
}
spin_unlock(&sclp_lock);
sclp_process_queue();
}
/* Return current Time-Of-Day clock. */
static inline u64
sclp_get_clock(void)
{
u64 result;
asm volatile ("STCK 0(%1)" : "=m" (result) : "a" (&(result)) : "cc");
return result;
}
/* Convert interval in jiffies to TOD ticks. */
static inline u64
sclp_tod_from_jiffies(unsigned long jiffies)
{
return (u64) (jiffies / HZ) << 32;
}
/* Wait until a currently running request finished. Note: while this function
* is running, no timers are served on the calling CPU. */
void
sclp_sync_wait(void)
{
unsigned long psw_mask;
unsigned long cr0, cr0_sync;
u64 timeout;
/* We'll be disabling timer interrupts, so we need a custom timeout
* mechanism */
timeout = 0;
if (timer_pending(&sclp_request_timer)) {
/* Get timeout TOD value */
timeout = sclp_get_clock() +
sclp_tod_from_jiffies(sclp_request_timer.expires -
jiffies);
}
/* Prevent bottom half from executing once we force interrupts open */
local_bh_disable();
/* Enable service-signal interruption, disable timer interrupts */
__ctl_store(cr0, 0, 0);
cr0_sync = cr0;
cr0_sync |= 0x00000200;
cr0_sync &= 0xFFFFF3AC;
__ctl_load(cr0_sync, 0, 0);
asm volatile ("STOSM 0(%1),0x01"
: "=m" (psw_mask) : "a" (&psw_mask) : "memory");
/* Loop until driver state indicates finished request */
while (sclp_running_state != sclp_running_state_idle) {
/* Check for expired request timer */
if (timer_pending(&sclp_request_timer) &&
sclp_get_clock() > timeout &&
del_timer(&sclp_request_timer))
sclp_request_timer.function(sclp_request_timer.data);
barrier();
cpu_relax();
}
/* Restore interrupt settings */
asm volatile ("SSM 0(%0)"
: : "a" (&psw_mask) : "memory");
__ctl_load(cr0, 0, 0);
__local_bh_enable();
}
EXPORT_SYMBOL(sclp_sync_wait);
/* Dispatch changes in send and receive mask to registered listeners. */
static inline void
sclp_dispatch_state_change(void)
{
struct list_head *l;
struct sclp_register *reg;
unsigned long flags;
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
do {
spin_lock_irqsave(&sclp_lock, flags);
reg = NULL;
list_for_each(l, &sclp_reg_list) {
reg = list_entry(l, struct sclp_register, list);
receive_mask = reg->receive_mask & sclp_receive_mask;
send_mask = reg->send_mask & sclp_send_mask;
if (reg->sclp_receive_mask != receive_mask ||
reg->sclp_send_mask != send_mask) {
reg->sclp_receive_mask = receive_mask;
reg->sclp_send_mask = send_mask;
break;
} else
reg = NULL;
}
spin_unlock_irqrestore(&sclp_lock, flags);
if (reg && reg->state_change_fn)
reg->state_change_fn(reg);
} while (reg);
}
struct sclp_statechangebuf {
struct evbuf_header header;
u8 validity_sclp_active_facility_mask : 1;
u8 validity_sclp_receive_mask : 1;
u8 validity_sclp_send_mask : 1;
u8 validity_read_data_function_mask : 1;
u16 _zeros : 12;
u16 mask_length;
u64 sclp_active_facility_mask;
sccb_mask_t sclp_receive_mask;
sccb_mask_t sclp_send_mask;
u32 read_data_function_mask;
} __attribute__((packed));
/* State change event callback. Inform listeners of changes. */
static void
sclp_state_change_cb(struct evbuf_header *evbuf)
{
unsigned long flags;
struct sclp_statechangebuf *scbuf;
scbuf = (struct sclp_statechangebuf *) evbuf;
if (scbuf->mask_length != sizeof(sccb_mask_t))
return;
spin_lock_irqsave(&sclp_lock, flags);
if (scbuf->validity_sclp_receive_mask)
sclp_receive_mask = scbuf->sclp_receive_mask;
if (scbuf->validity_sclp_send_mask)
sclp_send_mask = scbuf->sclp_send_mask;
spin_unlock_irqrestore(&sclp_lock, flags);
sclp_dispatch_state_change();
}
static struct sclp_register sclp_state_change_event = {
.receive_mask = EvTyp_StateChange_Mask,
.receiver_fn = sclp_state_change_cb
};
/* Calculate receive and send mask of currently registered listeners.
* Called while sclp_lock is locked. */
static inline void
__sclp_get_mask(sccb_mask_t *receive_mask, sccb_mask_t *send_mask)
{
struct list_head *l;
struct sclp_register *t;
*receive_mask = 0;
*send_mask = 0;
list_for_each(l, &sclp_reg_list) {
t = list_entry(l, struct sclp_register, list);
*receive_mask |= t->receive_mask;
*send_mask |= t->send_mask;
}
}
/* Register event listener. Return 0 on success, non-zero otherwise. */
int
sclp_register(struct sclp_register *reg)
{
unsigned long flags;
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
int rc;
rc = sclp_init();
if (rc)
return rc;
spin_lock_irqsave(&sclp_lock, flags);
/* Check event mask for collisions */
__sclp_get_mask(&receive_mask, &send_mask);
if (reg->receive_mask & receive_mask || reg->send_mask & send_mask) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EBUSY;
}
/* Trigger initial state change callback */
reg->sclp_receive_mask = 0;
reg->sclp_send_mask = 0;
list_add(&reg->list, &sclp_reg_list);
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_init_mask(1);
if (rc) {
spin_lock_irqsave(&sclp_lock, flags);
list_del(&reg->list);
spin_unlock_irqrestore(&sclp_lock, flags);
}
return rc;
}
EXPORT_SYMBOL(sclp_register);
/* Unregister event listener. */
void
sclp_unregister(struct sclp_register *reg)
{
unsigned long flags;
spin_lock_irqsave(&sclp_lock, flags);
list_del(&reg->list);
spin_unlock_irqrestore(&sclp_lock, flags);
sclp_init_mask(1);
}
EXPORT_SYMBOL(sclp_unregister);
/* Remove event buffers which are marked processed. Return the number of
* remaining event buffers. */
int
sclp_remove_processed(struct sccb_header *sccb)
{
struct evbuf_header *evbuf;
int unprocessed;
u16 remaining;
evbuf = (struct evbuf_header *) (sccb + 1);
unprocessed = 0;
remaining = sccb->length - sizeof(struct sccb_header);
while (remaining > 0) {
remaining -= evbuf->length;
if (evbuf->flags & 0x80) {
sccb->length -= evbuf->length;
memcpy(evbuf, (void *) ((addr_t) evbuf + evbuf->length),
remaining);
} else {
unprocessed++;
evbuf = (struct evbuf_header *)
((addr_t) evbuf + evbuf->length);
}
}
return unprocessed;
}
EXPORT_SYMBOL(sclp_remove_processed);
struct init_sccb {
struct sccb_header header;
u16 _reserved;
u16 mask_length;
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
sccb_mask_t sclp_send_mask;
sccb_mask_t sclp_receive_mask;
} __attribute__((packed));
/* Prepare init mask request. Called while sclp_lock is locked. */
static inline void
__sclp_make_init_req(u32 receive_mask, u32 send_mask)
{
struct init_sccb *sccb;
sccb = (struct init_sccb *) sclp_init_sccb;
clear_page(sccb);
memset(&sclp_init_req, 0, sizeof(struct sclp_req));
sclp_init_req.command = SCLP_CMDW_WRITEMASK;
sclp_init_req.status = SCLP_REQ_FILLED;
sclp_init_req.start_count = 0;
sclp_init_req.callback = NULL;
sclp_init_req.callback_data = NULL;
sclp_init_req.sccb = sccb;
sccb->header.length = sizeof(struct init_sccb);
sccb->mask_length = sizeof(sccb_mask_t);
sccb->receive_mask = receive_mask;
sccb->send_mask = send_mask;
sccb->sclp_receive_mask = 0;
sccb->sclp_send_mask = 0;
}
/* Start init mask request. If calculate is non-zero, calculate the mask as
* requested by registered listeners. Use zero mask otherwise. Return 0 on
* success, non-zero otherwise. */
static int
sclp_init_mask(int calculate)
{
unsigned long flags;
struct init_sccb *sccb = (struct init_sccb *) sclp_init_sccb;
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
int retry;
int rc;
unsigned long wait;
spin_lock_irqsave(&sclp_lock, flags);
/* Check if interface is in appropriate state */
if (sclp_mask_state != sclp_mask_state_idle) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EBUSY;
}
if (sclp_activation_state == sclp_activation_state_inactive) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EINVAL;
}
sclp_mask_state = sclp_mask_state_initializing;
/* Determine mask */
if (calculate)
__sclp_get_mask(&receive_mask, &send_mask);
else {
receive_mask = 0;
send_mask = 0;
}
rc = -EIO;
for (retry = 0; retry <= SCLP_MASK_RETRY; retry++) {
/* Prepare request */
__sclp_make_init_req(receive_mask, send_mask);
spin_unlock_irqrestore(&sclp_lock, flags);
if (sclp_add_request(&sclp_init_req)) {
/* Try again later */
wait = jiffies + SCLP_BUSY_INTERVAL * HZ;
while (time_before(jiffies, wait))
sclp_sync_wait();
spin_lock_irqsave(&sclp_lock, flags);
continue;
}
while (sclp_init_req.status != SCLP_REQ_DONE &&
sclp_init_req.status != SCLP_REQ_FAILED)
sclp_sync_wait();
spin_lock_irqsave(&sclp_lock, flags);
if (sclp_init_req.status == SCLP_REQ_DONE &&
sccb->header.response_code == 0x20) {
/* Successful request */
if (calculate) {
sclp_receive_mask = sccb->sclp_receive_mask;
sclp_send_mask = sccb->sclp_send_mask;
} else {
sclp_receive_mask = 0;
sclp_send_mask = 0;
}
spin_unlock_irqrestore(&sclp_lock, flags);
sclp_dispatch_state_change();
spin_lock_irqsave(&sclp_lock, flags);
rc = 0;
break;
}
}
sclp_mask_state = sclp_mask_state_idle;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Deactivate SCLP interface. On success, new requests will be rejected,
* events will no longer be dispatched. Return 0 on success, non-zero
* otherwise. */
int
sclp_deactivate(void)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
/* Deactivate can only be called when active */
if (sclp_activation_state != sclp_activation_state_active) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EINVAL;
}
sclp_activation_state = sclp_activation_state_deactivating;
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_init_mask(0);
spin_lock_irqsave(&sclp_lock, flags);
if (rc == 0)
sclp_activation_state = sclp_activation_state_inactive;
else
sclp_activation_state = sclp_activation_state_active;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
EXPORT_SYMBOL(sclp_deactivate);
/* Reactivate SCLP interface after sclp_deactivate. On success, new
* requests will be accepted, events will be dispatched again. Return 0 on
* success, non-zero otherwise. */
int
sclp_reactivate(void)
{
unsigned long flags;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
/* Reactivate can only be called when inactive */
if (sclp_activation_state != sclp_activation_state_inactive) {
spin_unlock_irqrestore(&sclp_lock, flags);
return -EINVAL;
}
sclp_activation_state = sclp_activation_state_activating;
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_init_mask(1);
spin_lock_irqsave(&sclp_lock, flags);
if (rc == 0)
sclp_activation_state = sclp_activation_state_active;
else
sclp_activation_state = sclp_activation_state_inactive;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
EXPORT_SYMBOL(sclp_reactivate);
/* Handler for external interruption used during initialization. Modify
* request state to done. */
static void
sclp_check_handler(struct pt_regs *regs, __u16 code)
{
u32 finished_sccb;
finished_sccb = S390_lowcore.ext_params & 0xfffffff8;
/* Is this the interrupt we are waiting for? */
if (finished_sccb == 0)
return;
if (finished_sccb != (u32) (addr_t) sclp_init_sccb) {
printk(KERN_WARNING SCLP_HEADER "unsolicited interrupt "
"for buffer at 0x%x\n", finished_sccb);
return;
}
spin_lock(&sclp_lock);
if (sclp_running_state == sclp_running_state_running) {
sclp_init_req.status = SCLP_REQ_DONE;
sclp_running_state = sclp_running_state_idle;
}
spin_unlock(&sclp_lock);
}
/* Initial init mask request timed out. Modify request state to failed. */
static void
sclp_check_timeout(unsigned long data)
{
unsigned long flags;
spin_lock_irqsave(&sclp_lock, flags);
if (sclp_running_state == sclp_running_state_running) {
sclp_init_req.status = SCLP_REQ_FAILED;
sclp_running_state = sclp_running_state_idle;
}
spin_unlock_irqrestore(&sclp_lock, flags);
}
/* Perform a check of the SCLP interface. Return zero if the interface is
* available and there are no pending requests from a previous instance.
* Return non-zero otherwise. */
static int
sclp_check_interface(void)
{
struct init_sccb *sccb;
unsigned long flags;
int retry;
int rc;
spin_lock_irqsave(&sclp_lock, flags);
/* Prepare init mask command */
rc = register_early_external_interrupt(0x2401, sclp_check_handler,
&ext_int_info_hwc);
if (rc) {
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
for (retry = 0; retry <= SCLP_INIT_RETRY; retry++) {
__sclp_make_init_req(0, 0);
sccb = (struct init_sccb *) sclp_init_req.sccb;
rc = service_call(sclp_init_req.command, sccb);
if (rc == -EIO)
break;
sclp_init_req.status = SCLP_REQ_RUNNING;
sclp_running_state = sclp_running_state_running;
__sclp_set_request_timer(SCLP_RETRY_INTERVAL * HZ,
sclp_check_timeout, 0);
spin_unlock_irqrestore(&sclp_lock, flags);
/* Enable service-signal interruption - needs to happen
* with IRQs enabled. */
ctl_set_bit(0, 9);
/* Wait for signal from interrupt or timeout */
sclp_sync_wait();
/* Disable service-signal interruption - needs to happen
* with IRQs enabled. */
ctl_clear_bit(0,9);
spin_lock_irqsave(&sclp_lock, flags);
del_timer(&sclp_request_timer);
if (sclp_init_req.status == SCLP_REQ_DONE &&
sccb->header.response_code == 0x20) {
rc = 0;
break;
} else
rc = -EBUSY;
}
unregister_early_external_interrupt(0x2401, sclp_check_handler,
&ext_int_info_hwc);
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Reboot event handler. Reset send and receive mask to prevent pending SCLP
* events from interfering with rebooted system. */
static int
sclp_reboot_event(struct notifier_block *this, unsigned long event, void *ptr)
{
sclp_deactivate();
return NOTIFY_DONE;
}
static struct notifier_block sclp_reboot_notifier = {
.notifier_call = sclp_reboot_event
};
/* Initialize SCLP driver. Return zero if driver is operational, non-zero
* otherwise. */
static int
sclp_init(void)
{
unsigned long flags;
int rc;
if (!MACHINE_HAS_SCLP)
return -ENODEV;
spin_lock_irqsave(&sclp_lock, flags);
/* Check for previous or running initialization */
if (sclp_init_state != sclp_init_state_uninitialized) {
spin_unlock_irqrestore(&sclp_lock, flags);
return 0;
}
sclp_init_state = sclp_init_state_initializing;
/* Set up variables */
INIT_LIST_HEAD(&sclp_req_queue);
INIT_LIST_HEAD(&sclp_reg_list);
list_add(&sclp_state_change_event.list, &sclp_reg_list);
init_timer(&sclp_request_timer);
/* Check interface */
spin_unlock_irqrestore(&sclp_lock, flags);
rc = sclp_check_interface();
spin_lock_irqsave(&sclp_lock, flags);
if (rc) {
sclp_init_state = sclp_init_state_uninitialized;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Register reboot handler */
rc = register_reboot_notifier(&sclp_reboot_notifier);
if (rc) {
sclp_init_state = sclp_init_state_uninitialized;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
/* Register interrupt handler */
rc = register_early_external_interrupt(0x2401, sclp_interrupt_handler,
&ext_int_info_hwc);
if (rc) {
unregister_reboot_notifier(&sclp_reboot_notifier);
sclp_init_state = sclp_init_state_uninitialized;
spin_unlock_irqrestore(&sclp_lock, flags);
return rc;
}
sclp_init_state = sclp_init_state_initialized;
spin_unlock_irqrestore(&sclp_lock, flags);
/* Enable service-signal external interruption - needs to happen with
* IRQs enabled. */
ctl_set_bit(0, 9);
sclp_init_mask(1);
return 0;
}

159
drivers/s390/char/sclp.h Normal file
View file

@ -0,0 +1,159 @@
/*
* drivers/s390/char/sclp.h
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#ifndef __SCLP_H__
#define __SCLP_H__
#include <linux/types.h>
#include <linux/list.h>
#include <asm/ebcdic.h>
/* maximum number of pages concerning our own memory management */
#define MAX_KMEM_PAGES (sizeof(unsigned long) << 3)
#define MAX_CONSOLE_PAGES 4
#define EvTyp_OpCmd 0x01
#define EvTyp_Msg 0x02
#define EvTyp_StateChange 0x08
#define EvTyp_PMsgCmd 0x09
#define EvTyp_CntlProgOpCmd 0x20
#define EvTyp_CntlProgIdent 0x0B
#define EvTyp_SigQuiesce 0x1D
#define EvTyp_VT220Msg 0x1A
#define EvTyp_OpCmd_Mask 0x80000000
#define EvTyp_Msg_Mask 0x40000000
#define EvTyp_StateChange_Mask 0x01000000
#define EvTyp_PMsgCmd_Mask 0x00800000
#define EvTyp_CtlProgOpCmd_Mask 0x00000001
#define EvTyp_CtlProgIdent_Mask 0x00200000
#define EvTyp_SigQuiesce_Mask 0x00000008
#define EvTyp_VT220Msg_Mask 0x00000040
#define GnrlMsgFlgs_DOM 0x8000
#define GnrlMsgFlgs_SndAlrm 0x4000
#define GnrlMsgFlgs_HoldMsg 0x2000
#define LnTpFlgs_CntlText 0x8000
#define LnTpFlgs_LabelText 0x4000
#define LnTpFlgs_DataText 0x2000
#define LnTpFlgs_EndText 0x1000
#define LnTpFlgs_PromptText 0x0800
typedef unsigned int sclp_cmdw_t;
#define SCLP_CMDW_READDATA 0x00770005
#define SCLP_CMDW_WRITEDATA 0x00760005
#define SCLP_CMDW_WRITEMASK 0x00780005
#define GDS_ID_MDSMU 0x1310
#define GDS_ID_MDSRouteInfo 0x1311
#define GDS_ID_AgUnWrkCorr 0x1549
#define GDS_ID_SNACondReport 0x1532
#define GDS_ID_CPMSU 0x1212
#define GDS_ID_RoutTargInstr 0x154D
#define GDS_ID_OpReq 0x8070
#define GDS_ID_TextCmd 0x1320
#define GDS_KEY_SelfDefTextMsg 0x31
typedef u32 sccb_mask_t; /* ATTENTION: assumes 32bit mask !!! */
struct sccb_header {
u16 length;
u8 function_code;
u8 control_mask[3];
u16 response_code;
} __attribute__((packed));
struct gds_subvector {
u8 length;
u8 key;
} __attribute__((packed));
struct gds_vector {
u16 length;
u16 gds_id;
} __attribute__((packed));
struct evbuf_header {
u16 length;
u8 type;
u8 flags;
u16 _reserved;
} __attribute__((packed));
struct sclp_req {
struct list_head list; /* list_head for request queueing. */
sclp_cmdw_t command; /* sclp command to execute */
void *sccb; /* pointer to the sccb to execute */
char status; /* status of this request */
int start_count; /* number of SVCs done for this req */
/* Callback that is called after reaching final status. */
void (*callback)(struct sclp_req *, void *data);
void *callback_data;
};
#define SCLP_REQ_FILLED 0x00 /* request is ready to be processed */
#define SCLP_REQ_QUEUED 0x01 /* request is queued to be processed */
#define SCLP_REQ_RUNNING 0x02 /* request is currently running */
#define SCLP_REQ_DONE 0x03 /* request is completed successfully */
#define SCLP_REQ_FAILED 0x05 /* request is finally failed */
/* function pointers that a high level driver has to use for registration */
/* of some routines it wants to be called from the low level driver */
struct sclp_register {
struct list_head list;
/* event masks this user is registered for */
sccb_mask_t receive_mask;
sccb_mask_t send_mask;
/* actually present events */
sccb_mask_t sclp_receive_mask;
sccb_mask_t sclp_send_mask;
/* called if event type availability changes */
void (*state_change_fn)(struct sclp_register *);
/* called for events in cp_receive_mask/sclp_receive_mask */
void (*receiver_fn)(struct evbuf_header *);
};
/* externals from sclp.c */
int sclp_add_request(struct sclp_req *req);
void sclp_sync_wait(void);
int sclp_register(struct sclp_register *reg);
void sclp_unregister(struct sclp_register *reg);
int sclp_remove_processed(struct sccb_header *sccb);
int sclp_deactivate(void);
int sclp_reactivate(void);
/* useful inlines */
/* VM uses EBCDIC 037, LPAR+native(SE+HMC) use EBCDIC 500 */
/* translate single character from ASCII to EBCDIC */
static inline unsigned char
sclp_ascebc(unsigned char ch)
{
return (MACHINE_IS_VM) ? _ascebc[ch] : _ascebc_500[ch];
}
/* translate string from EBCDIC to ASCII */
static inline void
sclp_ebcasc_str(unsigned char *str, int nr)
{
(MACHINE_IS_VM) ? EBCASC(str, nr) : EBCASC_500(str, nr);
}
/* translate string from ASCII to EBCDIC */
static inline void
sclp_ascebc_str(unsigned char *str, int nr)
{
(MACHINE_IS_VM) ? ASCEBC(str, nr) : ASCEBC_500(str, nr);
}
#endif /* __SCLP_H__ */

View file

@ -0,0 +1,252 @@
/*
* drivers/s390/char/sclp_con.c
* SCLP line mode console driver
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#include <linux/config.h>
#include <linux/kmod.h>
#include <linux/console.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/jiffies.h>
#include <linux/bootmem.h>
#include <linux/err.h>
#include "sclp.h"
#include "sclp_rw.h"
#include "sclp_tty.h"
#define SCLP_CON_PRINT_HEADER "sclp console driver: "
#define sclp_console_major 4 /* TTYAUX_MAJOR */
#define sclp_console_minor 64
#define sclp_console_name "ttyS"
/* Lock to guard over changes to global variables */
static spinlock_t sclp_con_lock;
/* List of free pages that can be used for console output buffering */
static struct list_head sclp_con_pages;
/* List of full struct sclp_buffer structures ready for output */
static struct list_head sclp_con_outqueue;
/* Counter how many buffers are emitted (max 1) and how many */
/* are on the output queue. */
static int sclp_con_buffer_count;
/* Pointer to current console buffer */
static struct sclp_buffer *sclp_conbuf;
/* Timer for delayed output of console messages */
static struct timer_list sclp_con_timer;
/* Output format for console messages */
static unsigned short sclp_con_columns;
static unsigned short sclp_con_width_htab;
static void
sclp_conbuf_callback(struct sclp_buffer *buffer, int rc)
{
unsigned long flags;
void *page;
do {
page = sclp_unmake_buffer(buffer);
spin_lock_irqsave(&sclp_con_lock, flags);
/* Remove buffer from outqueue */
list_del(&buffer->list);
sclp_con_buffer_count--;
list_add_tail((struct list_head *) page, &sclp_con_pages);
/* Check if there is a pending buffer on the out queue. */
buffer = NULL;
if (!list_empty(&sclp_con_outqueue))
buffer = list_entry(sclp_con_outqueue.next,
struct sclp_buffer, list);
spin_unlock_irqrestore(&sclp_con_lock, flags);
} while (buffer && sclp_emit_buffer(buffer, sclp_conbuf_callback));
}
static inline void
sclp_conbuf_emit(void)
{
struct sclp_buffer* buffer;
unsigned long flags;
int count;
int rc;
spin_lock_irqsave(&sclp_con_lock, flags);
buffer = sclp_conbuf;
sclp_conbuf = NULL;
if (buffer == NULL) {
spin_unlock_irqrestore(&sclp_con_lock, flags);
return;
}
list_add_tail(&buffer->list, &sclp_con_outqueue);
count = sclp_con_buffer_count++;
spin_unlock_irqrestore(&sclp_con_lock, flags);
if (count)
return;
rc = sclp_emit_buffer(buffer, sclp_conbuf_callback);
if (rc)
sclp_conbuf_callback(buffer, rc);
}
/*
* When this routine is called from the timer then we flush the
* temporary write buffer without further waiting on a final new line.
*/
static void
sclp_console_timeout(unsigned long data)
{
sclp_conbuf_emit();
}
/*
* Writes the given message to S390 system console
*/
static void
sclp_console_write(struct console *console, const char *message,
unsigned int count)
{
unsigned long flags;
void *page;
int written;
if (count == 0)
return;
spin_lock_irqsave(&sclp_con_lock, flags);
/*
* process escape characters, write message into buffer,
* send buffer to SCLP
*/
do {
/* make sure we have a console output buffer */
if (sclp_conbuf == NULL) {
while (list_empty(&sclp_con_pages)) {
spin_unlock_irqrestore(&sclp_con_lock, flags);
sclp_sync_wait();
spin_lock_irqsave(&sclp_con_lock, flags);
}
page = sclp_con_pages.next;
list_del((struct list_head *) page);
sclp_conbuf = sclp_make_buffer(page, sclp_con_columns,
sclp_con_width_htab);
}
/* try to write the string to the current output buffer */
written = sclp_write(sclp_conbuf, (const unsigned char *)
message, count);
if (written == count)
break;
/*
* Not all characters could be written to the current
* output buffer. Emit the buffer, create a new buffer
* and then output the rest of the string.
*/
spin_unlock_irqrestore(&sclp_con_lock, flags);
sclp_conbuf_emit();
spin_lock_irqsave(&sclp_con_lock, flags);
message += written;
count -= written;
} while (count > 0);
/* Setup timer to output current console buffer after 1/10 second */
if (sclp_conbuf != NULL && sclp_chars_in_buffer(sclp_conbuf) != 0 &&
!timer_pending(&sclp_con_timer)) {
init_timer(&sclp_con_timer);
sclp_con_timer.function = sclp_console_timeout;
sclp_con_timer.data = 0UL;
sclp_con_timer.expires = jiffies + HZ/10;
add_timer(&sclp_con_timer);
}
spin_unlock_irqrestore(&sclp_con_lock, flags);
}
static struct tty_driver *
sclp_console_device(struct console *c, int *index)
{
*index = c->index;
return sclp_tty_driver;
}
/*
* This routine is called from panic when the kernel
* is going to give up. We have to make sure that all buffers
* will be flushed to the SCLP.
*/
static void
sclp_console_unblank(void)
{
unsigned long flags;
sclp_conbuf_emit();
spin_lock_irqsave(&sclp_con_lock, flags);
if (timer_pending(&sclp_con_timer))
del_timer(&sclp_con_timer);
while (sclp_con_buffer_count > 0) {
spin_unlock_irqrestore(&sclp_con_lock, flags);
sclp_sync_wait();
spin_lock_irqsave(&sclp_con_lock, flags);
}
spin_unlock_irqrestore(&sclp_con_lock, flags);
}
/*
* used to register the SCLP console to the kernel and to
* give printk necessary information
*/
static struct console sclp_console =
{
.name = sclp_console_name,
.write = sclp_console_write,
.device = sclp_console_device,
.unblank = sclp_console_unblank,
.flags = CON_PRINTBUFFER,
.index = 0 /* ttyS0 */
};
/*
* called by console_init() in drivers/char/tty_io.c at boot-time.
*/
static int __init
sclp_console_init(void)
{
void *page;
int i;
int rc;
if (!CONSOLE_IS_SCLP)
return 0;
rc = sclp_rw_init();
if (rc)
return rc;
/* Allocate pages for output buffering */
INIT_LIST_HEAD(&sclp_con_pages);
for (i = 0; i < MAX_CONSOLE_PAGES; i++) {
page = alloc_bootmem_low_pages(PAGE_SIZE);
if (page == NULL)
return -ENOMEM;
list_add_tail((struct list_head *) page, &sclp_con_pages);
}
INIT_LIST_HEAD(&sclp_con_outqueue);
spin_lock_init(&sclp_con_lock);
sclp_con_buffer_count = 0;
sclp_conbuf = NULL;
init_timer(&sclp_con_timer);
/* Set output format */
if (MACHINE_IS_VM)
/*
* save 4 characters for the CPU number
* written at start of each line by VM/CP
*/
sclp_con_columns = 76;
else
sclp_con_columns = 80;
sclp_con_width_htab = 8;
/* enable printk-access to this driver */
register_console(&sclp_console);
return 0;
}
console_initcall(sclp_console_init);

View file

@ -0,0 +1,254 @@
/*
* Author: Martin Peschke <mpeschke@de.ibm.com>
* Copyright (C) 2001 IBM Entwicklung GmbH, IBM Corporation
*
* SCLP Control-Program Identification.
*/
#include <linux/config.h>
#include <linux/version.h>
#include <linux/kmod.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <linux/string.h>
#include <linux/err.h>
#include <linux/slab.h>
#include <asm/ebcdic.h>
#include <asm/semaphore.h>
#include "sclp.h"
#include "sclp_rw.h"
#define CPI_LENGTH_SYSTEM_TYPE 8
#define CPI_LENGTH_SYSTEM_NAME 8
#define CPI_LENGTH_SYSPLEX_NAME 8
struct cpi_evbuf {
struct evbuf_header header;
u8 id_format;
u8 reserved0;
u8 system_type[CPI_LENGTH_SYSTEM_TYPE];
u64 reserved1;
u8 system_name[CPI_LENGTH_SYSTEM_NAME];
u64 reserved2;
u64 system_level;
u64 reserved3;
u8 sysplex_name[CPI_LENGTH_SYSPLEX_NAME];
u8 reserved4[16];
} __attribute__((packed));
struct cpi_sccb {
struct sccb_header header;
struct cpi_evbuf cpi_evbuf;
} __attribute__((packed));
/* Event type structure for write message and write priority message */
static struct sclp_register sclp_cpi_event =
{
.send_mask = EvTyp_CtlProgIdent_Mask
};
MODULE_AUTHOR(
"Martin Peschke, IBM Deutschland Entwicklung GmbH "
"<[email protected]>");
MODULE_DESCRIPTION(
"identify this operating system instance to the S/390 "
"or zSeries hardware");
static char *system_name = NULL;
module_param(system_name, charp, 0);
MODULE_PARM_DESC(system_name, "e.g. hostname - max. 8 characters");
static char *sysplex_name = NULL;
#ifdef ALLOW_SYSPLEX_NAME
module_param(sysplex_name, charp, 0);
MODULE_PARM_DESC(sysplex_name, "if applicable - max. 8 characters");
#endif
/* use default value for this field (as well as for system level) */
static char *system_type = "LINUX";
static int
cpi_check_parms(void)
{
/* reject if no system type specified */
if (!system_type) {
printk("cpi: bug: no system type specified\n");
return -EINVAL;
}
/* reject if system type larger than 8 characters */
if (strlen(system_type) > CPI_LENGTH_SYSTEM_NAME) {
printk("cpi: bug: system type has length of %li characters - "
"only %i characters supported\n",
strlen(system_type), CPI_LENGTH_SYSTEM_TYPE);
return -EINVAL;
}
/* reject if no system name specified */
if (!system_name) {
printk("cpi: no system name specified\n");
return -EINVAL;
}
/* reject if system name larger than 8 characters */
if (strlen(system_name) > CPI_LENGTH_SYSTEM_NAME) {
printk("cpi: system name has length of %li characters - "
"only %i characters supported\n",
strlen(system_name), CPI_LENGTH_SYSTEM_NAME);
return -EINVAL;
}
/* reject if specified sysplex name larger than 8 characters */
if (sysplex_name && strlen(sysplex_name) > CPI_LENGTH_SYSPLEX_NAME) {
printk("cpi: sysplex name has length of %li characters"
" - only %i characters supported\n",
strlen(sysplex_name), CPI_LENGTH_SYSPLEX_NAME);
return -EINVAL;
}
return 0;
}
static void
cpi_callback(struct sclp_req *req, void *data)
{
struct semaphore *sem;
sem = (struct semaphore *) data;
up(sem);
}
static struct sclp_req *
cpi_prepare_req(void)
{
struct sclp_req *req;
struct cpi_sccb *sccb;
struct cpi_evbuf *evb;
req = (struct sclp_req *) kmalloc(sizeof(struct sclp_req), GFP_KERNEL);
if (req == NULL)
return ERR_PTR(-ENOMEM);
sccb = (struct cpi_sccb *) __get_free_page(GFP_KERNEL | GFP_DMA);
if (sccb == NULL) {
kfree(req);
return ERR_PTR(-ENOMEM);
}
memset(sccb, 0, sizeof(struct cpi_sccb));
/* setup SCCB for Control-Program Identification */
sccb->header.length = sizeof(struct cpi_sccb);
sccb->cpi_evbuf.header.length = sizeof(struct cpi_evbuf);
sccb->cpi_evbuf.header.type = 0x0B;
evb = &sccb->cpi_evbuf;
/* set system type */
memset(evb->system_type, ' ', CPI_LENGTH_SYSTEM_TYPE);
memcpy(evb->system_type, system_type, strlen(system_type));
sclp_ascebc_str(evb->system_type, CPI_LENGTH_SYSTEM_TYPE);
EBC_TOUPPER(evb->system_type, CPI_LENGTH_SYSTEM_TYPE);
/* set system name */
memset(evb->system_name, ' ', CPI_LENGTH_SYSTEM_NAME);
memcpy(evb->system_name, system_name, strlen(system_name));
sclp_ascebc_str(evb->system_name, CPI_LENGTH_SYSTEM_NAME);
EBC_TOUPPER(evb->system_name, CPI_LENGTH_SYSTEM_NAME);
/* set sytem level */
evb->system_level = LINUX_VERSION_CODE;
/* set sysplex name */
if (sysplex_name) {
memset(evb->sysplex_name, ' ', CPI_LENGTH_SYSPLEX_NAME);
memcpy(evb->sysplex_name, sysplex_name, strlen(sysplex_name));
sclp_ascebc_str(evb->sysplex_name, CPI_LENGTH_SYSPLEX_NAME);
EBC_TOUPPER(evb->sysplex_name, CPI_LENGTH_SYSPLEX_NAME);
}
/* prepare request data structure presented to SCLP driver */
req->command = SCLP_CMDW_WRITEDATA;
req->sccb = sccb;
req->status = SCLP_REQ_FILLED;
req->callback = cpi_callback;
return req;
}
static void
cpi_free_req(struct sclp_req *req)
{
free_page((unsigned long) req->sccb);
kfree(req);
}
static int __init
cpi_module_init(void)
{
struct semaphore sem;
struct sclp_req *req;
int rc;
rc = cpi_check_parms();
if (rc)
return rc;
rc = sclp_register(&sclp_cpi_event);
if (rc) {
/* could not register sclp event. Die. */
printk(KERN_WARNING "cpi: could not register to hardware "
"console.\n");
return -EINVAL;
}
if (!(sclp_cpi_event.sclp_send_mask & EvTyp_CtlProgIdent_Mask)) {
printk(KERN_WARNING "cpi: no control program identification "
"support\n");
sclp_unregister(&sclp_cpi_event);
return -ENOTSUPP;
}
req = cpi_prepare_req();
if (IS_ERR(req)) {
printk(KERN_WARNING "cpi: couldn't allocate request\n");
sclp_unregister(&sclp_cpi_event);
return PTR_ERR(req);
}
/* Prepare semaphore */
sema_init(&sem, 0);
req->callback_data = &sem;
/* Add request to sclp queue */
rc = sclp_add_request(req);
if (rc) {
printk(KERN_WARNING "cpi: could not start request\n");
cpi_free_req(req);
sclp_unregister(&sclp_cpi_event);
return rc;
}
/* make "insmod" sleep until callback arrives */
down(&sem);
rc = ((struct cpi_sccb *) req->sccb)->header.response_code;
if (rc != 0x0020) {
printk(KERN_WARNING "cpi: failed with response code 0x%x\n",
rc);
rc = -ECOMM;
} else
rc = 0;
cpi_free_req(req);
sclp_unregister(&sclp_cpi_event);
return rc;
}
static void __exit cpi_module_exit(void)
{
}
/* declare driver module init/cleanup functions */
module_init(cpi_module_init);
module_exit(cpi_module_exit);

View file

@ -0,0 +1,99 @@
/*
* drivers/s390/char/sclp_quiesce.c
* signal quiesce handler
*
* (C) Copyright IBM Corp. 1999,2004
* Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
* Peter Oberparleiter <peter.oberparleiter@de.ibm.com>
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/cpumask.h>
#include <linux/smp.h>
#include <linux/init.h>
#include <asm/atomic.h>
#include <asm/ptrace.h>
#include <asm/sigp.h>
#include "sclp.h"
#ifdef CONFIG_SMP
/* Signal completion of shutdown process. All CPUs except the first to enter
* this function: go to stopped state. First CPU: wait until all other
* CPUs are in stopped or check stop state. Afterwards, load special PSW
* to indicate completion. */
static void
do_load_quiesce_psw(void * __unused)
{
static atomic_t cpuid = ATOMIC_INIT(-1);
psw_t quiesce_psw;
int cpu;
if (atomic_compare_and_swap(-1, smp_processor_id(), &cpuid))
signal_processor(smp_processor_id(), sigp_stop);
/* Wait for all other cpus to enter stopped state */
for_each_online_cpu(cpu) {
if (cpu == smp_processor_id())
continue;
while(!smp_cpu_not_running(cpu))
cpu_relax();
}
/* Quiesce the last cpu with the special psw */
quiesce_psw.mask = PSW_BASE_BITS | PSW_MASK_WAIT;
quiesce_psw.addr = 0xfff;
__load_psw(quiesce_psw);
}
/* Shutdown handler. Perform shutdown function on all CPUs. */
static void
do_machine_quiesce(void)
{
on_each_cpu(do_load_quiesce_psw, NULL, 0, 0);
}
#else
/* Shutdown handler. Signal completion of shutdown by loading special PSW. */
static void
do_machine_quiesce(void)
{
psw_t quiesce_psw;
quiesce_psw.mask = PSW_BASE_BITS | PSW_MASK_WAIT;
quiesce_psw.addr = 0xfff;
__load_psw(quiesce_psw);
}
#endif
extern void ctrl_alt_del(void);
/* Handler for quiesce event. Start shutdown procedure. */
static void
sclp_quiesce_handler(struct evbuf_header *evbuf)
{
_machine_restart = (void *) do_machine_quiesce;
_machine_halt = do_machine_quiesce;
_machine_power_off = do_machine_quiesce;
ctrl_alt_del();
}
static struct sclp_register sclp_quiesce_event = {
.receive_mask = EvTyp_SigQuiesce_Mask,
.receiver_fn = sclp_quiesce_handler
};
/* Initialize quiesce driver. */
static int __init
sclp_quiesce_init(void)
{
int rc;
rc = sclp_register(&sclp_quiesce_event);
if (rc)
printk(KERN_WARNING "sclp: could not register quiesce handler "
"(rc=%d)\n", rc);
return rc;
}
module_init(sclp_quiesce_init);

471
drivers/s390/char/sclp_rw.c Normal file
View file

@ -0,0 +1,471 @@
/*
* drivers/s390/char/sclp_rw.c
* driver: reading from and writing to system console on S/390 via SCLP
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#include <linux/config.h>
#include <linux/kmod.h>
#include <linux/types.h>
#include <linux/err.h>
#include <linux/string.h>
#include <linux/spinlock.h>
#include <linux/ctype.h>
#include <asm/uaccess.h>
#include "sclp.h"
#include "sclp_rw.h"
#define SCLP_RW_PRINT_HEADER "sclp low level driver: "
/*
* The room for the SCCB (only for writing) is not equal to a pages size
* (as it is specified as the maximum size in the the SCLP ducumentation)
* because of the additional data structure described above.
*/
#define MAX_SCCB_ROOM (PAGE_SIZE - sizeof(struct sclp_buffer))
/* Event type structure for write message and write priority message */
static struct sclp_register sclp_rw_event = {
.send_mask = EvTyp_Msg_Mask | EvTyp_PMsgCmd_Mask
};
/*
* Setup a sclp write buffer. Gets a page as input (4K) and returns
* a pointer to a struct sclp_buffer structure that is located at the
* end of the input page. This reduces the buffer space by a few
* bytes but simplifies things.
*/
struct sclp_buffer *
sclp_make_buffer(void *page, unsigned short columns, unsigned short htab)
{
struct sclp_buffer *buffer;
struct write_sccb *sccb;
sccb = (struct write_sccb *) page;
/*
* We keep the struct sclp_buffer structure at the end
* of the sccb page.
*/
buffer = ((struct sclp_buffer *) ((addr_t) sccb + PAGE_SIZE)) - 1;
buffer->sccb = sccb;
buffer->retry_count = 0;
buffer->mto_number = 0;
buffer->mto_char_sum = 0;
buffer->current_line = NULL;
buffer->current_length = 0;
buffer->columns = columns;
buffer->htab = htab;
/* initialize sccb */
memset(sccb, 0, sizeof(struct write_sccb));
sccb->header.length = sizeof(struct write_sccb);
sccb->msg_buf.header.length = sizeof(struct msg_buf);
sccb->msg_buf.header.type = EvTyp_Msg;
sccb->msg_buf.mdb.header.length = sizeof(struct mdb);
sccb->msg_buf.mdb.header.type = 1;
sccb->msg_buf.mdb.header.tag = 0xD4C4C240; /* ebcdic "MDB " */
sccb->msg_buf.mdb.header.revision_code = 1;
sccb->msg_buf.mdb.go.length = sizeof(struct go);
sccb->msg_buf.mdb.go.type = 1;
return buffer;
}
/*
* Return a pointer to the orignal page that has been used to create
* the buffer.
*/
void *
sclp_unmake_buffer(struct sclp_buffer *buffer)
{
return buffer->sccb;
}
/*
* Initialize a new Message Text Object (MTO) at the end of the provided buffer
* with enough room for max_len characters. Return 0 on success.
*/
static int
sclp_initialize_mto(struct sclp_buffer *buffer, int max_len)
{
struct write_sccb *sccb;
struct mto *mto;
int mto_size;
/* max size of new Message Text Object including message text */
mto_size = sizeof(struct mto) + max_len;
/* check if current buffer sccb can contain the mto */
sccb = buffer->sccb;
if ((MAX_SCCB_ROOM - sccb->header.length) < mto_size)
return -ENOMEM;
/* find address of new message text object */
mto = (struct mto *)(((addr_t) sccb) + sccb->header.length);
/*
* fill the new Message-Text Object,
* starting behind the former last byte of the SCCB
*/
memset(mto, 0, sizeof(struct mto));
mto->length = sizeof(struct mto);
mto->type = 4; /* message text object */
mto->line_type_flags = LnTpFlgs_EndText; /* end text */
/* set pointer to first byte after struct mto. */
buffer->current_line = (char *) (mto + 1);
buffer->current_length = 0;
return 0;
}
/*
* Finalize MTO initialized by sclp_initialize_mto(), updating the sizes of
* MTO, enclosing MDB, event buffer and SCCB.
*/
static void
sclp_finalize_mto(struct sclp_buffer *buffer)
{
struct write_sccb *sccb;
struct mto *mto;
int str_len, mto_size;
str_len = buffer->current_length;
buffer->current_line = NULL;
buffer->current_length = 0;
/* real size of new Message Text Object including message text */
mto_size = sizeof(struct mto) + str_len;
/* find address of new message text object */
sccb = buffer->sccb;
mto = (struct mto *)(((addr_t) sccb) + sccb->header.length);
/* set size of message text object */
mto->length = mto_size;
/*
* update values of sizes
* (SCCB, Event(Message) Buffer, Message Data Block)
*/
sccb->header.length += mto_size;
sccb->msg_buf.header.length += mto_size;
sccb->msg_buf.mdb.header.length += mto_size;
/*
* count number of buffered messages (= number of Message Text
* Objects) and number of buffered characters
* for the SCCB currently used for buffering and at all
*/
buffer->mto_number++;
buffer->mto_char_sum += str_len;
}
/*
* processing of a message including escape characters,
* returns number of characters written to the output sccb
* ("processed" means that is not guaranteed that the character have already
* been sent to the SCLP but that it will be done at least next time the SCLP
* is not busy)
*/
int
sclp_write(struct sclp_buffer *buffer, const unsigned char *msg, int count)
{
int spaces, i_msg;
int rc;
/*
* parse msg for escape sequences (\t,\v ...) and put formated
* msg into an mto (created by sclp_initialize_mto).
*
* We have to do this work ourselfs because there is no support for
* these characters on the native machine and only partial support
* under VM (Why does VM interpret \n but the native machine doesn't ?)
*
* Depending on i/o-control setting the message is always written
* immediately or we wait for a final new line maybe coming with the
* next message. Besides we avoid a buffer overrun by writing its
* content.
*
* RESTRICTIONS:
*
* \r and \b work within one line because we are not able to modify
* previous output that have already been accepted by the SCLP.
*
* \t combined with following \r is not correctly represented because
* \t is expanded to some spaces but \r does not know about a
* previous \t and decreases the current position by one column.
* This is in order to a slim and quick implementation.
*/
for (i_msg = 0; i_msg < count; i_msg++) {
switch (msg[i_msg]) {
case '\n': /* new line, line feed (ASCII) */
/* check if new mto needs to be created */
if (buffer->current_line == NULL) {
rc = sclp_initialize_mto(buffer, 0);
if (rc)
return i_msg;
}
sclp_finalize_mto(buffer);
break;
case '\a': /* bell, one for several times */
/* set SCLP sound alarm bit in General Object */
buffer->sccb->msg_buf.mdb.go.general_msg_flags |=
GnrlMsgFlgs_SndAlrm;
break;
case '\t': /* horizontal tabulator */
/* check if new mto needs to be created */
if (buffer->current_line == NULL) {
rc = sclp_initialize_mto(buffer,
buffer->columns);
if (rc)
return i_msg;
}
/* "go to (next htab-boundary + 1, same line)" */
do {
if (buffer->current_length >= buffer->columns)
break;
/* ok, add a blank */
*buffer->current_line++ = 0x40;
buffer->current_length++;
} while (buffer->current_length % buffer->htab);
break;
case '\f': /* form feed */
case '\v': /* vertical tabulator */
/* "go to (actual column, actual line + 1)" */
/* = new line, leading spaces */
if (buffer->current_line != NULL) {
spaces = buffer->current_length;
sclp_finalize_mto(buffer);
rc = sclp_initialize_mto(buffer,
buffer->columns);
if (rc)
return i_msg;
memset(buffer->current_line, 0x40, spaces);
buffer->current_line += spaces;
buffer->current_length = spaces;
} else {
/* one an empty line this is the same as \n */
rc = sclp_initialize_mto(buffer,
buffer->columns);
if (rc)
return i_msg;
sclp_finalize_mto(buffer);
}
break;
case '\b': /* backspace */
/* "go to (actual column - 1, actual line)" */
/* decrement counter indicating position, */
/* do not remove last character */
if (buffer->current_line != NULL &&
buffer->current_length > 0) {
buffer->current_length--;
buffer->current_line--;
}
break;
case 0x00: /* end of string */
/* transfer current line to SCCB */
if (buffer->current_line != NULL)
sclp_finalize_mto(buffer);
/* skip the rest of the message including the 0 byte */
i_msg = count - 1;
break;
default: /* no escape character */
/* do not output unprintable characters */
if (!isprint(msg[i_msg]))
break;
/* check if new mto needs to be created */
if (buffer->current_line == NULL) {
rc = sclp_initialize_mto(buffer,
buffer->columns);
if (rc)
return i_msg;
}
*buffer->current_line++ = sclp_ascebc(msg[i_msg]);
buffer->current_length++;
break;
}
/* check if current mto is full */
if (buffer->current_line != NULL &&
buffer->current_length >= buffer->columns)
sclp_finalize_mto(buffer);
}
/* return number of processed characters */
return i_msg;
}
/*
* Return the number of free bytes in the sccb
*/
int
sclp_buffer_space(struct sclp_buffer *buffer)
{
int count;
count = MAX_SCCB_ROOM - buffer->sccb->header.length;
if (buffer->current_line != NULL)
count -= sizeof(struct mto) + buffer->current_length;
return count;
}
/*
* Return number of characters in buffer
*/
int
sclp_chars_in_buffer(struct sclp_buffer *buffer)
{
int count;
count = buffer->mto_char_sum;
if (buffer->current_line != NULL)
count += buffer->current_length;
return count;
}
/*
* sets or provides some values that influence the drivers behaviour
*/
void
sclp_set_columns(struct sclp_buffer *buffer, unsigned short columns)
{
buffer->columns = columns;
if (buffer->current_line != NULL &&
buffer->current_length > buffer->columns)
sclp_finalize_mto(buffer);
}
void
sclp_set_htab(struct sclp_buffer *buffer, unsigned short htab)
{
buffer->htab = htab;
}
/*
* called by sclp_console_init and/or sclp_tty_init
*/
int
sclp_rw_init(void)
{
static int init_done = 0;
int rc;
if (init_done)
return 0;
rc = sclp_register(&sclp_rw_event);
if (rc == 0)
init_done = 1;
return rc;
}
#define SCLP_BUFFER_MAX_RETRY 1
/*
* second half of Write Event Data-function that has to be done after
* interruption indicating completion of Service Call.
*/
static void
sclp_writedata_callback(struct sclp_req *request, void *data)
{
int rc;
struct sclp_buffer *buffer;
struct write_sccb *sccb;
buffer = (struct sclp_buffer *) data;
sccb = buffer->sccb;
if (request->status == SCLP_REQ_FAILED) {
if (buffer->callback != NULL)
buffer->callback(buffer, -EIO);
return;
}
/* check SCLP response code and choose suitable action */
switch (sccb->header.response_code) {
case 0x0020 :
/* Normal completion, buffer processed, message(s) sent */
rc = 0;
break;
case 0x0340: /* Contained SCLP equipment check */
if (++buffer->retry_count > SCLP_BUFFER_MAX_RETRY) {
rc = -EIO;
break;
}
/* remove processed buffers and requeue rest */
if (sclp_remove_processed((struct sccb_header *) sccb) > 0) {
/* not all buffers were processed */
sccb->header.response_code = 0x0000;
buffer->request.status = SCLP_REQ_FILLED;
rc = sclp_add_request(request);
if (rc == 0)
return;
} else
rc = 0;
break;
case 0x0040: /* SCLP equipment check */
case 0x05f0: /* Target resource in improper state */
if (++buffer->retry_count > SCLP_BUFFER_MAX_RETRY) {
rc = -EIO;
break;
}
/* retry request */
sccb->header.response_code = 0x0000;
buffer->request.status = SCLP_REQ_FILLED;
rc = sclp_add_request(request);
if (rc == 0)
return;
break;
default:
if (sccb->header.response_code == 0x71f0)
rc = -ENOMEM;
else
rc = -EINVAL;
break;
}
if (buffer->callback != NULL)
buffer->callback(buffer, rc);
}
/*
* Setup the request structure in the struct sclp_buffer to do SCLP Write
* Event Data and pass the request to the core SCLP loop. Return zero on
* success, non-zero otherwise.
*/
int
sclp_emit_buffer(struct sclp_buffer *buffer,
void (*callback)(struct sclp_buffer *, int))
{
struct write_sccb *sccb;
/* add current line if there is one */
if (buffer->current_line != NULL)
sclp_finalize_mto(buffer);
/* Are there messages in the output buffer ? */
if (buffer->mto_number == 0)
return -EIO;
sccb = buffer->sccb;
if (sclp_rw_event.sclp_send_mask & EvTyp_Msg_Mask)
/* Use normal write message */
sccb->msg_buf.header.type = EvTyp_Msg;
else if (sclp_rw_event.sclp_send_mask & EvTyp_PMsgCmd_Mask)
/* Use write priority message */
sccb->msg_buf.header.type = EvTyp_PMsgCmd;
else
return -ENOSYS;
buffer->request.command = SCLP_CMDW_WRITEDATA;
buffer->request.status = SCLP_REQ_FILLED;
buffer->request.callback = sclp_writedata_callback;
buffer->request.callback_data = buffer;
buffer->request.sccb = sccb;
buffer->callback = callback;
return sclp_add_request(&buffer->request);
}

View file

@ -0,0 +1,96 @@
/*
* drivers/s390/char/sclp_rw.h
* interface to the SCLP-read/write driver
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#ifndef __SCLP_RW_H__
#define __SCLP_RW_H__
#include <linux/list.h>
struct mto {
u16 length;
u16 type;
u16 line_type_flags;
u8 alarm_control;
u8 _reserved[3];
} __attribute__((packed));
struct go {
u16 length;
u16 type;
u32 domid;
u8 hhmmss_time[8];
u8 th_time[3];
u8 reserved_0;
u8 dddyyyy_date[7];
u8 _reserved_1;
u16 general_msg_flags;
u8 _reserved_2[10];
u8 originating_system_name[8];
u8 job_guest_name[8];
} __attribute__((packed));
struct mdb_header {
u16 length;
u16 type;
u32 tag;
u32 revision_code;
} __attribute__((packed));
struct mdb {
struct mdb_header header;
struct go go;
} __attribute__((packed));
struct msg_buf {
struct evbuf_header header;
struct mdb mdb;
} __attribute__((packed));
struct write_sccb {
struct sccb_header header;
struct msg_buf msg_buf;
} __attribute__((packed));
/* The number of empty mto buffers that can be contained in a single sccb. */
#define NR_EMPTY_MTO_PER_SCCB ((PAGE_SIZE - sizeof(struct sclp_buffer) - \
sizeof(struct write_sccb)) / sizeof(struct mto))
/*
* data structure for information about list of SCCBs (only for writing),
* will be located at the end of a SCCBs page
*/
struct sclp_buffer {
struct list_head list; /* list_head for sccb_info chain */
struct sclp_req request;
struct write_sccb *sccb;
char *current_line;
int current_length;
int retry_count;
/* output format settings */
unsigned short columns;
unsigned short htab;
/* statistics about this buffer */
unsigned int mto_char_sum; /* # chars in sccb */
unsigned int mto_number; /* # mtos in sccb */
/* Callback that is called after reaching final status. */
void (*callback)(struct sclp_buffer *, int);
};
int sclp_rw_init(void);
struct sclp_buffer *sclp_make_buffer(void *, unsigned short, unsigned short);
void *sclp_unmake_buffer(struct sclp_buffer *);
int sclp_buffer_space(struct sclp_buffer *);
int sclp_write(struct sclp_buffer *buffer, const unsigned char *, int);
int sclp_emit_buffer(struct sclp_buffer *,void (*)(struct sclp_buffer *,int));
void sclp_set_columns(struct sclp_buffer *, unsigned short);
void sclp_set_htab(struct sclp_buffer *, unsigned short);
int sclp_chars_in_buffer(struct sclp_buffer *);
#endif /* __SCLP_RW_H__ */

View file

@ -0,0 +1,813 @@
/*
* drivers/s390/char/sclp_tty.c
* SCLP line mode terminal driver.
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/kmod.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/sched.h>
#include <linux/wait.h>
#include <linux/slab.h>
#include <linux/err.h>
#include <linux/init.h>
#include <linux/interrupt.h>
#include <asm/uaccess.h>
#include "ctrlchar.h"
#include "sclp.h"
#include "sclp_rw.h"
#include "sclp_tty.h"
#define SCLP_TTY_PRINT_HEADER "sclp tty driver: "
/*
* size of a buffer that collects single characters coming in
* via sclp_tty_put_char()
*/
#define SCLP_TTY_BUF_SIZE 512
/*
* There is exactly one SCLP terminal, so we can keep things simple
* and allocate all variables statically.
*/
/* Lock to guard over changes to global variables. */
static spinlock_t sclp_tty_lock;
/* List of free pages that can be used for console output buffering. */
static struct list_head sclp_tty_pages;
/* List of full struct sclp_buffer structures ready for output. */
static struct list_head sclp_tty_outqueue;
/* Counter how many buffers are emitted. */
static int sclp_tty_buffer_count;
/* Pointer to current console buffer. */
static struct sclp_buffer *sclp_ttybuf;
/* Timer for delayed output of console messages. */
static struct timer_list sclp_tty_timer;
/* Waitqueue to wait for buffers to get empty. */
static wait_queue_head_t sclp_tty_waitq;
static struct tty_struct *sclp_tty;
static unsigned char sclp_tty_chars[SCLP_TTY_BUF_SIZE];
static unsigned short int sclp_tty_chars_count;
struct tty_driver *sclp_tty_driver;
extern struct termios tty_std_termios;
static struct sclp_ioctls sclp_ioctls;
static struct sclp_ioctls sclp_ioctls_init =
{
8, /* 1 hor. tab. = 8 spaces */
0, /* no echo of input by this driver */
80, /* 80 characters/line */
1, /* write after 1/10 s without final new line */
MAX_KMEM_PAGES, /* quick fix: avoid __alloc_pages */
MAX_KMEM_PAGES, /* take 32/64 pages from kernel memory, */
0, /* do not convert to lower case */
0x6c /* to seprate upper and lower case */
/* ('%' in EBCDIC) */
};
/* This routine is called whenever we try to open a SCLP terminal. */
static int
sclp_tty_open(struct tty_struct *tty, struct file *filp)
{
sclp_tty = tty;
tty->driver_data = NULL;
tty->low_latency = 0;
return 0;
}
/* This routine is called when the SCLP terminal is closed. */
static void
sclp_tty_close(struct tty_struct *tty, struct file *filp)
{
if (tty->count > 1)
return;
sclp_tty = NULL;
}
/* execute commands to control the i/o behaviour of the SCLP tty at runtime */
static int
sclp_tty_ioctl(struct tty_struct *tty, struct file * file,
unsigned int cmd, unsigned long arg)
{
unsigned long flags;
unsigned int obuf;
int check;
int rc;
if (tty->flags & (1 << TTY_IO_ERROR))
return -EIO;
rc = 0;
check = 0;
switch (cmd) {
case TIOCSCLPSHTAB:
/* set width of horizontal tab */
if (get_user(sclp_ioctls.htab, (unsigned short __user *) arg))
rc = -EFAULT;
else
check = 1;
break;
case TIOCSCLPGHTAB:
/* get width of horizontal tab */
if (put_user(sclp_ioctls.htab, (unsigned short __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPSECHO:
/* enable/disable echo of input */
if (get_user(sclp_ioctls.echo, (unsigned char __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPGECHO:
/* Is echo of input enabled ? */
if (put_user(sclp_ioctls.echo, (unsigned char __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPSCOLS:
/* set number of columns for output */
if (get_user(sclp_ioctls.columns, (unsigned short __user *) arg))
rc = -EFAULT;
else
check = 1;
break;
case TIOCSCLPGCOLS:
/* get number of columns for output */
if (put_user(sclp_ioctls.columns, (unsigned short __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPSNL:
/* enable/disable writing without final new line character */
if (get_user(sclp_ioctls.final_nl, (signed char __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPGNL:
/* Is writing without final new line character enabled ? */
if (put_user(sclp_ioctls.final_nl, (signed char __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPSOBUF:
/*
* set the maximum buffers size for output, will be rounded
* up to next 4kB boundary and stored as number of SCCBs
* (4kB Buffers) limitation: 256 x 4kB
*/
if (get_user(obuf, (unsigned int __user *) arg) == 0) {
if (obuf & 0xFFF)
sclp_ioctls.max_sccb = (obuf >> 12) + 1;
else
sclp_ioctls.max_sccb = (obuf >> 12);
} else
rc = -EFAULT;
break;
case TIOCSCLPGOBUF:
/* get the maximum buffers size for output */
obuf = sclp_ioctls.max_sccb << 12;
if (put_user(obuf, (unsigned int __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPGKBUF:
/* get the number of buffers got from kernel at startup */
if (put_user(sclp_ioctls.kmem_sccb, (unsigned short __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPSCASE:
/* enable/disable conversion from upper to lower case */
if (get_user(sclp_ioctls.tolower, (unsigned char __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPGCASE:
/* Is conversion from upper to lower case of input enabled? */
if (put_user(sclp_ioctls.tolower, (unsigned char __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPSDELIM:
/*
* set special character used for separating upper and
* lower case, 0x00 disables this feature
*/
if (get_user(sclp_ioctls.delim, (unsigned char __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPGDELIM:
/*
* get special character used for separating upper and
* lower case, 0x00 disables this feature
*/
if (put_user(sclp_ioctls.delim, (unsigned char __user *) arg))
rc = -EFAULT;
break;
case TIOCSCLPSINIT:
/* set initial (default) sclp ioctls */
sclp_ioctls = sclp_ioctls_init;
check = 1;
break;
default:
rc = -ENOIOCTLCMD;
break;
}
if (check) {
spin_lock_irqsave(&sclp_tty_lock, flags);
if (sclp_ttybuf != NULL) {
sclp_set_htab(sclp_ttybuf, sclp_ioctls.htab);
sclp_set_columns(sclp_ttybuf, sclp_ioctls.columns);
}
spin_unlock_irqrestore(&sclp_tty_lock, flags);
}
return rc;
}
/*
* This routine returns the numbers of characters the tty driver
* will accept for queuing to be written. This number is subject
* to change as output buffers get emptied, or if the output flow
* control is acted. This is not an exact number because not every
* character needs the same space in the sccb. The worst case is
* a string of newlines. Every newlines creates a new mto which
* needs 8 bytes.
*/
static int
sclp_tty_write_room (struct tty_struct *tty)
{
unsigned long flags;
struct list_head *l;
int count;
spin_lock_irqsave(&sclp_tty_lock, flags);
count = 0;
if (sclp_ttybuf != NULL)
count = sclp_buffer_space(sclp_ttybuf) / sizeof(struct mto);
list_for_each(l, &sclp_tty_pages)
count += NR_EMPTY_MTO_PER_SCCB;
spin_unlock_irqrestore(&sclp_tty_lock, flags);
return count;
}
static void
sclp_ttybuf_callback(struct sclp_buffer *buffer, int rc)
{
unsigned long flags;
void *page;
do {
page = sclp_unmake_buffer(buffer);
spin_lock_irqsave(&sclp_tty_lock, flags);
/* Remove buffer from outqueue */
list_del(&buffer->list);
sclp_tty_buffer_count--;
list_add_tail((struct list_head *) page, &sclp_tty_pages);
/* Check if there is a pending buffer on the out queue. */
buffer = NULL;
if (!list_empty(&sclp_tty_outqueue))
buffer = list_entry(sclp_tty_outqueue.next,
struct sclp_buffer, list);
spin_unlock_irqrestore(&sclp_tty_lock, flags);
} while (buffer && sclp_emit_buffer(buffer, sclp_ttybuf_callback));
wake_up(&sclp_tty_waitq);
/* check if the tty needs a wake up call */
if (sclp_tty != NULL) {
tty_wakeup(sclp_tty);
}
}
static inline void
__sclp_ttybuf_emit(struct sclp_buffer *buffer)
{
unsigned long flags;
int count;
int rc;
spin_lock_irqsave(&sclp_tty_lock, flags);
list_add_tail(&buffer->list, &sclp_tty_outqueue);
count = sclp_tty_buffer_count++;
spin_unlock_irqrestore(&sclp_tty_lock, flags);
if (count)
return;
rc = sclp_emit_buffer(buffer, sclp_ttybuf_callback);
if (rc)
sclp_ttybuf_callback(buffer, rc);
}
/*
* When this routine is called from the timer then we flush the
* temporary write buffer.
*/
static void
sclp_tty_timeout(unsigned long data)
{
unsigned long flags;
struct sclp_buffer *buf;
spin_lock_irqsave(&sclp_tty_lock, flags);
buf = sclp_ttybuf;
sclp_ttybuf = NULL;
spin_unlock_irqrestore(&sclp_tty_lock, flags);
if (buf != NULL) {
__sclp_ttybuf_emit(buf);
}
}
/*
* Write a string to the sclp tty.
*/
static void
sclp_tty_write_string(const unsigned char *str, int count)
{
unsigned long flags;
void *page;
int written;
struct sclp_buffer *buf;
if (count <= 0)
return;
spin_lock_irqsave(&sclp_tty_lock, flags);
do {
/* Create a sclp output buffer if none exists yet */
if (sclp_ttybuf == NULL) {
while (list_empty(&sclp_tty_pages)) {
spin_unlock_irqrestore(&sclp_tty_lock, flags);
if (in_interrupt())
sclp_sync_wait();
else
wait_event(sclp_tty_waitq,
!list_empty(&sclp_tty_pages));
spin_lock_irqsave(&sclp_tty_lock, flags);
}
page = sclp_tty_pages.next;
list_del((struct list_head *) page);
sclp_ttybuf = sclp_make_buffer(page,
sclp_ioctls.columns,
sclp_ioctls.htab);
}
/* try to write the string to the current output buffer */
written = sclp_write(sclp_ttybuf, str, count);
if (written == count)
break;
/*
* Not all characters could be written to the current
* output buffer. Emit the buffer, create a new buffer
* and then output the rest of the string.
*/
buf = sclp_ttybuf;
sclp_ttybuf = NULL;
spin_unlock_irqrestore(&sclp_tty_lock, flags);
__sclp_ttybuf_emit(buf);
spin_lock_irqsave(&sclp_tty_lock, flags);
str += written;
count -= written;
} while (count > 0);
/* Setup timer to output current console buffer after 1/10 second */
if (sclp_ioctls.final_nl) {
if (sclp_ttybuf != NULL &&
sclp_chars_in_buffer(sclp_ttybuf) != 0 &&
!timer_pending(&sclp_tty_timer)) {
init_timer(&sclp_tty_timer);
sclp_tty_timer.function = sclp_tty_timeout;
sclp_tty_timer.data = 0UL;
sclp_tty_timer.expires = jiffies + HZ/10;
add_timer(&sclp_tty_timer);
}
} else {
if (sclp_ttybuf != NULL &&
sclp_chars_in_buffer(sclp_ttybuf) != 0) {
buf = sclp_ttybuf;
sclp_ttybuf = NULL;
spin_unlock_irqrestore(&sclp_tty_lock, flags);
__sclp_ttybuf_emit(buf);
spin_lock_irqsave(&sclp_tty_lock, flags);
}
}
spin_unlock_irqrestore(&sclp_tty_lock, flags);
}
/*
* This routine is called by the kernel to write a series of characters to the
* tty device. The characters may come from user space or kernel space. This
* routine will return the number of characters actually accepted for writing.
*/
static int
sclp_tty_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
if (sclp_tty_chars_count > 0) {
sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
sclp_tty_chars_count = 0;
}
sclp_tty_write_string(buf, count);
return count;
}
/*
* This routine is called by the kernel to write a single character to the tty
* device. If the kernel uses this routine, it must call the flush_chars()
* routine (if defined) when it is done stuffing characters into the driver.
*
* Characters provided to sclp_tty_put_char() are buffered by the SCLP driver.
* If the given character is a '\n' the contents of the SCLP write buffer
* - including previous characters from sclp_tty_put_char() and strings from
* sclp_write() without final '\n' - will be written.
*/
static void
sclp_tty_put_char(struct tty_struct *tty, unsigned char ch)
{
sclp_tty_chars[sclp_tty_chars_count++] = ch;
if (ch == '\n' || sclp_tty_chars_count >= SCLP_TTY_BUF_SIZE) {
sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
sclp_tty_chars_count = 0;
}
}
/*
* This routine is called by the kernel after it has written a series of
* characters to the tty device using put_char().
*/
static void
sclp_tty_flush_chars(struct tty_struct *tty)
{
if (sclp_tty_chars_count > 0) {
sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
sclp_tty_chars_count = 0;
}
}
/*
* This routine returns the number of characters in the write buffer of the
* SCLP driver. The provided number includes all characters that are stored
* in the SCCB (will be written next time the SCLP is not busy) as well as
* characters in the write buffer (will not be written as long as there is a
* final line feed missing).
*/
static int
sclp_tty_chars_in_buffer(struct tty_struct *tty)
{
unsigned long flags;
struct list_head *l;
struct sclp_buffer *t;
int count;
spin_lock_irqsave(&sclp_tty_lock, flags);
count = 0;
if (sclp_ttybuf != NULL)
count = sclp_chars_in_buffer(sclp_ttybuf);
list_for_each(l, &sclp_tty_outqueue) {
t = list_entry(l, struct sclp_buffer, list);
count += sclp_chars_in_buffer(t);
}
spin_unlock_irqrestore(&sclp_tty_lock, flags);
return count;
}
/*
* removes all content from buffers of low level driver
*/
static void
sclp_tty_flush_buffer(struct tty_struct *tty)
{
if (sclp_tty_chars_count > 0) {
sclp_tty_write_string(sclp_tty_chars, sclp_tty_chars_count);
sclp_tty_chars_count = 0;
}
}
/*
* push input to tty
*/
static void
sclp_tty_input(unsigned char* buf, unsigned int count)
{
unsigned int cchar;
/*
* If this tty driver is currently closed
* then throw the received input away.
*/
if (sclp_tty == NULL)
return;
cchar = ctrlchar_handle(buf, count, sclp_tty);
switch (cchar & CTRLCHAR_MASK) {
case CTRLCHAR_SYSRQ:
break;
case CTRLCHAR_CTRL:
sclp_tty->flip.count++;
*sclp_tty->flip.flag_buf_ptr++ = TTY_NORMAL;
*sclp_tty->flip.char_buf_ptr++ = cchar;
tty_flip_buffer_push(sclp_tty);
break;
case CTRLCHAR_NONE:
/* send (normal) input to line discipline */
memcpy(sclp_tty->flip.char_buf_ptr, buf, count);
if (count < 2 ||
(strncmp ((const char *) buf + count - 2, "^n", 2) &&
strncmp ((const char *) buf + count - 2, "\0252n", 2))) {
sclp_tty->flip.char_buf_ptr[count] = '\n';
count++;
} else
count -= 2;
memset(sclp_tty->flip.flag_buf_ptr, TTY_NORMAL, count);
sclp_tty->flip.char_buf_ptr += count;
sclp_tty->flip.flag_buf_ptr += count;
sclp_tty->flip.count += count;
tty_flip_buffer_push(sclp_tty);
break;
}
}
/*
* get a EBCDIC string in upper/lower case,
* find out characters in lower/upper case separated by a special character,
* modifiy original string,
* returns length of resulting string
*/
static int
sclp_switch_cases(unsigned char *buf, int count,
unsigned char delim, int tolower)
{
unsigned char *ip, *op;
int toggle;
/* initially changing case is off */
toggle = 0;
ip = op = buf;
while (count-- > 0) {
/* compare with special character */
if (*ip == delim) {
/* followed by another special character? */
if (count && ip[1] == delim) {
/*
* ... then put a single copy of the special
* character to the output string
*/
*op++ = *ip++;
count--;
} else
/*
* ... special character follower by a normal
* character toggles the case change behaviour
*/
toggle = ~toggle;
/* skip special character */
ip++;
} else
/* not the special character */
if (toggle)
/* but case switching is on */
if (tolower)
/* switch to uppercase */
*op++ = _ebc_toupper[(int) *ip++];
else
/* switch to lowercase */
*op++ = _ebc_tolower[(int) *ip++];
else
/* no case switching, copy the character */
*op++ = *ip++;
}
/* return length of reformatted string. */
return op - buf;
}
static void
sclp_get_input(unsigned char *start, unsigned char *end)
{
int count;
count = end - start;
/*
* if set in ioctl convert EBCDIC to lower case
* (modify original input in SCCB)
*/
if (sclp_ioctls.tolower)
EBC_TOLOWER(start, count);
/*
* if set in ioctl find out characters in lower or upper case
* (depends on current case) separated by a special character,
* works on EBCDIC
*/
if (sclp_ioctls.delim)
count = sclp_switch_cases(start, count,
sclp_ioctls.delim,
sclp_ioctls.tolower);
/* convert EBCDIC to ASCII (modify original input in SCCB) */
sclp_ebcasc_str(start, count);
/* if set in ioctl write operators input to console */
if (sclp_ioctls.echo)
sclp_tty_write(sclp_tty, start, count);
/* transfer input to high level driver */
sclp_tty_input(start, count);
}
static inline struct gds_vector *
find_gds_vector(struct gds_vector *start, struct gds_vector *end, u16 id)
{
struct gds_vector *vec;
for (vec = start; vec < end; vec = (void *) vec + vec->length)
if (vec->gds_id == id)
return vec;
return NULL;
}
static inline struct gds_subvector *
find_gds_subvector(struct gds_subvector *start,
struct gds_subvector *end, u8 key)
{
struct gds_subvector *subvec;
for (subvec = start; subvec < end;
subvec = (void *) subvec + subvec->length)
if (subvec->key == key)
return subvec;
return NULL;
}
static inline void
sclp_eval_selfdeftextmsg(struct gds_subvector *start,
struct gds_subvector *end)
{
struct gds_subvector *subvec;
subvec = start;
while (subvec < end) {
subvec = find_gds_subvector(subvec, end, 0x30);
if (!subvec)
break;
sclp_get_input((unsigned char *)(subvec + 1),
(unsigned char *) subvec + subvec->length);
subvec = (void *) subvec + subvec->length;
}
}
static inline void
sclp_eval_textcmd(struct gds_subvector *start,
struct gds_subvector *end)
{
struct gds_subvector *subvec;
subvec = start;
while (subvec < end) {
subvec = find_gds_subvector(subvec, end,
GDS_KEY_SelfDefTextMsg);
if (!subvec)
break;
sclp_eval_selfdeftextmsg((struct gds_subvector *)(subvec + 1),
(void *)subvec + subvec->length);
subvec = (void *) subvec + subvec->length;
}
}
static inline void
sclp_eval_cpmsu(struct gds_vector *start, struct gds_vector *end)
{
struct gds_vector *vec;
vec = start;
while (vec < end) {
vec = find_gds_vector(vec, end, GDS_ID_TextCmd);
if (!vec)
break;
sclp_eval_textcmd((struct gds_subvector *)(vec + 1),
(void *) vec + vec->length);
vec = (void *) vec + vec->length;
}
}
static inline void
sclp_eval_mdsmu(struct gds_vector *start, void *end)
{
struct gds_vector *vec;
vec = find_gds_vector(start, end, GDS_ID_CPMSU);
if (vec)
sclp_eval_cpmsu(vec + 1, (void *) vec + vec->length);
}
static void
sclp_tty_receiver(struct evbuf_header *evbuf)
{
struct gds_vector *start, *end, *vec;
start = (struct gds_vector *)(evbuf + 1);
end = (void *) evbuf + evbuf->length;
vec = find_gds_vector(start, end, GDS_ID_MDSMU);
if (vec)
sclp_eval_mdsmu(vec + 1, (void *) vec + vec->length);
}
static void
sclp_tty_state_change(struct sclp_register *reg)
{
}
static struct sclp_register sclp_input_event =
{
.receive_mask = EvTyp_OpCmd_Mask | EvTyp_PMsgCmd_Mask,
.state_change_fn = sclp_tty_state_change,
.receiver_fn = sclp_tty_receiver
};
static struct tty_operations sclp_ops = {
.open = sclp_tty_open,
.close = sclp_tty_close,
.write = sclp_tty_write,
.put_char = sclp_tty_put_char,
.flush_chars = sclp_tty_flush_chars,
.write_room = sclp_tty_write_room,
.chars_in_buffer = sclp_tty_chars_in_buffer,
.flush_buffer = sclp_tty_flush_buffer,
.ioctl = sclp_tty_ioctl,
};
int __init
sclp_tty_init(void)
{
struct tty_driver *driver;
void *page;
int i;
int rc;
if (!CONSOLE_IS_SCLP)
return 0;
driver = alloc_tty_driver(1);
if (!driver)
return -ENOMEM;
rc = sclp_rw_init();
if (rc) {
printk(KERN_ERR SCLP_TTY_PRINT_HEADER
"could not register tty - "
"sclp_rw_init returned %d\n", rc);
put_tty_driver(driver);
return rc;
}
/* Allocate pages for output buffering */
INIT_LIST_HEAD(&sclp_tty_pages);
for (i = 0; i < MAX_KMEM_PAGES; i++) {
page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
if (page == NULL) {
put_tty_driver(driver);
return -ENOMEM;
}
list_add_tail((struct list_head *) page, &sclp_tty_pages);
}
INIT_LIST_HEAD(&sclp_tty_outqueue);
spin_lock_init(&sclp_tty_lock);
init_waitqueue_head(&sclp_tty_waitq);
init_timer(&sclp_tty_timer);
sclp_ttybuf = NULL;
sclp_tty_buffer_count = 0;
if (MACHINE_IS_VM) {
/*
* save 4 characters for the CPU number
* written at start of each line by VM/CP
*/
sclp_ioctls_init.columns = 76;
/* case input lines to lowercase */
sclp_ioctls_init.tolower = 1;
}
sclp_ioctls = sclp_ioctls_init;
sclp_tty_chars_count = 0;
sclp_tty = NULL;
rc = sclp_register(&sclp_input_event);
if (rc) {
put_tty_driver(driver);
return rc;
}
driver->owner = THIS_MODULE;
driver->driver_name = "sclp_line";
driver->name = "sclp_line";
driver->major = TTY_MAJOR;
driver->minor_start = 64;
driver->type = TTY_DRIVER_TYPE_SYSTEM;
driver->subtype = SYSTEM_TYPE_TTY;
driver->init_termios = tty_std_termios;
driver->init_termios.c_iflag = IGNBRK | IGNPAR;
driver->init_termios.c_oflag = ONLCR | XTABS;
driver->init_termios.c_lflag = ISIG | ECHO;
driver->flags = TTY_DRIVER_REAL_RAW;
tty_set_operations(driver, &sclp_ops);
rc = tty_register_driver(driver);
if (rc) {
printk(KERN_ERR SCLP_TTY_PRINT_HEADER
"could not register tty - "
"tty_register_driver returned %d\n", rc);
put_tty_driver(driver);
return rc;
}
sclp_tty_driver = driver;
return 0;
}
module_init(sclp_tty_init);

View file

@ -0,0 +1,71 @@
/*
* drivers/s390/char/sclp_tty.h
* interface to the SCLP-read/write driver
*
* S390 version
* Copyright (C) 1999 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Martin Peschke <mpeschke@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#ifndef __SCLP_TTY_H__
#define __SCLP_TTY_H__
#include <linux/ioctl.h>
#include <linux/termios.h>
#include <linux/tty_driver.h>
/* This is the type of data structures storing sclp ioctl setting. */
struct sclp_ioctls {
unsigned short htab;
unsigned char echo;
unsigned short columns;
unsigned char final_nl;
unsigned short max_sccb;
unsigned short kmem_sccb; /* can't be modified at run time */
unsigned char tolower;
unsigned char delim;
};
/* must be unique, FIXME: must be added in Documentation/ioctl_number.txt */
#define SCLP_IOCTL_LETTER 'B'
/* set width of horizontal tabulator */
#define TIOCSCLPSHTAB _IOW(SCLP_IOCTL_LETTER, 0, unsigned short)
/* enable/disable echo of input (independent from line discipline) */
#define TIOCSCLPSECHO _IOW(SCLP_IOCTL_LETTER, 1, unsigned char)
/* set number of colums for output */
#define TIOCSCLPSCOLS _IOW(SCLP_IOCTL_LETTER, 2, unsigned short)
/* enable/disable writing without final new line character */
#define TIOCSCLPSNL _IOW(SCLP_IOCTL_LETTER, 4, signed char)
/* set the maximum buffers size for output, rounded up to next 4kB boundary */
#define TIOCSCLPSOBUF _IOW(SCLP_IOCTL_LETTER, 5, unsigned short)
/* set initial (default) sclp ioctls */
#define TIOCSCLPSINIT _IO(SCLP_IOCTL_LETTER, 6)
/* enable/disable conversion from upper to lower case of input */
#define TIOCSCLPSCASE _IOW(SCLP_IOCTL_LETTER, 7, unsigned char)
/* set special character used for separating upper and lower case, */
/* 0x00 disables this feature */
#define TIOCSCLPSDELIM _IOW(SCLP_IOCTL_LETTER, 9, unsigned char)
/* get width of horizontal tabulator */
#define TIOCSCLPGHTAB _IOR(SCLP_IOCTL_LETTER, 10, unsigned short)
/* Is echo of input enabled ? (independent from line discipline) */
#define TIOCSCLPGECHO _IOR(SCLP_IOCTL_LETTER, 11, unsigned char)
/* get number of colums for output */
#define TIOCSCLPGCOLS _IOR(SCLP_IOCTL_LETTER, 12, unsigned short)
/* Is writing without final new line character enabled ? */
#define TIOCSCLPGNL _IOR(SCLP_IOCTL_LETTER, 14, signed char)
/* get the maximum buffers size for output */
#define TIOCSCLPGOBUF _IOR(SCLP_IOCTL_LETTER, 15, unsigned short)
/* Is conversion from upper to lower case of input enabled ? */
#define TIOCSCLPGCASE _IOR(SCLP_IOCTL_LETTER, 17, unsigned char)
/* get special character used for separating upper and lower case, */
/* 0x00 disables this feature */
#define TIOCSCLPGDELIM _IOR(SCLP_IOCTL_LETTER, 19, unsigned char)
/* get the number of buffers/pages got from kernel at startup */
#define TIOCSCLPGKBUF _IOR(SCLP_IOCTL_LETTER, 20, unsigned short)
extern struct tty_driver *sclp_tty_driver;
#endif /* __SCLP_TTY_H__ */

View file

@ -0,0 +1,785 @@
/*
* drivers/s390/char/sclp_vt220.c
* SCLP VT220 terminal driver.
*
* S390 version
* Copyright (C) 2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Peter Oberparleiter <Peter.Oberparleiter@de.ibm.com>
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/spinlock.h>
#include <linux/list.h>
#include <linux/wait.h>
#include <linux/timer.h>
#include <linux/kernel.h>
#include <linux/tty.h>
#include <linux/tty_driver.h>
#include <linux/sched.h>
#include <linux/errno.h>
#include <linux/mm.h>
#include <linux/major.h>
#include <linux/console.h>
#include <linux/kdev_t.h>
#include <linux/bootmem.h>
#include <linux/interrupt.h>
#include <linux/init.h>
#include <asm/uaccess.h>
#include "sclp.h"
#define SCLP_VT220_PRINT_HEADER "sclp vt220 tty driver: "
#define SCLP_VT220_MAJOR TTY_MAJOR
#define SCLP_VT220_MINOR 65
#define SCLP_VT220_DRIVER_NAME "sclp_vt220"
#define SCLP_VT220_DEVICE_NAME "ttysclp"
#define SCLP_VT220_CONSOLE_NAME "ttyS"
#define SCLP_VT220_CONSOLE_INDEX 1 /* console=ttyS1 */
#define SCLP_VT220_BUF_SIZE 80
/* Representation of a single write request */
struct sclp_vt220_request {
struct list_head list;
struct sclp_req sclp_req;
int retry_count;
};
/* VT220 SCCB */
struct sclp_vt220_sccb {
struct sccb_header header;
struct evbuf_header evbuf;
};
#define SCLP_VT220_MAX_CHARS_PER_BUFFER (PAGE_SIZE - \
sizeof(struct sclp_vt220_request) - \
sizeof(struct sclp_vt220_sccb))
/* Structures and data needed to register tty driver */
static struct tty_driver *sclp_vt220_driver;
/* The tty_struct that the kernel associated with us */
static struct tty_struct *sclp_vt220_tty;
/* Lock to protect internal data from concurrent access */
static spinlock_t sclp_vt220_lock;
/* List of empty pages to be used as write request buffers */
static struct list_head sclp_vt220_empty;
/* List of pending requests */
static struct list_head sclp_vt220_outqueue;
/* Number of requests in outqueue */
static int sclp_vt220_outqueue_count;
/* Wait queue used to delay write requests while we've run out of buffers */
static wait_queue_head_t sclp_vt220_waitq;
/* Timer used for delaying write requests to merge subsequent messages into
* a single buffer */
static struct timer_list sclp_vt220_timer;
/* Pointer to current request buffer which has been partially filled but not
* yet sent */
static struct sclp_vt220_request *sclp_vt220_current_request;
/* Number of characters in current request buffer */
static int sclp_vt220_buffered_chars;
/* Flag indicating whether this driver has already been initialized */
static int sclp_vt220_initialized = 0;
/* Flag indicating that sclp_vt220_current_request should really
* have been already queued but wasn't because the SCLP was processing
* another buffer */
static int sclp_vt220_flush_later;
static void sclp_vt220_receiver_fn(struct evbuf_header *evbuf);
static int __sclp_vt220_emit(struct sclp_vt220_request *request);
static void sclp_vt220_emit_current(void);
/* Registration structure for our interest in SCLP event buffers */
static struct sclp_register sclp_vt220_register = {
.send_mask = EvTyp_VT220Msg_Mask,
.receive_mask = EvTyp_VT220Msg_Mask,
.state_change_fn = NULL,
.receiver_fn = sclp_vt220_receiver_fn
};
/*
* Put provided request buffer back into queue and check emit pending
* buffers if necessary.
*/
static void
sclp_vt220_process_queue(struct sclp_vt220_request *request)
{
unsigned long flags;
void *page;
do {
/* Put buffer back to list of empty buffers */
page = request->sclp_req.sccb;
spin_lock_irqsave(&sclp_vt220_lock, flags);
/* Move request from outqueue to empty queue */
list_del(&request->list);
sclp_vt220_outqueue_count--;
list_add_tail((struct list_head *) page, &sclp_vt220_empty);
/* Check if there is a pending buffer on the out queue. */
request = NULL;
if (!list_empty(&sclp_vt220_outqueue))
request = list_entry(sclp_vt220_outqueue.next,
struct sclp_vt220_request, list);
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
} while (request && __sclp_vt220_emit(request));
if (request == NULL && sclp_vt220_flush_later)
sclp_vt220_emit_current();
wake_up(&sclp_vt220_waitq);
/* Check if the tty needs a wake up call */
if (sclp_vt220_tty != NULL) {
tty_wakeup(sclp_vt220_tty);
}
}
#define SCLP_BUFFER_MAX_RETRY 1
/*
* Callback through which the result of a write request is reported by the
* SCLP.
*/
static void
sclp_vt220_callback(struct sclp_req *request, void *data)
{
struct sclp_vt220_request *vt220_request;
struct sclp_vt220_sccb *sccb;
vt220_request = (struct sclp_vt220_request *) data;
if (request->status == SCLP_REQ_FAILED) {
sclp_vt220_process_queue(vt220_request);
return;
}
sccb = (struct sclp_vt220_sccb *) vt220_request->sclp_req.sccb;
/* Check SCLP response code and choose suitable action */
switch (sccb->header.response_code) {
case 0x0020 :
break;
case 0x05f0: /* Target resource in improper state */
break;
case 0x0340: /* Contained SCLP equipment check */
if (++vt220_request->retry_count > SCLP_BUFFER_MAX_RETRY)
break;
/* Remove processed buffers and requeue rest */
if (sclp_remove_processed((struct sccb_header *) sccb) > 0) {
/* Not all buffers were processed */
sccb->header.response_code = 0x0000;
vt220_request->sclp_req.status = SCLP_REQ_FILLED;
if (sclp_add_request(request) == 0)
return;
}
break;
case 0x0040: /* SCLP equipment check */
if (++vt220_request->retry_count > SCLP_BUFFER_MAX_RETRY)
break;
sccb->header.response_code = 0x0000;
vt220_request->sclp_req.status = SCLP_REQ_FILLED;
if (sclp_add_request(request) == 0)
return;
break;
default:
break;
}
sclp_vt220_process_queue(vt220_request);
}
/*
* Emit vt220 request buffer to SCLP. Return zero on success, non-zero
* otherwise.
*/
static int
__sclp_vt220_emit(struct sclp_vt220_request *request)
{
if (!(sclp_vt220_register.sclp_send_mask & EvTyp_VT220Msg_Mask)) {
request->sclp_req.status = SCLP_REQ_FAILED;
return -EIO;
}
request->sclp_req.command = SCLP_CMDW_WRITEDATA;
request->sclp_req.status = SCLP_REQ_FILLED;
request->sclp_req.callback = sclp_vt220_callback;
request->sclp_req.callback_data = (void *) request;
return sclp_add_request(&request->sclp_req);
}
/*
* Queue and emit given request.
*/
static void
sclp_vt220_emit(struct sclp_vt220_request *request)
{
unsigned long flags;
int count;
spin_lock_irqsave(&sclp_vt220_lock, flags);
list_add_tail(&request->list, &sclp_vt220_outqueue);
count = sclp_vt220_outqueue_count++;
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
/* Emit only the first buffer immediately - callback takes care of
* the rest */
if (count == 0 && __sclp_vt220_emit(request))
sclp_vt220_process_queue(request);
}
/*
* Queue and emit current request. Return zero on success, non-zero otherwise.
*/
static void
sclp_vt220_emit_current(void)
{
unsigned long flags;
struct sclp_vt220_request *request;
struct sclp_vt220_sccb *sccb;
spin_lock_irqsave(&sclp_vt220_lock, flags);
request = NULL;
if (sclp_vt220_current_request != NULL) {
sccb = (struct sclp_vt220_sccb *)
sclp_vt220_current_request->sclp_req.sccb;
/* Only emit buffers with content */
if (sccb->header.length != sizeof(struct sclp_vt220_sccb)) {
request = sclp_vt220_current_request;
sclp_vt220_current_request = NULL;
if (timer_pending(&sclp_vt220_timer))
del_timer(&sclp_vt220_timer);
}
sclp_vt220_flush_later = 0;
}
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
if (request != NULL)
sclp_vt220_emit(request);
}
#define SCLP_NORMAL_WRITE 0x00
/*
* Helper function to initialize a page with the sclp request structure.
*/
static struct sclp_vt220_request *
sclp_vt220_initialize_page(void *page)
{
struct sclp_vt220_request *request;
struct sclp_vt220_sccb *sccb;
/* Place request structure at end of page */
request = ((struct sclp_vt220_request *)
((addr_t) page + PAGE_SIZE)) - 1;
request->retry_count = 0;
request->sclp_req.sccb = page;
/* SCCB goes at start of page */
sccb = (struct sclp_vt220_sccb *) page;
memset((void *) sccb, 0, sizeof(struct sclp_vt220_sccb));
sccb->header.length = sizeof(struct sclp_vt220_sccb);
sccb->header.function_code = SCLP_NORMAL_WRITE;
sccb->header.response_code = 0x0000;
sccb->evbuf.type = EvTyp_VT220Msg;
sccb->evbuf.length = sizeof(struct evbuf_header);
return request;
}
static inline unsigned int
sclp_vt220_space_left(struct sclp_vt220_request *request)
{
struct sclp_vt220_sccb *sccb;
sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb;
return PAGE_SIZE - sizeof(struct sclp_vt220_request) -
sccb->header.length;
}
static inline unsigned int
sclp_vt220_chars_stored(struct sclp_vt220_request *request)
{
struct sclp_vt220_sccb *sccb;
sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb;
return sccb->evbuf.length - sizeof(struct evbuf_header);
}
/*
* Add msg to buffer associated with request. Return the number of characters
* added.
*/
static int
sclp_vt220_add_msg(struct sclp_vt220_request *request,
const unsigned char *msg, int count, int convertlf)
{
struct sclp_vt220_sccb *sccb;
void *buffer;
unsigned char c;
int from;
int to;
if (count > sclp_vt220_space_left(request))
count = sclp_vt220_space_left(request);
if (count <= 0)
return 0;
sccb = (struct sclp_vt220_sccb *) request->sclp_req.sccb;
buffer = (void *) ((addr_t) sccb + sccb->header.length);
if (convertlf) {
/* Perform Linefeed conversion (0x0a -> 0x0a 0x0d)*/
for (from=0, to=0;
(from < count) && (to < sclp_vt220_space_left(request));
from++) {
/* Retrieve character */
c = msg[from];
/* Perform conversion */
if (c == 0x0a) {
if (to + 1 < sclp_vt220_space_left(request)) {
((unsigned char *) buffer)[to++] = c;
((unsigned char *) buffer)[to++] = 0x0d;
} else
break;
} else
((unsigned char *) buffer)[to++] = c;
}
sccb->header.length += to;
sccb->evbuf.length += to;
return from;
} else {
memcpy(buffer, (const void *) msg, count);
sccb->header.length += count;
sccb->evbuf.length += count;
return count;
}
}
/*
* Emit buffer after having waited long enough for more data to arrive.
*/
static void
sclp_vt220_timeout(unsigned long data)
{
sclp_vt220_emit_current();
}
#define BUFFER_MAX_DELAY HZ/2
/*
* Internal implementation of the write function. Write COUNT bytes of data
* from memory at BUF
* to the SCLP interface. In case that the data does not fit into the current
* write buffer, emit the current one and allocate a new one. If there are no
* more empty buffers available, wait until one gets emptied. If DO_SCHEDULE
* is non-zero, the buffer will be scheduled for emitting after a timeout -
* otherwise the user has to explicitly call the flush function.
* A non-zero CONVERTLF parameter indicates that 0x0a characters in the message
* buffer should be converted to 0x0a 0x0d. After completion, return the number
* of bytes written.
*/
static int
__sclp_vt220_write(const unsigned char *buf, int count, int do_schedule,
int convertlf)
{
unsigned long flags;
void *page;
int written;
int overall_written;
if (count <= 0)
return 0;
overall_written = 0;
spin_lock_irqsave(&sclp_vt220_lock, flags);
do {
/* Create a sclp output buffer if none exists yet */
if (sclp_vt220_current_request == NULL) {
while (list_empty(&sclp_vt220_empty)) {
spin_unlock_irqrestore(&sclp_vt220_lock,
flags);
if (in_interrupt())
sclp_sync_wait();
else
wait_event(sclp_vt220_waitq,
!list_empty(&sclp_vt220_empty));
spin_lock_irqsave(&sclp_vt220_lock, flags);
}
page = (void *) sclp_vt220_empty.next;
list_del((struct list_head *) page);
sclp_vt220_current_request =
sclp_vt220_initialize_page(page);
}
/* Try to write the string to the current request buffer */
written = sclp_vt220_add_msg(sclp_vt220_current_request,
buf, count, convertlf);
overall_written += written;
if (written == count)
break;
/*
* Not all characters could be written to the current
* output buffer. Emit the buffer, create a new buffer
* and then output the rest of the string.
*/
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
sclp_vt220_emit_current();
spin_lock_irqsave(&sclp_vt220_lock, flags);
buf += written;
count -= written;
} while (count > 0);
/* Setup timer to output current console buffer after some time */
if (sclp_vt220_current_request != NULL &&
!timer_pending(&sclp_vt220_timer) && do_schedule) {
sclp_vt220_timer.function = sclp_vt220_timeout;
sclp_vt220_timer.data = 0UL;
sclp_vt220_timer.expires = jiffies + BUFFER_MAX_DELAY;
add_timer(&sclp_vt220_timer);
}
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
return overall_written;
}
/*
* This routine is called by the kernel to write a series of
* characters to the tty device. The characters may come from
* user space or kernel space. This routine will return the
* number of characters actually accepted for writing.
*/
static int
sclp_vt220_write(struct tty_struct *tty, const unsigned char *buf, int count)
{
return __sclp_vt220_write(buf, count, 1, 0);
}
#define SCLP_VT220_SESSION_ENDED 0x01
#define SCLP_VT220_SESSION_STARTED 0x80
#define SCLP_VT220_SESSION_DATA 0x00
/*
* Called by the SCLP to report incoming event buffers.
*/
static void
sclp_vt220_receiver_fn(struct evbuf_header *evbuf)
{
char *buffer;
unsigned int count;
/* Ignore input if device is not open */
if (sclp_vt220_tty == NULL)
return;
buffer = (char *) ((addr_t) evbuf + sizeof(struct evbuf_header));
count = evbuf->length - sizeof(struct evbuf_header);
switch (*buffer) {
case SCLP_VT220_SESSION_ENDED:
case SCLP_VT220_SESSION_STARTED:
break;
case SCLP_VT220_SESSION_DATA:
/* Send input to line discipline */
buffer++;
count--;
/* Prevent buffer overrun by discarding input. Note that
* because buffer_push works asynchronously, we cannot wait
* for the buffer to be emptied. */
if (count + sclp_vt220_tty->flip.count > TTY_FLIPBUF_SIZE)
count = TTY_FLIPBUF_SIZE - sclp_vt220_tty->flip.count;
memcpy(sclp_vt220_tty->flip.char_buf_ptr, buffer, count);
memset(sclp_vt220_tty->flip.flag_buf_ptr, TTY_NORMAL, count);
sclp_vt220_tty->flip.char_buf_ptr += count;
sclp_vt220_tty->flip.flag_buf_ptr += count;
sclp_vt220_tty->flip.count += count;
tty_flip_buffer_push(sclp_vt220_tty);
break;
}
}
/*
* This routine is called when a particular tty device is opened.
*/
static int
sclp_vt220_open(struct tty_struct *tty, struct file *filp)
{
if (tty->count == 1) {
sclp_vt220_tty = tty;
tty->driver_data = kmalloc(SCLP_VT220_BUF_SIZE, GFP_KERNEL);
if (tty->driver_data == NULL)
return -ENOMEM;
tty->low_latency = 0;
}
return 0;
}
/*
* This routine is called when a particular tty device is closed.
*/
static void
sclp_vt220_close(struct tty_struct *tty, struct file *filp)
{
if (tty->count == 1) {
sclp_vt220_tty = NULL;
kfree(tty->driver_data);
tty->driver_data = NULL;
}
}
/*
* This routine is called by the kernel to write a single
* character to the tty device. If the kernel uses this routine,
* it must call the flush_chars() routine (if defined) when it is
* done stuffing characters into the driver.
*
* NOTE: include/linux/tty_driver.h specifies that a character should be
* ignored if there is no room in the queue. This driver implements a different
* semantic in that it will block when there is no more room left.
*/
static void
sclp_vt220_put_char(struct tty_struct *tty, unsigned char ch)
{
__sclp_vt220_write(&ch, 1, 0, 0);
}
/*
* This routine is called by the kernel after it has written a
* series of characters to the tty device using put_char().
*/
static void
sclp_vt220_flush_chars(struct tty_struct *tty)
{
if (sclp_vt220_outqueue_count == 0)
sclp_vt220_emit_current();
else
sclp_vt220_flush_later = 1;
}
/*
* This routine returns the numbers of characters the tty driver
* will accept for queuing to be written. This number is subject
* to change as output buffers get emptied, or if the output flow
* control is acted.
*/
static int
sclp_vt220_write_room(struct tty_struct *tty)
{
unsigned long flags;
struct list_head *l;
int count;
spin_lock_irqsave(&sclp_vt220_lock, flags);
count = 0;
if (sclp_vt220_current_request != NULL)
count = sclp_vt220_space_left(sclp_vt220_current_request);
list_for_each(l, &sclp_vt220_empty)
count += SCLP_VT220_MAX_CHARS_PER_BUFFER;
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
return count;
}
/*
* Return number of buffered chars.
*/
static int
sclp_vt220_chars_in_buffer(struct tty_struct *tty)
{
unsigned long flags;
struct list_head *l;
struct sclp_vt220_request *r;
int count;
spin_lock_irqsave(&sclp_vt220_lock, flags);
count = 0;
if (sclp_vt220_current_request != NULL)
count = sclp_vt220_chars_stored(sclp_vt220_current_request);
list_for_each(l, &sclp_vt220_outqueue) {
r = list_entry(l, struct sclp_vt220_request, list);
count += sclp_vt220_chars_stored(r);
}
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
return count;
}
static void
__sclp_vt220_flush_buffer(void)
{
unsigned long flags;
sclp_vt220_emit_current();
spin_lock_irqsave(&sclp_vt220_lock, flags);
if (timer_pending(&sclp_vt220_timer))
del_timer(&sclp_vt220_timer);
while (sclp_vt220_outqueue_count > 0) {
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
sclp_sync_wait();
spin_lock_irqsave(&sclp_vt220_lock, flags);
}
spin_unlock_irqrestore(&sclp_vt220_lock, flags);
}
/*
* Pass on all buffers to the hardware. Return only when there are no more
* buffers pending.
*/
static void
sclp_vt220_flush_buffer(struct tty_struct *tty)
{
sclp_vt220_emit_current();
}
/*
* Initialize all relevant components and register driver with system.
*/
static int
__sclp_vt220_init(int early)
{
void *page;
int i;
if (sclp_vt220_initialized)
return 0;
sclp_vt220_initialized = 1;
spin_lock_init(&sclp_vt220_lock);
INIT_LIST_HEAD(&sclp_vt220_empty);
INIT_LIST_HEAD(&sclp_vt220_outqueue);
init_waitqueue_head(&sclp_vt220_waitq);
init_timer(&sclp_vt220_timer);
sclp_vt220_current_request = NULL;
sclp_vt220_buffered_chars = 0;
sclp_vt220_outqueue_count = 0;
sclp_vt220_tty = NULL;
sclp_vt220_flush_later = 0;
/* Allocate pages for output buffering */
for (i = 0; i < (early ? MAX_CONSOLE_PAGES : MAX_KMEM_PAGES); i++) {
if (early)
page = alloc_bootmem_low_pages(PAGE_SIZE);
else
page = (void *) get_zeroed_page(GFP_KERNEL | GFP_DMA);
if (!page)
return -ENOMEM;
list_add_tail((struct list_head *) page, &sclp_vt220_empty);
}
return 0;
}
static struct tty_operations sclp_vt220_ops = {
.open = sclp_vt220_open,
.close = sclp_vt220_close,
.write = sclp_vt220_write,
.put_char = sclp_vt220_put_char,
.flush_chars = sclp_vt220_flush_chars,
.write_room = sclp_vt220_write_room,
.chars_in_buffer = sclp_vt220_chars_in_buffer,
.flush_buffer = sclp_vt220_flush_buffer
};
/*
* Register driver with SCLP and Linux and initialize internal tty structures.
*/
int __init
sclp_vt220_tty_init(void)
{
struct tty_driver *driver;
int rc;
/* Note: we're not testing for CONSOLE_IS_SCLP here to preserve
* symmetry between VM and LPAR systems regarding ttyS1. */
driver = alloc_tty_driver(1);
if (!driver)
return -ENOMEM;
rc = __sclp_vt220_init(0);
if (rc) {
put_tty_driver(driver);
return rc;
}
rc = sclp_register(&sclp_vt220_register);
if (rc) {
printk(KERN_ERR SCLP_VT220_PRINT_HEADER
"could not register tty - "
"sclp_register returned %d\n", rc);
put_tty_driver(driver);
return rc;
}
driver->owner = THIS_MODULE;
driver->driver_name = SCLP_VT220_DRIVER_NAME;
driver->name = SCLP_VT220_DEVICE_NAME;
driver->major = SCLP_VT220_MAJOR;
driver->minor_start = SCLP_VT220_MINOR;
driver->type = TTY_DRIVER_TYPE_SYSTEM;
driver->subtype = SYSTEM_TYPE_TTY;
driver->init_termios = tty_std_termios;
driver->flags = TTY_DRIVER_REAL_RAW;
tty_set_operations(driver, &sclp_vt220_ops);
rc = tty_register_driver(driver);
if (rc) {
printk(KERN_ERR SCLP_VT220_PRINT_HEADER
"could not register tty - "
"tty_register_driver returned %d\n", rc);
put_tty_driver(driver);
return rc;
}
sclp_vt220_driver = driver;
return 0;
}
module_init(sclp_vt220_tty_init);
#ifdef CONFIG_SCLP_VT220_CONSOLE
static void
sclp_vt220_con_write(struct console *con, const char *buf, unsigned int count)
{
__sclp_vt220_write((const unsigned char *) buf, count, 1, 1);
}
static struct tty_driver *
sclp_vt220_con_device(struct console *c, int *index)
{
*index = 0;
return sclp_vt220_driver;
}
/*
* This routine is called from panic when the kernel is going to give up.
* We have to make sure that all buffers will be flushed to the SCLP.
* Note that this function may be called from within an interrupt context.
*/
static void
sclp_vt220_con_unblank(void)
{
__sclp_vt220_flush_buffer();
}
/* Structure needed to register with printk */
static struct console sclp_vt220_console =
{
.name = SCLP_VT220_CONSOLE_NAME,
.write = sclp_vt220_con_write,
.device = sclp_vt220_con_device,
.unblank = sclp_vt220_con_unblank,
.flags = CON_PRINTBUFFER,
.index = SCLP_VT220_CONSOLE_INDEX
};
static int __init
sclp_vt220_con_init(void)
{
int rc;
if (!CONSOLE_IS_SCLP)
return 0;
rc = __sclp_vt220_init(1);
if (rc)
return rc;
/* Attach linux console */
register_console(&sclp_vt220_console);
return 0;
}
console_initcall(sclp_vt220_con_init);
#endif /* CONFIG_SCLP_VT220_CONSOLE */

384
drivers/s390/char/tape.h Normal file
View file

@ -0,0 +1,384 @@
/*
* drivers/s390/char/tape.h
* tape device driver for 3480/3490E/3590 tapes.
*
* S390 and zSeries version
* Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Carsten Otte <cotte@de.ibm.com>
* Tuan Ngo-Anh <ngoanh@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#ifndef _TAPE_H
#define _TAPE_H
#include <asm/ccwdev.h>
#include <asm/debug.h>
#include <asm/idals.h>
#include <linux/config.h>
#include <linux/blkdev.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/mtio.h>
#include <linux/interrupt.h>
#include <linux/workqueue.h>
struct gendisk;
/*
* Define DBF_LIKE_HELL for lots of messages in the debug feature.
*/
#define DBF_LIKE_HELL
#ifdef DBF_LIKE_HELL
#define DBF_LH(level, str, ...) \
do { \
debug_sprintf_event(TAPE_DBF_AREA, level, str, ## __VA_ARGS__); \
} while (0)
#else
#define DBF_LH(level, str, ...) do {} while(0)
#endif
/*
* macros s390 debug feature (dbf)
*/
#define DBF_EVENT(d_level, d_str...) \
do { \
debug_sprintf_event(TAPE_DBF_AREA, d_level, d_str); \
} while (0)
#define DBF_EXCEPTION(d_level, d_str...) \
do { \
debug_sprintf_exception(TAPE_DBF_AREA, d_level, d_str); \
} while (0)
#define TAPE_VERSION_MAJOR 2
#define TAPE_VERSION_MINOR 0
#define TAPE_MAGIC "tape"
#define TAPE_MINORS_PER_DEV 2 /* two minors per device */
#define TAPEBLOCK_HSEC_SIZE 2048
#define TAPEBLOCK_HSEC_S2B 2
#define TAPEBLOCK_RETRIES 5
enum tape_medium_state {
MS_UNKNOWN,
MS_LOADED,
MS_UNLOADED,
MS_SIZE
};
enum tape_state {
TS_UNUSED=0,
TS_IN_USE,
TS_BLKUSE,
TS_INIT,
TS_NOT_OPER,
TS_SIZE
};
enum tape_op {
TO_BLOCK, /* Block read */
TO_BSB, /* Backward space block */
TO_BSF, /* Backward space filemark */
TO_DSE, /* Data security erase */
TO_FSB, /* Forward space block */
TO_FSF, /* Forward space filemark */
TO_LBL, /* Locate block label */
TO_NOP, /* No operation */
TO_RBA, /* Read backward */
TO_RBI, /* Read block information */
TO_RFO, /* Read forward */
TO_REW, /* Rewind tape */
TO_RUN, /* Rewind and unload tape */
TO_WRI, /* Write block */
TO_WTM, /* Write tape mark */
TO_MSEN, /* Medium sense */
TO_LOAD, /* Load tape */
TO_READ_CONFIG, /* Read configuration data */
TO_READ_ATTMSG, /* Read attention message */
TO_DIS, /* Tape display */
TO_ASSIGN, /* Assign tape to channel path */
TO_UNASSIGN, /* Unassign tape from channel path */
TO_SIZE /* #entries in tape_op_t */
};
/* Forward declaration */
struct tape_device;
/* tape_request->status can be: */
enum tape_request_status {
TAPE_REQUEST_INIT, /* request is ready to be processed */
TAPE_REQUEST_QUEUED, /* request is queued to be processed */
TAPE_REQUEST_IN_IO, /* request is currently in IO */
TAPE_REQUEST_DONE, /* request is completed. */
};
/* Tape CCW request */
struct tape_request {
struct list_head list; /* list head for request queueing. */
struct tape_device *device; /* tape device of this request */
struct ccw1 *cpaddr; /* address of the channel program. */
void *cpdata; /* pointer to ccw data. */
enum tape_request_status status;/* status of this request */
int options; /* options for execution. */
int retries; /* retry counter for error recovery. */
int rescnt; /* residual count from devstat. */
/* Callback for delivering final status. */
void (*callback)(struct tape_request *, void *);
void *callback_data;
enum tape_op op;
int rc;
};
/* Function type for magnetic tape commands */
typedef int (*tape_mtop_fn)(struct tape_device *, int);
/* Size of the array containing the mtops for a discipline */
#define TAPE_NR_MTOPS (MTMKPART+1)
/* Tape Discipline */
struct tape_discipline {
struct module *owner;
int (*setup_device)(struct tape_device *);
void (*cleanup_device)(struct tape_device *);
int (*irq)(struct tape_device *, struct tape_request *, struct irb *);
struct tape_request *(*read_block)(struct tape_device *, size_t);
struct tape_request *(*write_block)(struct tape_device *, size_t);
void (*process_eov)(struct tape_device*);
#ifdef CONFIG_S390_TAPE_BLOCK
/* Block device stuff. */
struct tape_request *(*bread)(struct tape_device *, struct request *);
void (*check_locate)(struct tape_device *, struct tape_request *);
void (*free_bread)(struct tape_request *);
#endif
/* ioctl function for additional ioctls. */
int (*ioctl_fn)(struct tape_device *, unsigned int, unsigned long);
/* Array of tape commands with TAPE_NR_MTOPS entries */
tape_mtop_fn *mtop_array;
};
/*
* The discipline irq function either returns an error code (<0) which
* means that the request has failed with an error or one of the following:
*/
#define TAPE_IO_SUCCESS 0 /* request successful */
#define TAPE_IO_PENDING 1 /* request still running */
#define TAPE_IO_RETRY 2 /* retry to current request */
#define TAPE_IO_STOP 3 /* stop the running request */
/* Char Frontend Data */
struct tape_char_data {
struct idal_buffer *idal_buf; /* idal buffer for user char data */
int block_size; /* of size block_size. */
};
#ifdef CONFIG_S390_TAPE_BLOCK
/* Block Frontend Data */
struct tape_blk_data
{
/* Block device request queue. */
request_queue_t * request_queue;
spinlock_t request_queue_lock;
/* Task to move entries from block request to CCS request queue. */
struct work_struct requeue_task;
atomic_t requeue_scheduled;
/* Current position on the tape. */
long block_position;
int medium_changed;
struct gendisk * disk;
};
#endif
/* Tape Info */
struct tape_device {
/* entry in tape_device_list */
struct list_head node;
int cdev_id;
struct ccw_device * cdev;
struct tape_class_device * nt;
struct tape_class_device * rt;
/* Device discipline information. */
struct tape_discipline * discipline;
void * discdata;
/* Generic status flags */
long tape_generic_status;
/* Device state information. */
wait_queue_head_t state_change_wq;
enum tape_state tape_state;
enum tape_medium_state medium_state;
unsigned char * modeset_byte;
/* Reference count. */
atomic_t ref_count;
/* Request queue. */
struct list_head req_queue;
/* Each tape device has (currently) two minor numbers. */
int first_minor;
/* Number of tapemarks required for correct termination. */
int required_tapemarks;
/* Block ID of the BOF */
unsigned int bof;
/* Character device frontend data */
struct tape_char_data char_data;
#ifdef CONFIG_S390_TAPE_BLOCK
/* Block dev frontend data */
struct tape_blk_data blk_data;
#endif
};
/* Externals from tape_core.c */
extern struct tape_request *tape_alloc_request(int cplength, int datasize);
extern void tape_free_request(struct tape_request *);
extern int tape_do_io(struct tape_device *, struct tape_request *);
extern int tape_do_io_async(struct tape_device *, struct tape_request *);
extern int tape_do_io_interruptible(struct tape_device *, struct tape_request *);
void tape_hotplug_event(struct tape_device *, int major, int action);
static inline int
tape_do_io_free(struct tape_device *device, struct tape_request *request)
{
int rc;
rc = tape_do_io(device, request);
tape_free_request(request);
return rc;
}
extern int tape_oper_handler(int irq, int status);
extern void tape_noper_handler(int irq, int status);
extern int tape_open(struct tape_device *);
extern int tape_release(struct tape_device *);
extern int tape_mtop(struct tape_device *, int, int);
extern void tape_state_set(struct tape_device *, enum tape_state);
extern int tape_generic_online(struct tape_device *, struct tape_discipline *);
extern int tape_generic_offline(struct tape_device *device);
/* Externals from tape_devmap.c */
extern int tape_generic_probe(struct ccw_device *);
extern void tape_generic_remove(struct ccw_device *);
extern struct tape_device *tape_get_device(int devindex);
extern struct tape_device *tape_get_device_reference(struct tape_device *);
extern struct tape_device *tape_put_device(struct tape_device *);
/* Externals from tape_char.c */
extern int tapechar_init(void);
extern void tapechar_exit(void);
extern int tapechar_setup_device(struct tape_device *);
extern void tapechar_cleanup_device(struct tape_device *);
/* Externals from tape_block.c */
#ifdef CONFIG_S390_TAPE_BLOCK
extern int tapeblock_init (void);
extern void tapeblock_exit(void);
extern int tapeblock_setup_device(struct tape_device *);
extern void tapeblock_cleanup_device(struct tape_device *);
#else
static inline int tapeblock_init (void) {return 0;}
static inline void tapeblock_exit (void) {;}
static inline int tapeblock_setup_device(struct tape_device *t) {return 0;}
static inline void tapeblock_cleanup_device (struct tape_device *t) {;}
#endif
/* tape initialisation functions */
#ifdef CONFIG_PROC_FS
extern void tape_proc_init (void);
extern void tape_proc_cleanup (void);
#else
static inline void tape_proc_init (void) {;}
static inline void tape_proc_cleanup (void) {;}
#endif
/* a function for dumping device sense info */
extern void tape_dump_sense(struct tape_device *, struct tape_request *,
struct irb *);
extern void tape_dump_sense_dbf(struct tape_device *, struct tape_request *,
struct irb *);
/* functions for handling the status of a device */
extern void tape_med_state_set(struct tape_device *, enum tape_medium_state);
/* The debug area */
extern debug_info_t *TAPE_DBF_AREA;
/* functions for building ccws */
static inline struct ccw1 *
tape_ccw_cc(struct ccw1 *ccw, __u8 cmd_code, __u16 memsize, void *cda)
{
ccw->cmd_code = cmd_code;
ccw->flags = CCW_FLAG_CC;
ccw->count = memsize;
ccw->cda = (__u32)(addr_t) cda;
return ccw + 1;
}
static inline struct ccw1 *
tape_ccw_end(struct ccw1 *ccw, __u8 cmd_code, __u16 memsize, void *cda)
{
ccw->cmd_code = cmd_code;
ccw->flags = 0;
ccw->count = memsize;
ccw->cda = (__u32)(addr_t) cda;
return ccw + 1;
}
static inline struct ccw1 *
tape_ccw_cmd(struct ccw1 *ccw, __u8 cmd_code)
{
ccw->cmd_code = cmd_code;
ccw->flags = 0;
ccw->count = 0;
ccw->cda = (__u32)(addr_t) &ccw->cmd_code;
return ccw + 1;
}
static inline struct ccw1 *
tape_ccw_repeat(struct ccw1 *ccw, __u8 cmd_code, int count)
{
while (count-- > 0) {
ccw->cmd_code = cmd_code;
ccw->flags = CCW_FLAG_CC;
ccw->count = 0;
ccw->cda = (__u32)(addr_t) &ccw->cmd_code;
ccw++;
}
return ccw;
}
static inline struct ccw1 *
tape_ccw_cc_idal(struct ccw1 *ccw, __u8 cmd_code, struct idal_buffer *idal)
{
ccw->cmd_code = cmd_code;
ccw->flags = CCW_FLAG_CC;
idal_buffer_set_cda(idal, ccw);
return ccw++;
}
static inline struct ccw1 *
tape_ccw_end_idal(struct ccw1 *ccw, __u8 cmd_code, struct idal_buffer *idal)
{
ccw->cmd_code = cmd_code;
ccw->flags = 0;
idal_buffer_set_cda(idal, ccw);
return ccw++;
}
/* Global vars */
extern const char *tape_state_verbose[];
extern const char *tape_op_verbose[];
#endif /* for ifdef tape.h */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,492 @@
/*
* drivers/s390/char/tape_block.c
* block device frontend for tape device driver
*
* S390 and zSeries version
* Copyright (C) 2001,2003 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Carsten Otte <cotte@de.ibm.com>
* Tuan Ngo-Anh <ngoanh@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Stefan Bader <shbader@de.ibm.com>
*/
#include <linux/fs.h>
#include <linux/config.h>
#include <linux/module.h>
#include <linux/blkdev.h>
#include <linux/interrupt.h>
#include <linux/buffer_head.h>
#include <asm/debug.h>
#define TAPE_DBF_AREA tape_core_dbf
#include "tape.h"
#define PRINTK_HEADER "TAPE_BLOCK: "
#define TAPEBLOCK_MAX_SEC 100
#define TAPEBLOCK_MIN_REQUEUE 3
/*
* 2003/11/25 Stefan Bader <shbader@de.ibm.com>
*
* In 2.5/2.6 the block device request function is very likely to be called
* with disabled interrupts (e.g. generic_unplug_device). So the driver can't
* just call any function that tries to allocate CCW requests from that con-
* text since it might sleep. There are two choices to work around this:
* a) do not allocate with kmalloc but use its own memory pool
* b) take requests from the queue outside that context, knowing that
* allocation might sleep
*/
/*
* file operation structure for tape block frontend
*/
static int tapeblock_open(struct inode *, struct file *);
static int tapeblock_release(struct inode *, struct file *);
static int tapeblock_ioctl(struct inode *, struct file *, unsigned int,
unsigned long);
static int tapeblock_medium_changed(struct gendisk *);
static int tapeblock_revalidate_disk(struct gendisk *);
static struct block_device_operations tapeblock_fops = {
.owner = THIS_MODULE,
.open = tapeblock_open,
.release = tapeblock_release,
.ioctl = tapeblock_ioctl,
.media_changed = tapeblock_medium_changed,
.revalidate_disk = tapeblock_revalidate_disk,
};
static int tapeblock_major = 0;
static void
tapeblock_trigger_requeue(struct tape_device *device)
{
/* Protect against rescheduling. */
if (atomic_compare_and_swap(0, 1, &device->blk_data.requeue_scheduled))
return;
schedule_work(&device->blk_data.requeue_task);
}
/*
* Post finished request.
*/
static inline void
tapeblock_end_request(struct request *req, int uptodate)
{
if (end_that_request_first(req, uptodate, req->hard_nr_sectors))
BUG();
end_that_request_last(req);
}
static void
__tapeblock_end_request(struct tape_request *ccw_req, void *data)
{
struct tape_device *device;
struct request *req;
DBF_LH(6, "__tapeblock_end_request()\n");
device = ccw_req->device;
req = (struct request *) data;
tapeblock_end_request(req, ccw_req->rc == 0);
if (ccw_req->rc == 0)
/* Update position. */
device->blk_data.block_position =
(req->sector + req->nr_sectors) >> TAPEBLOCK_HSEC_S2B;
else
/* We lost the position information due to an error. */
device->blk_data.block_position = -1;
device->discipline->free_bread(ccw_req);
if (!list_empty(&device->req_queue) ||
elv_next_request(device->blk_data.request_queue))
tapeblock_trigger_requeue(device);
}
/*
* Feed the tape device CCW queue with requests supplied in a list.
*/
static inline int
tapeblock_start_request(struct tape_device *device, struct request *req)
{
struct tape_request * ccw_req;
int rc;
DBF_LH(6, "tapeblock_start_request(%p, %p)\n", device, req);
ccw_req = device->discipline->bread(device, req);
if (IS_ERR(ccw_req)) {
DBF_EVENT(1, "TBLOCK: bread failed\n");
tapeblock_end_request(req, 0);
return PTR_ERR(ccw_req);
}
ccw_req->callback = __tapeblock_end_request;
ccw_req->callback_data = (void *) req;
ccw_req->retries = TAPEBLOCK_RETRIES;
rc = tape_do_io_async(device, ccw_req);
if (rc) {
/*
* Start/enqueueing failed. No retries in
* this case.
*/
tapeblock_end_request(req, 0);
device->discipline->free_bread(ccw_req);
}
return rc;
}
/*
* Move requests from the block device request queue to the tape device ccw
* queue.
*/
static void
tapeblock_requeue(void *data) {
struct tape_device * device;
request_queue_t * queue;
int nr_queued;
struct request * req;
struct list_head * l;
int rc;
device = (struct tape_device *) data;
if (!device)
return;
spin_lock_irq(get_ccwdev_lock(device->cdev));
queue = device->blk_data.request_queue;
/* Count number of requests on ccw queue. */
nr_queued = 0;
list_for_each(l, &device->req_queue)
nr_queued++;
spin_unlock(get_ccwdev_lock(device->cdev));
spin_lock(&device->blk_data.request_queue_lock);
while (
!blk_queue_plugged(queue) &&
elv_next_request(queue) &&
nr_queued < TAPEBLOCK_MIN_REQUEUE
) {
req = elv_next_request(queue);
if (rq_data_dir(req) == WRITE) {
DBF_EVENT(1, "TBLOCK: Rejecting write request\n");
blkdev_dequeue_request(req);
tapeblock_end_request(req, 0);
continue;
}
spin_unlock_irq(&device->blk_data.request_queue_lock);
rc = tapeblock_start_request(device, req);
spin_lock_irq(&device->blk_data.request_queue_lock);
blkdev_dequeue_request(req);
nr_queued++;
}
spin_unlock_irq(&device->blk_data.request_queue_lock);
atomic_set(&device->blk_data.requeue_scheduled, 0);
}
/*
* Tape request queue function. Called from ll_rw_blk.c
*/
static void
tapeblock_request_fn(request_queue_t *queue)
{
struct tape_device *device;
device = (struct tape_device *) queue->queuedata;
DBF_LH(6, "tapeblock_request_fn(device=%p)\n", device);
if (device == NULL)
BUG();
tapeblock_trigger_requeue(device);
}
/*
* This function is called for every new tapedevice
*/
int
tapeblock_setup_device(struct tape_device * device)
{
struct tape_blk_data * blkdat;
struct gendisk * disk;
int rc;
blkdat = &device->blk_data;
spin_lock_init(&blkdat->request_queue_lock);
atomic_set(&blkdat->requeue_scheduled, 0);
blkdat->request_queue = blk_init_queue(
tapeblock_request_fn,
&blkdat->request_queue_lock
);
if (!blkdat->request_queue)
return -ENOMEM;
elevator_exit(blkdat->request_queue->elevator);
rc = elevator_init(blkdat->request_queue, "noop");
if (rc)
goto cleanup_queue;
blk_queue_hardsect_size(blkdat->request_queue, TAPEBLOCK_HSEC_SIZE);
blk_queue_max_sectors(blkdat->request_queue, TAPEBLOCK_MAX_SEC);
blk_queue_max_phys_segments(blkdat->request_queue, -1L);
blk_queue_max_hw_segments(blkdat->request_queue, -1L);
blk_queue_max_segment_size(blkdat->request_queue, -1L);
blk_queue_segment_boundary(blkdat->request_queue, -1L);
disk = alloc_disk(1);
if (!disk) {
rc = -ENOMEM;
goto cleanup_queue;
}
disk->major = tapeblock_major;
disk->first_minor = device->first_minor;
disk->fops = &tapeblock_fops;
disk->private_data = tape_get_device_reference(device);
disk->queue = blkdat->request_queue;
set_capacity(disk, 0);
sprintf(disk->disk_name, "btibm%d",
device->first_minor / TAPE_MINORS_PER_DEV);
blkdat->disk = disk;
blkdat->medium_changed = 1;
blkdat->request_queue->queuedata = tape_get_device_reference(device);
add_disk(disk);
INIT_WORK(&blkdat->requeue_task, tapeblock_requeue,
tape_get_device_reference(device));
return 0;
cleanup_queue:
blk_cleanup_queue(blkdat->request_queue);
blkdat->request_queue = NULL;
return rc;
}
void
tapeblock_cleanup_device(struct tape_device *device)
{
flush_scheduled_work();
device->blk_data.requeue_task.data = tape_put_device(device);
if (!device->blk_data.disk) {
PRINT_ERR("(%s): No gendisk to clean up!\n",
device->cdev->dev.bus_id);
goto cleanup_queue;
}
del_gendisk(device->blk_data.disk);
device->blk_data.disk->private_data =
tape_put_device(device->blk_data.disk->private_data);
put_disk(device->blk_data.disk);
device->blk_data.disk = NULL;
cleanup_queue:
device->blk_data.request_queue->queuedata = tape_put_device(device);
blk_cleanup_queue(device->blk_data.request_queue);
device->blk_data.request_queue = NULL;
}
/*
* Detect number of blocks of the tape.
* FIXME: can we extent this to detect the blocks size as well ?
*/
static int
tapeblock_revalidate_disk(struct gendisk *disk)
{
struct tape_device * device;
unsigned int nr_of_blks;
int rc;
device = (struct tape_device *) disk->private_data;
if (!device)
BUG();
if (!device->blk_data.medium_changed)
return 0;
PRINT_INFO("Detecting media size...\n");
rc = tape_mtop(device, MTFSFM, 1);
if (rc)
return rc;
rc = tape_mtop(device, MTTELL, 1);
if (rc < 0)
return rc;
DBF_LH(3, "Image file ends at %d\n", rc);
nr_of_blks = rc;
/* This will fail for the first file. Catch the error by checking the
* position. */
tape_mtop(device, MTBSF, 1);
rc = tape_mtop(device, MTTELL, 1);
if (rc < 0)
return rc;
if (rc > nr_of_blks)
return -EINVAL;
DBF_LH(3, "Image file starts at %d\n", rc);
device->bof = rc;
nr_of_blks -= rc;
PRINT_INFO("Found %i blocks on media\n", nr_of_blks);
set_capacity(device->blk_data.disk,
nr_of_blks*(TAPEBLOCK_HSEC_SIZE/512));
device->blk_data.block_position = 0;
device->blk_data.medium_changed = 0;
return 0;
}
static int
tapeblock_medium_changed(struct gendisk *disk)
{
struct tape_device *device;
device = (struct tape_device *) disk->private_data;
DBF_LH(6, "tapeblock_medium_changed(%p) = %d\n",
device, device->blk_data.medium_changed);
return device->blk_data.medium_changed;
}
/*
* Block frontend tape device open function.
*/
static int
tapeblock_open(struct inode *inode, struct file *filp)
{
struct gendisk * disk;
struct tape_device * device;
int rc;
disk = inode->i_bdev->bd_disk;
device = tape_get_device_reference(disk->private_data);
if (device->required_tapemarks) {
DBF_EVENT(2, "TBLOCK: missing tapemarks\n");
PRINT_ERR("TBLOCK: Refusing to open tape with missing"
" end of file marks.\n");
rc = -EPERM;
goto put_device;
}
rc = tape_open(device);
if (rc)
goto put_device;
rc = tapeblock_revalidate_disk(disk);
if (rc)
goto release;
/*
* Note: The reference to <device> is hold until the release function
* is called.
*/
tape_state_set(device, TS_BLKUSE);
return 0;
release:
tape_release(device);
put_device:
tape_put_device(device);
return rc;
}
/*
* Block frontend tape device release function.
*
* Note: One reference to the tape device was made by the open function. So
* we just get the pointer here and release the reference.
*/
static int
tapeblock_release(struct inode *inode, struct file *filp)
{
struct gendisk *disk = inode->i_bdev->bd_disk;
struct tape_device *device = disk->private_data;
tape_state_set(device, TS_IN_USE);
tape_release(device);
tape_put_device(device);
return 0;
}
/*
* Support of some generic block device IOCTLs.
*/
static int
tapeblock_ioctl(
struct inode * inode,
struct file * file,
unsigned int command,
unsigned long arg
) {
int rc;
int minor;
struct gendisk *disk = inode->i_bdev->bd_disk;
struct tape_device *device = disk->private_data;
rc = 0;
disk = inode->i_bdev->bd_disk;
if (!disk)
BUG();
device = disk->private_data;
if (!device)
BUG();
minor = iminor(inode);
DBF_LH(6, "tapeblock_ioctl(0x%0x)\n", command);
DBF_LH(6, "device = %d:%d\n", tapeblock_major, minor);
switch (command) {
/* Refuse some IOCTL calls without complaining (mount). */
case 0x5310: /* CDROMMULTISESSION */
rc = -EINVAL;
break;
default:
PRINT_WARN("invalid ioctl 0x%x\n", command);
rc = -EINVAL;
}
return rc;
}
/*
* Initialize block device frontend.
*/
int
tapeblock_init(void)
{
int rc;
/* Register the tape major number to the kernel */
rc = register_blkdev(tapeblock_major, "tBLK");
if (rc < 0)
return rc;
if (tapeblock_major == 0)
tapeblock_major = rc;
PRINT_INFO("tape gets major %d for block device\n", tapeblock_major);
return 0;
}
/*
* Deregister major for block device frontend
*/
void
tapeblock_exit(void)
{
unregister_blkdev(tapeblock_major, "tBLK");
}

View file

@ -0,0 +1,492 @@
/*
* drivers/s390/char/tape_char.c
* character device frontend for tape device driver
*
* S390 and zSeries version
* Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Carsten Otte <cotte@de.ibm.com>
* Michael Holzheu <holzheu@de.ibm.com>
* Tuan Ngo-Anh <ngoanh@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/types.h>
#include <linux/proc_fs.h>
#include <linux/mtio.h>
#include <asm/uaccess.h>
#define TAPE_DBF_AREA tape_core_dbf
#include "tape.h"
#include "tape_std.h"
#include "tape_class.h"
#define PRINTK_HEADER "TAPE_CHAR: "
#define TAPECHAR_MAJOR 0 /* get dynamic major */
/*
* file operation structure for tape character frontend
*/
static ssize_t tapechar_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t tapechar_write(struct file *, const char __user *, size_t, loff_t *);
static int tapechar_open(struct inode *,struct file *);
static int tapechar_release(struct inode *,struct file *);
static int tapechar_ioctl(struct inode *, struct file *, unsigned int,
unsigned long);
static struct file_operations tape_fops =
{
.owner = THIS_MODULE,
.read = tapechar_read,
.write = tapechar_write,
.ioctl = tapechar_ioctl,
.open = tapechar_open,
.release = tapechar_release,
};
static int tapechar_major = TAPECHAR_MAJOR;
/*
* This function is called for every new tapedevice
*/
int
tapechar_setup_device(struct tape_device * device)
{
char device_name[20];
sprintf(device_name, "ntibm%i", device->first_minor / 2);
device->nt = register_tape_dev(
&device->cdev->dev,
MKDEV(tapechar_major, device->first_minor),
&tape_fops,
device_name,
"non-rewinding"
);
device_name[0] = 'r';
device->rt = register_tape_dev(
&device->cdev->dev,
MKDEV(tapechar_major, device->first_minor + 1),
&tape_fops,
device_name,
"rewinding"
);
return 0;
}
void
tapechar_cleanup_device(struct tape_device *device)
{
unregister_tape_dev(device->rt);
device->rt = NULL;
unregister_tape_dev(device->nt);
device->nt = NULL;
}
/*
* Terminate write command (we write two TMs and skip backward over last)
* This ensures that the tape is always correctly terminated.
* When the user writes afterwards a new file, he will overwrite the
* second TM and therefore one TM will remain to separate the
* two files on the tape...
*/
static inline void
tapechar_terminate_write(struct tape_device *device)
{
if (tape_mtop(device, MTWEOF, 1) == 0 &&
tape_mtop(device, MTWEOF, 1) == 0)
tape_mtop(device, MTBSR, 1);
}
static inline int
tapechar_check_idalbuffer(struct tape_device *device, size_t block_size)
{
struct idal_buffer *new;
if (device->char_data.idal_buf != NULL &&
device->char_data.idal_buf->size == block_size)
return 0;
if (block_size > MAX_BLOCKSIZE) {
DBF_EVENT(3, "Invalid blocksize (%zd > %d)\n",
block_size, MAX_BLOCKSIZE);
PRINT_ERR("Invalid blocksize (%zd> %d)\n",
block_size, MAX_BLOCKSIZE);
return -EINVAL;
}
/* The current idal buffer is not correct. Allocate a new one. */
new = idal_buffer_alloc(block_size, 0);
if (new == NULL)
return -ENOMEM;
if (device->char_data.idal_buf != NULL)
idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = new;
return 0;
}
/*
* Tape device read function
*/
ssize_t
tapechar_read(struct file *filp, char __user *data, size_t count, loff_t *ppos)
{
struct tape_device *device;
struct tape_request *request;
size_t block_size;
int rc;
DBF_EVENT(6, "TCHAR:read\n");
device = (struct tape_device *) filp->private_data;
/*
* If the tape isn't terminated yet, do it now. And since we then
* are at the end of the tape there wouldn't be anything to read
* anyways. So we return immediatly.
*/
if(device->required_tapemarks) {
return tape_std_terminate_write(device);
}
/* Find out block size to use */
if (device->char_data.block_size != 0) {
if (count < device->char_data.block_size) {
DBF_EVENT(3, "TCHAR:read smaller than block "
"size was requested\n");
return -EINVAL;
}
block_size = device->char_data.block_size;
} else {
block_size = count;
}
rc = tapechar_check_idalbuffer(device, block_size);
if (rc)
return rc;
#ifdef CONFIG_S390_TAPE_BLOCK
/* Changes position. */
device->blk_data.medium_changed = 1;
#endif
DBF_EVENT(6, "TCHAR:nbytes: %lx\n", block_size);
/* Let the discipline build the ccw chain. */
request = device->discipline->read_block(device, block_size);
if (IS_ERR(request))
return PTR_ERR(request);
/* Execute it. */
rc = tape_do_io(device, request);
if (rc == 0) {
rc = block_size - request->rescnt;
DBF_EVENT(6, "TCHAR:rbytes: %x\n", rc);
filp->f_pos += rc;
/* Copy data from idal buffer to user space. */
if (idal_buffer_to_user(device->char_data.idal_buf,
data, rc) != 0)
rc = -EFAULT;
}
tape_free_request(request);
return rc;
}
/*
* Tape device write function
*/
ssize_t
tapechar_write(struct file *filp, const char __user *data, size_t count, loff_t *ppos)
{
struct tape_device *device;
struct tape_request *request;
size_t block_size;
size_t written;
int nblocks;
int i, rc;
DBF_EVENT(6, "TCHAR:write\n");
device = (struct tape_device *) filp->private_data;
/* Find out block size and number of blocks */
if (device->char_data.block_size != 0) {
if (count < device->char_data.block_size) {
DBF_EVENT(3, "TCHAR:write smaller than block "
"size was requested\n");
return -EINVAL;
}
block_size = device->char_data.block_size;
nblocks = count / block_size;
} else {
block_size = count;
nblocks = 1;
}
rc = tapechar_check_idalbuffer(device, block_size);
if (rc)
return rc;
#ifdef CONFIG_S390_TAPE_BLOCK
/* Changes position. */
device->blk_data.medium_changed = 1;
#endif
DBF_EVENT(6,"TCHAR:nbytes: %lx\n", block_size);
DBF_EVENT(6, "TCHAR:nblocks: %x\n", nblocks);
/* Let the discipline build the ccw chain. */
request = device->discipline->write_block(device, block_size);
if (IS_ERR(request))
return PTR_ERR(request);
rc = 0;
written = 0;
for (i = 0; i < nblocks; i++) {
/* Copy data from user space to idal buffer. */
if (idal_buffer_from_user(device->char_data.idal_buf,
data, block_size)) {
rc = -EFAULT;
break;
}
rc = tape_do_io(device, request);
if (rc)
break;
DBF_EVENT(6, "TCHAR:wbytes: %lx\n",
block_size - request->rescnt);
filp->f_pos += block_size - request->rescnt;
written += block_size - request->rescnt;
if (request->rescnt != 0)
break;
data += block_size;
}
tape_free_request(request);
if (rc == -ENOSPC) {
/*
* Ok, the device has no more space. It has NOT written
* the block.
*/
if (device->discipline->process_eov)
device->discipline->process_eov(device);
if (written > 0)
rc = 0;
}
/*
* After doing a write we always need two tapemarks to correctly
* terminate the tape (one to terminate the file, the second to
* flag the end of recorded data.
* Since process_eov positions the tape in front of the written
* tapemark it doesn't hurt to write two marks again.
*/
if (!rc)
device->required_tapemarks = 2;
return rc ? rc : written;
}
/*
* Character frontend tape device open function.
*/
int
tapechar_open (struct inode *inode, struct file *filp)
{
struct tape_device *device;
int minor, rc;
DBF_EVENT(6, "TCHAR:open: %i:%i\n",
imajor(filp->f_dentry->d_inode),
iminor(filp->f_dentry->d_inode));
if (imajor(filp->f_dentry->d_inode) != tapechar_major)
return -ENODEV;
minor = iminor(filp->f_dentry->d_inode);
device = tape_get_device(minor / TAPE_MINORS_PER_DEV);
if (IS_ERR(device)) {
DBF_EVENT(3, "TCHAR:open: tape_get_device() failed\n");
return PTR_ERR(device);
}
rc = tape_open(device);
if (rc == 0) {
filp->private_data = device;
return nonseekable_open(inode, filp);
}
tape_put_device(device);
return rc;
}
/*
* Character frontend tape device release function.
*/
int
tapechar_release(struct inode *inode, struct file *filp)
{
struct tape_device *device;
DBF_EVENT(6, "TCHAR:release: %x\n", iminor(inode));
device = (struct tape_device *) filp->private_data;
/*
* If this is the rewinding tape minor then rewind. In that case we
* write all required tapemarks. Otherwise only one to terminate the
* file.
*/
if ((iminor(inode) & 1) != 0) {
if (device->required_tapemarks)
tape_std_terminate_write(device);
tape_mtop(device, MTREW, 1);
} else {
if (device->required_tapemarks > 1) {
if (tape_mtop(device, MTWEOF, 1) == 0)
device->required_tapemarks--;
}
}
if (device->char_data.idal_buf != NULL) {
idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = NULL;
}
tape_release(device);
filp->private_data = tape_put_device(device);
return 0;
}
/*
* Tape device io controls.
*/
static int
tapechar_ioctl(struct inode *inp, struct file *filp,
unsigned int no, unsigned long data)
{
struct tape_device *device;
int rc;
DBF_EVENT(6, "TCHAR:ioct\n");
device = (struct tape_device *) filp->private_data;
if (no == MTIOCTOP) {
struct mtop op;
if (copy_from_user(&op, (char __user *) data, sizeof(op)) != 0)
return -EFAULT;
if (op.mt_count < 0)
return -EINVAL;
/*
* Operations that change tape position should write final
* tapemarks.
*/
switch (op.mt_op) {
case MTFSF:
case MTBSF:
case MTFSR:
case MTBSR:
case MTREW:
case MTOFFL:
case MTEOM:
case MTRETEN:
case MTBSFM:
case MTFSFM:
case MTSEEK:
#ifdef CONFIG_S390_TAPE_BLOCK
device->blk_data.medium_changed = 1;
#endif
if (device->required_tapemarks)
tape_std_terminate_write(device);
default:
;
}
rc = tape_mtop(device, op.mt_op, op.mt_count);
if (op.mt_op == MTWEOF && rc == 0) {
if (op.mt_count > device->required_tapemarks)
device->required_tapemarks = 0;
else
device->required_tapemarks -= op.mt_count;
}
return rc;
}
if (no == MTIOCPOS) {
/* MTIOCPOS: query the tape position. */
struct mtpos pos;
rc = tape_mtop(device, MTTELL, 1);
if (rc < 0)
return rc;
pos.mt_blkno = rc;
if (copy_to_user((char __user *) data, &pos, sizeof(pos)) != 0)
return -EFAULT;
return 0;
}
if (no == MTIOCGET) {
/* MTIOCGET: query the tape drive status. */
struct mtget get;
memset(&get, 0, sizeof(get));
get.mt_type = MT_ISUNKNOWN;
get.mt_resid = 0 /* device->devstat.rescnt */;
get.mt_dsreg = device->tape_state;
/* FIXME: mt_gstat, mt_erreg, mt_fileno */
get.mt_gstat = 0;
get.mt_erreg = 0;
get.mt_fileno = 0;
get.mt_gstat = device->tape_generic_status;
if (device->medium_state == MS_LOADED) {
rc = tape_mtop(device, MTTELL, 1);
if (rc < 0)
return rc;
if (rc == 0)
get.mt_gstat |= GMT_BOT(~0);
get.mt_blkno = rc;
}
if (copy_to_user((char __user *) data, &get, sizeof(get)) != 0)
return -EFAULT;
return 0;
}
/* Try the discipline ioctl function. */
if (device->discipline->ioctl_fn == NULL)
return -EINVAL;
return device->discipline->ioctl_fn(device, no, data);
}
/*
* Initialize character device frontend.
*/
int
tapechar_init (void)
{
dev_t dev;
if (alloc_chrdev_region(&dev, 0, 256, "tape") != 0)
return -1;
tapechar_major = MAJOR(dev);
PRINT_INFO("tape gets major %d for character devices\n", MAJOR(dev));
return 0;
}
/*
* cleanup
*/
void
tapechar_exit(void)
{
PRINT_INFO("tape releases major %d for character devices\n",
tapechar_major);
unregister_chrdev_region(MKDEV(tapechar_major, 0), 256);
}

View file

@ -0,0 +1,126 @@
/*
* (C) Copyright IBM Corp. 2004
* tape_class.c ($Revision: 1.8 $)
*
* Tape class device support
*
* Author: Stefan Bader <shbader@de.ibm.com>
* Based on simple class device code by Greg K-H
*/
#include "tape_class.h"
MODULE_AUTHOR("Stefan Bader <[email protected]>");
MODULE_DESCRIPTION(
"(C) Copyright IBM Corp. 2004 All Rights Reserved.\n"
"tape_class.c ($Revision: 1.8 $)"
);
MODULE_LICENSE("GPL");
struct class_simple *tape_class;
/*
* Register a tape device and return a pointer to the cdev structure.
*
* device
* The pointer to the struct device of the physical (base) device.
* drivername
* The pointer to the drivers name for it's character devices.
* dev
* The intended major/minor number. The major number may be 0 to
* get a dynamic major number.
* fops
* The pointer to the drivers file operations for the tape device.
* devname
* The pointer to the name of the character device.
*/
struct tape_class_device *register_tape_dev(
struct device * device,
dev_t dev,
struct file_operations *fops,
char * device_name,
char * mode_name)
{
struct tape_class_device * tcd;
int rc;
char * s;
tcd = kmalloc(sizeof(struct tape_class_device), GFP_KERNEL);
if (!tcd)
return ERR_PTR(-ENOMEM);
memset(tcd, 0, sizeof(struct tape_class_device));
strncpy(tcd->device_name, device_name, TAPECLASS_NAME_LEN);
for (s = strchr(tcd->device_name, '/'); s; s = strchr(s, '/'))
*s = '!';
strncpy(tcd->mode_name, mode_name, TAPECLASS_NAME_LEN);
for (s = strchr(tcd->mode_name, '/'); s; s = strchr(s, '/'))
*s = '!';
tcd->char_device = cdev_alloc();
if (!tcd->char_device) {
rc = -ENOMEM;
goto fail_with_tcd;
}
tcd->char_device->owner = fops->owner;
tcd->char_device->ops = fops;
tcd->char_device->dev = dev;
rc = cdev_add(tcd->char_device, tcd->char_device->dev, 1);
if (rc)
goto fail_with_cdev;
tcd->class_device = class_simple_device_add(
tape_class,
tcd->char_device->dev,
device,
"%s", tcd->device_name
);
sysfs_create_link(
&device->kobj,
&tcd->class_device->kobj,
tcd->mode_name
);
return tcd;
fail_with_cdev:
cdev_del(tcd->char_device);
fail_with_tcd:
kfree(tcd);
return ERR_PTR(rc);
}
EXPORT_SYMBOL(register_tape_dev);
void unregister_tape_dev(struct tape_class_device *tcd)
{
if (tcd != NULL && !IS_ERR(tcd)) {
sysfs_remove_link(
&tcd->class_device->dev->kobj,
tcd->mode_name
);
class_simple_device_remove(tcd->char_device->dev);
cdev_del(tcd->char_device);
kfree(tcd);
}
}
EXPORT_SYMBOL(unregister_tape_dev);
static int __init tape_init(void)
{
tape_class = class_simple_create(THIS_MODULE, "tape390");
return 0;
}
static void __exit tape_exit(void)
{
class_simple_destroy(tape_class);
tape_class = NULL;
}
postcore_initcall(tape_init);
module_exit(tape_exit);

View file

@ -0,0 +1,61 @@
/*
* (C) Copyright IBM Corp. 2004 All Rights Reserved.
* tape_class.h ($Revision: 1.4 $)
*
* Tape class device support
*
* Author: Stefan Bader <shbader@de.ibm.com>
* Based on simple class device code by Greg K-H
*/
#ifndef __TAPE_CLASS_H__
#define __TAPE_CLASS_H__
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/major.h>
#include <linux/kobject.h>
#include <linux/kobj_map.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/kdev_t.h>
#define TAPECLASS_NAME_LEN 32
struct tape_class_device {
struct cdev * char_device;
struct class_device * class_device;
char device_name[TAPECLASS_NAME_LEN];
char mode_name[TAPECLASS_NAME_LEN];
};
/*
* Register a tape device and return a pointer to the tape class device
* created by the call.
*
* device
* The pointer to the struct device of the physical (base) device.
* dev
* The intended major/minor number. The major number may be 0 to
* get a dynamic major number.
* fops
* The pointer to the drivers file operations for the tape device.
* device_name
* Pointer to the logical device name (will also be used as kobject name
* of the cdev). This can also be called the name of the tape class
* device.
* mode_name
* Points to the name of the tape mode. This creates a link with that
* name from the physical device to the logical device (class).
*/
struct tape_class_device *register_tape_dev(
struct device * device,
dev_t dev,
struct file_operations *fops,
char * device_name,
char * node_name
);
void unregister_tape_dev(struct tape_class_device *tcd);
#endif /* __TAPE_CLASS_H__ */

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,145 @@
/*
* drivers/s390/char/tape.c
* tape device driver for S/390 and zSeries tapes.
*
* S390 and zSeries version
* Copyright (C) 2001 IBM Corporation
* Author(s): Carsten Otte <cotte@de.ibm.com>
* Michael Holzheu <holzheu@de.ibm.com>
* Tuan Ngo-Anh <ngoanh@de.ibm.com>
*
* PROCFS Functions
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/vmalloc.h>
#include <linux/seq_file.h>
#define TAPE_DBF_AREA tape_core_dbf
#include "tape.h"
#define PRINTK_HEADER "TAPE_PROC: "
static const char *tape_med_st_verbose[MS_SIZE] =
{
[MS_UNKNOWN] = "UNKNOWN ",
[MS_LOADED] = "LOADED ",
[MS_UNLOADED] = "UNLOADED"
};
/* our proc tapedevices entry */
static struct proc_dir_entry *tape_proc_devices;
/*
* Show function for /proc/tapedevices
*/
static int tape_proc_show(struct seq_file *m, void *v)
{
struct tape_device *device;
struct tape_request *request;
const char *str;
unsigned long n;
n = (unsigned long) v - 1;
if (!n) {
seq_printf(m, "TapeNo\tBusID CuType/Model\t"
"DevType/Model\tBlkSize\tState\tOp\tMedState\n");
}
device = tape_get_device(n);
if (IS_ERR(device))
return 0;
spin_lock_irq(get_ccwdev_lock(device->cdev));
seq_printf(m, "%d\t", (int) n);
seq_printf(m, "%-10.10s ", device->cdev->dev.bus_id);
seq_printf(m, "%04X/", device->cdev->id.cu_type);
seq_printf(m, "%02X\t", device->cdev->id.cu_model);
seq_printf(m, "%04X/", device->cdev->id.dev_type);
seq_printf(m, "%02X\t\t", device->cdev->id.dev_model);
if (device->char_data.block_size == 0)
seq_printf(m, "auto\t");
else
seq_printf(m, "%i\t", device->char_data.block_size);
if (device->tape_state >= 0 &&
device->tape_state < TS_SIZE)
str = tape_state_verbose[device->tape_state];
else
str = "UNKNOWN";
seq_printf(m, "%s\t", str);
if (!list_empty(&device->req_queue)) {
request = list_entry(device->req_queue.next,
struct tape_request, list);
str = tape_op_verbose[request->op];
} else
str = "---";
seq_printf(m, "%s\t", str);
seq_printf(m, "%s\n", tape_med_st_verbose[device->medium_state]);
spin_unlock_irq(get_ccwdev_lock(device->cdev));
tape_put_device(device);
return 0;
}
static void *tape_proc_start(struct seq_file *m, loff_t *pos)
{
if (*pos >= 256 / TAPE_MINORS_PER_DEV)
return NULL;
return (void *)((unsigned long) *pos + 1);
}
static void *tape_proc_next(struct seq_file *m, void *v, loff_t *pos)
{
++*pos;
return tape_proc_start(m, pos);
}
static void tape_proc_stop(struct seq_file *m, void *v)
{
}
static struct seq_operations tape_proc_seq = {
.start = tape_proc_start,
.next = tape_proc_next,
.stop = tape_proc_stop,
.show = tape_proc_show,
};
static int tape_proc_open(struct inode *inode, struct file *file)
{
return seq_open(file, &tape_proc_seq);
}
static struct file_operations tape_proc_ops =
{
.open = tape_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = seq_release,
};
/*
* Initialize procfs stuff on startup
*/
void
tape_proc_init(void)
{
tape_proc_devices =
create_proc_entry ("tapedevices", S_IFREG | S_IRUGO | S_IWUSR,
&proc_root);
if (tape_proc_devices == NULL) {
PRINT_WARN("tape: Cannot register procfs entry tapedevices\n");
return;
}
tape_proc_devices->proc_fops = &tape_proc_ops;
tape_proc_devices->owner = THIS_MODULE;
}
/*
* Cleanup all stuff registered to the procfs
*/
void
tape_proc_cleanup(void)
{
if (tape_proc_devices != NULL)
remove_proc_entry ("tapedevices", &proc_root);
}

View file

@ -0,0 +1,765 @@
/*
* drivers/s390/char/tape_std.c
* standard tape device functions for ibm tapes.
*
* S390 and zSeries version
* Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Carsten Otte <cotte@de.ibm.com>
* Michael Holzheu <holzheu@de.ibm.com>
* Tuan Ngo-Anh <ngoanh@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
* Stefan Bader <shbader@de.ibm.com>
*/
#include <linux/config.h>
#include <linux/stddef.h>
#include <linux/kernel.h>
#include <linux/bio.h>
#include <linux/timer.h>
#include <asm/types.h>
#include <asm/idals.h>
#include <asm/ebcdic.h>
#include <asm/tape390.h>
#define TAPE_DBF_AREA tape_core_dbf
#include "tape.h"
#include "tape_std.h"
#define PRINTK_HEADER "TAPE_STD: "
/*
* tape_std_assign
*/
static void
tape_std_assign_timeout(unsigned long data)
{
struct tape_request * request;
struct tape_device * device;
request = (struct tape_request *) data;
if ((device = request->device) == NULL)
BUG();
spin_lock_irq(get_ccwdev_lock(device->cdev));
if (request->callback != NULL) {
DBF_EVENT(3, "%08x: Assignment timeout. Device busy.\n",
device->cdev_id);
PRINT_ERR("%s: Assignment timeout. Device busy.\n",
device->cdev->dev.bus_id);
ccw_device_clear(device->cdev, (long) request);
}
spin_unlock_irq(get_ccwdev_lock(device->cdev));
}
int
tape_std_assign(struct tape_device *device)
{
int rc;
struct timer_list timeout;
struct tape_request *request;
request = tape_alloc_request(2, 11);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_ASSIGN;
tape_ccw_cc(request->cpaddr, ASSIGN, 11, request->cpdata);
tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
/*
* The assign command sometimes blocks if the device is assigned
* to another host (actually this shouldn't happen but it does).
* So we set up a timeout for this call.
*/
init_timer(&timeout);
timeout.function = tape_std_assign_timeout;
timeout.data = (unsigned long) request;
timeout.expires = jiffies + 2 * HZ;
add_timer(&timeout);
rc = tape_do_io_interruptible(device, request);
del_timer(&timeout);
if (rc != 0) {
PRINT_WARN("%s: assign failed - device might be busy\n",
device->cdev->dev.bus_id);
DBF_EVENT(3, "%08x: assign failed - device might be busy\n",
device->cdev_id);
} else {
DBF_EVENT(3, "%08x: Tape assigned\n", device->cdev_id);
}
tape_free_request(request);
return rc;
}
/*
* tape_std_unassign
*/
int
tape_std_unassign (struct tape_device *device)
{
int rc;
struct tape_request *request;
if (device->tape_state == TS_NOT_OPER) {
DBF_EVENT(3, "(%08x): Can't unassign device\n",
device->cdev_id);
PRINT_WARN("(%s): Can't unassign device - device gone\n",
device->cdev->dev.bus_id);
return -EIO;
}
request = tape_alloc_request(2, 11);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_UNASSIGN;
tape_ccw_cc(request->cpaddr, UNASSIGN, 11, request->cpdata);
tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
if ((rc = tape_do_io(device, request)) != 0) {
DBF_EVENT(3, "%08x: Unassign failed\n", device->cdev_id);
PRINT_WARN("%s: Unassign failed\n", device->cdev->dev.bus_id);
} else {
DBF_EVENT(3, "%08x: Tape unassigned\n", device->cdev_id);
}
tape_free_request(request);
return rc;
}
/*
* TAPE390_DISPLAY: Show a string on the tape display.
*/
int
tape_std_display(struct tape_device *device, struct display_struct *disp)
{
struct tape_request *request;
int rc;
request = tape_alloc_request(2, 17);
if (IS_ERR(request)) {
DBF_EVENT(3, "TAPE: load display failed\n");
return PTR_ERR(request);
}
request->op = TO_DIS;
*(unsigned char *) request->cpdata = disp->cntrl;
DBF_EVENT(5, "TAPE: display cntrl=%04x\n", disp->cntrl);
memcpy(((unsigned char *) request->cpdata) + 1, disp->message1, 8);
memcpy(((unsigned char *) request->cpdata) + 9, disp->message2, 8);
ASCEBC(((unsigned char*) request->cpdata) + 1, 16);
tape_ccw_cc(request->cpaddr, LOAD_DISPLAY, 17, request->cpdata);
tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
rc = tape_do_io_interruptible(device, request);
tape_free_request(request);
return rc;
}
/*
* Read block id.
*/
int
tape_std_read_block_id(struct tape_device *device, __u64 *id)
{
struct tape_request *request;
int rc;
request = tape_alloc_request(3, 8);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_RBI;
/* setup ccws */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, READ_BLOCK_ID, 8, request->cpdata);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
/* execute it */
rc = tape_do_io(device, request);
if (rc == 0)
/* Get result from read buffer. */
*id = *(__u64 *) request->cpdata;
tape_free_request(request);
return rc;
}
int
tape_std_terminate_write(struct tape_device *device)
{
int rc;
if(device->required_tapemarks == 0)
return 0;
DBF_LH(5, "tape%d: terminate write %dxEOF\n", device->first_minor,
device->required_tapemarks);
rc = tape_mtop(device, MTWEOF, device->required_tapemarks);
if (rc)
return rc;
device->required_tapemarks = 0;
return tape_mtop(device, MTBSR, 1);
}
/*
* MTLOAD: Loads the tape.
* The default implementation just wait until the tape medium state changes
* to MS_LOADED.
*/
int
tape_std_mtload(struct tape_device *device, int count)
{
return wait_event_interruptible(device->state_change_wq,
(device->medium_state == MS_LOADED));
}
/*
* MTSETBLK: Set block size.
*/
int
tape_std_mtsetblk(struct tape_device *device, int count)
{
struct idal_buffer *new;
DBF_LH(6, "tape_std_mtsetblk(%d)\n", count);
if (count <= 0) {
/*
* Just set block_size to 0. tapechar_read/tapechar_write
* will realloc the idal buffer if a bigger one than the
* current is needed.
*/
device->char_data.block_size = 0;
return 0;
}
if (device->char_data.idal_buf != NULL &&
device->char_data.idal_buf->size == count)
/* We already have a idal buffer of that size. */
return 0;
if (count > MAX_BLOCKSIZE) {
DBF_EVENT(3, "Invalid block size (%d > %d) given.\n",
count, MAX_BLOCKSIZE);
PRINT_ERR("Invalid block size (%d > %d) given.\n",
count, MAX_BLOCKSIZE);
return -EINVAL;
}
/* Allocate a new idal buffer. */
new = idal_buffer_alloc(count, 0);
if (new == NULL)
return -ENOMEM;
if (device->char_data.idal_buf != NULL)
idal_buffer_free(device->char_data.idal_buf);
device->char_data.idal_buf = new;
device->char_data.block_size = count;
DBF_LH(6, "new blocksize is %d\n", device->char_data.block_size);
return 0;
}
/*
* MTRESET: Set block size to 0.
*/
int
tape_std_mtreset(struct tape_device *device, int count)
{
DBF_EVENT(6, "TCHAR:devreset:\n");
device->char_data.block_size = 0;
return 0;
}
/*
* MTFSF: Forward space over 'count' file marks. The tape is positioned
* at the EOT (End of Tape) side of the file mark.
*/
int
tape_std_mtfsf(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_FSF;
/* setup ccws */
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, FORSPACEFILE, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
/*
* MTFSR: Forward space over 'count' tape blocks (blocksize is set
* via MTSETBLK.
*/
int
tape_std_mtfsr(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
int rc;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_FSB;
/* setup ccws */
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, FORSPACEBLOCK, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
rc = tape_do_io(device, request);
if (rc == 0 && request->rescnt > 0) {
DBF_LH(3, "FSR over tapemark\n");
rc = 1;
}
tape_free_request(request);
return rc;
}
/*
* MTBSR: Backward space over 'count' tape blocks.
* (blocksize is set via MTSETBLK.
*/
int
tape_std_mtbsr(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
int rc;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_BSB;
/* setup ccws */
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, BACKSPACEBLOCK, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
rc = tape_do_io(device, request);
if (rc == 0 && request->rescnt > 0) {
DBF_LH(3, "BSR over tapemark\n");
rc = 1;
}
tape_free_request(request);
return rc;
}
/*
* MTWEOF: Write 'count' file marks at the current position.
*/
int
tape_std_mtweof(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_WTM;
/* setup ccws */
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, WRITETAPEMARK, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
/*
* MTBSFM: Backward space over 'count' file marks.
* The tape is positioned at the BOT (Begin Of Tape) side of the
* last skipped file mark.
*/
int
tape_std_mtbsfm(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_BSF;
/* setup ccws */
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, BACKSPACEFILE, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
/*
* MTBSF: Backward space over 'count' file marks. The tape is positioned at
* the EOT (End of Tape) side of the last skipped file mark.
*/
int
tape_std_mtbsf(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
int rc;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_BSF;
/* setup ccws */
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, BACKSPACEFILE, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
rc = tape_do_io_free(device, request);
if (rc == 0) {
rc = tape_mtop(device, MTFSR, 1);
if (rc > 0)
rc = 0;
}
return rc;
}
/*
* MTFSFM: Forward space over 'count' file marks.
* The tape is positioned at the BOT (Begin Of Tape) side
* of the last skipped file mark.
*/
int
tape_std_mtfsfm(struct tape_device *device, int mt_count)
{
struct tape_request *request;
struct ccw1 *ccw;
int rc;
request = tape_alloc_request(mt_count + 2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_FSF;
/* setup ccws */
ccw = tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
ccw = tape_ccw_repeat(ccw, FORSPACEFILE, mt_count);
ccw = tape_ccw_end(ccw, NOP, 0, NULL);
/* execute it */
rc = tape_do_io_free(device, request);
if (rc == 0) {
rc = tape_mtop(device, MTBSR, 1);
if (rc > 0)
rc = 0;
}
return rc;
}
/*
* MTREW: Rewind the tape.
*/
int
tape_std_mtrew(struct tape_device *device, int mt_count)
{
struct tape_request *request;
request = tape_alloc_request(3, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_REW;
/* setup ccws */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1,
device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, REWIND, 0, NULL);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
/*
* MTOFFL: Rewind the tape and put the drive off-line.
* Implement 'rewind unload'
*/
int
tape_std_mtoffl(struct tape_device *device, int mt_count)
{
struct tape_request *request;
request = tape_alloc_request(3, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_RUN;
/* setup ccws */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, REWIND_UNLOAD, 0, NULL);
tape_ccw_end(request->cpaddr + 2, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
/*
* MTNOP: 'No operation'.
*/
int
tape_std_mtnop(struct tape_device *device, int mt_count)
{
struct tape_request *request;
request = tape_alloc_request(2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_NOP;
/* setup ccws */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
/*
* MTEOM: positions at the end of the portion of the tape already used
* for recordind data. MTEOM positions after the last file mark, ready for
* appending another file.
*/
int
tape_std_mteom(struct tape_device *device, int mt_count)
{
int rc;
/*
* Seek from the beginning of tape (rewind).
*/
if ((rc = tape_mtop(device, MTREW, 1)) < 0)
return rc;
/*
* The logical end of volume is given by two sewuential tapemarks.
* Look for this by skipping to the next file (over one tapemark)
* and then test for another one (fsr returns 1 if a tapemark was
* encountered).
*/
do {
if ((rc = tape_mtop(device, MTFSF, 1)) < 0)
return rc;
if ((rc = tape_mtop(device, MTFSR, 1)) < 0)
return rc;
} while (rc == 0);
return tape_mtop(device, MTBSR, 1);
}
/*
* MTRETEN: Retension the tape, i.e. forward space to end of tape and rewind.
*/
int
tape_std_mtreten(struct tape_device *device, int mt_count)
{
struct tape_request *request;
int rc;
request = tape_alloc_request(4, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_FSF;
/* setup ccws */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1,FORSPACEFILE, 0, NULL);
tape_ccw_cc(request->cpaddr + 2, NOP, 0, NULL);
tape_ccw_end(request->cpaddr + 3, CCW_CMD_TIC, 0, request->cpaddr);
/* execute it, MTRETEN rc gets ignored */
rc = tape_do_io_interruptible(device, request);
tape_free_request(request);
return tape_mtop(device, MTREW, 1);
}
/*
* MTERASE: erases the tape.
*/
int
tape_std_mterase(struct tape_device *device, int mt_count)
{
struct tape_request *request;
request = tape_alloc_request(6, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_DSE;
/* setup ccws */
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc(request->cpaddr + 1, REWIND, 0, NULL);
tape_ccw_cc(request->cpaddr + 2, ERASE_GAP, 0, NULL);
tape_ccw_cc(request->cpaddr + 3, DATA_SEC_ERASE, 0, NULL);
tape_ccw_cc(request->cpaddr + 4, REWIND, 0, NULL);
tape_ccw_end(request->cpaddr + 5, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
/*
* MTUNLOAD: Rewind the tape and unload it.
*/
int
tape_std_mtunload(struct tape_device *device, int mt_count)
{
return tape_mtop(device, MTOFFL, mt_count);
}
/*
* MTCOMPRESSION: used to enable compression.
* Sets the IDRC on/off.
*/
int
tape_std_mtcompression(struct tape_device *device, int mt_count)
{
struct tape_request *request;
if (mt_count < 0 || mt_count > 1) {
DBF_EXCEPTION(6, "xcom parm\n");
if (*device->modeset_byte & 0x08)
PRINT_INFO("(%s) Compression is currently on\n",
device->cdev->dev.bus_id);
else
PRINT_INFO("(%s) Compression is currently off\n",
device->cdev->dev.bus_id);
PRINT_INFO("Use 1 to switch compression on, 0 to "
"switch it off\n");
return -EINVAL;
}
request = tape_alloc_request(2, 0);
if (IS_ERR(request))
return PTR_ERR(request);
request->op = TO_NOP;
/* setup ccws */
*device->modeset_byte = (mt_count == 0) ? 0x00 : 0x08;
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_end(request->cpaddr + 1, NOP, 0, NULL);
/* execute it */
return tape_do_io_free(device, request);
}
/*
* Read Block
*/
struct tape_request *
tape_std_read_block(struct tape_device *device, size_t count)
{
struct tape_request *request;
/*
* We have to alloc 4 ccws in order to be able to transform request
* into a read backward request in error case.
*/
request = tape_alloc_request(4, 0);
if (IS_ERR(request)) {
DBF_EXCEPTION(6, "xrbl fail");
return request;
}
request->op = TO_RFO;
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_end_idal(request->cpaddr + 1, READ_FORWARD,
device->char_data.idal_buf);
DBF_EVENT(6, "xrbl ccwg\n");
return request;
}
/*
* Read Block backward transformation function.
*/
void
tape_std_read_backward(struct tape_device *device, struct tape_request *request)
{
/*
* We have allocated 4 ccws in tape_std_read, so we can now
* transform the request to a read backward, followed by a
* forward space block.
*/
request->op = TO_RBA;
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_cc_idal(request->cpaddr + 1, READ_BACKWARD,
device->char_data.idal_buf);
tape_ccw_cc(request->cpaddr + 2, FORSPACEBLOCK, 0, NULL);
tape_ccw_end(request->cpaddr + 3, NOP, 0, NULL);
DBF_EVENT(6, "xrop ccwg");}
/*
* Write Block
*/
struct tape_request *
tape_std_write_block(struct tape_device *device, size_t count)
{
struct tape_request *request;
request = tape_alloc_request(2, 0);
if (IS_ERR(request)) {
DBF_EXCEPTION(6, "xwbl fail\n");
return request;
}
request->op = TO_WRI;
tape_ccw_cc(request->cpaddr, MODE_SET_DB, 1, device->modeset_byte);
tape_ccw_end_idal(request->cpaddr + 1, WRITE_CMD,
device->char_data.idal_buf);
DBF_EVENT(6, "xwbl ccwg\n");
return request;
}
/*
* This routine is called by frontend after an ENOSP on write
*/
void
tape_std_process_eov(struct tape_device *device)
{
/*
* End of volume: We have to backspace the last written record, then
* we TRY to write a tapemark and then backspace over the written TM
*/
if (tape_mtop(device, MTBSR, 1) == 0 &&
tape_mtop(device, MTWEOF, 1) == 0) {
tape_mtop(device, MTBSR, 1);
}
}
EXPORT_SYMBOL(tape_std_assign);
EXPORT_SYMBOL(tape_std_unassign);
EXPORT_SYMBOL(tape_std_display);
EXPORT_SYMBOL(tape_std_read_block_id);
EXPORT_SYMBOL(tape_std_mtload);
EXPORT_SYMBOL(tape_std_mtsetblk);
EXPORT_SYMBOL(tape_std_mtreset);
EXPORT_SYMBOL(tape_std_mtfsf);
EXPORT_SYMBOL(tape_std_mtfsr);
EXPORT_SYMBOL(tape_std_mtbsr);
EXPORT_SYMBOL(tape_std_mtweof);
EXPORT_SYMBOL(tape_std_mtbsfm);
EXPORT_SYMBOL(tape_std_mtbsf);
EXPORT_SYMBOL(tape_std_mtfsfm);
EXPORT_SYMBOL(tape_std_mtrew);
EXPORT_SYMBOL(tape_std_mtoffl);
EXPORT_SYMBOL(tape_std_mtnop);
EXPORT_SYMBOL(tape_std_mteom);
EXPORT_SYMBOL(tape_std_mtreten);
EXPORT_SYMBOL(tape_std_mterase);
EXPORT_SYMBOL(tape_std_mtunload);
EXPORT_SYMBOL(tape_std_mtcompression);
EXPORT_SYMBOL(tape_std_read_block);
EXPORT_SYMBOL(tape_std_read_backward);
EXPORT_SYMBOL(tape_std_write_block);
EXPORT_SYMBOL(tape_std_process_eov);

View file

@ -0,0 +1,152 @@
/*
* drivers/s390/char/tape_34xx.h
* standard tape device functions for ibm tapes.
*
* S390 and zSeries version
* Copyright (C) 2001,2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Carsten Otte <cotte@de.ibm.com>
* Tuan Ngo-Anh <ngoanh@de.ibm.com>
* Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#ifndef _TAPE_STD_H
#define _TAPE_STD_H
#include <asm/tape390.h>
/*
* Biggest block size to handle. Currently 64K because we only build
* channel programs without data chaining.
*/
#define MAX_BLOCKSIZE 65535
/*
* The CCW commands for the Tape type of command.
*/
#define INVALID_00 0x00 /* Invalid cmd */
#define BACKSPACEBLOCK 0x27 /* Back Space block */
#define BACKSPACEFILE 0x2f /* Back Space file */
#define DATA_SEC_ERASE 0x97 /* Data security erase */
#define ERASE_GAP 0x17 /* Erase Gap */
#define FORSPACEBLOCK 0x37 /* Forward space block */
#define FORSPACEFILE 0x3F /* Forward Space file */
#define FORCE_STREAM_CNT 0xEB /* Forced streaming count # */
#define NOP 0x03 /* No operation */
#define READ_FORWARD 0x02 /* Read forward */
#define REWIND 0x07 /* Rewind */
#define REWIND_UNLOAD 0x0F /* Rewind and Unload */
#define SENSE 0x04 /* Sense */
#define NEW_MODE_SET 0xEB /* Guess it is Mode set */
#define WRITE_CMD 0x01 /* Write */
#define WRITETAPEMARK 0x1F /* Write Tape Mark */
#define ASSIGN 0xB7 /* 3420 REJECT,3480 OK */
#define CONTROL_ACCESS 0xE3 /* Set high speed */
#define DIAG_MODE_SET 0x0B /* 3420 NOP, 3480 REJECT */
#define LOAD_DISPLAY 0x9F /* 3420 REJECT,3480 OK */
#define LOCATE 0x4F /* 3420 REJ, 3480 NOP */
#define LOOP_WRITE_TO_READ 0x8B /* 3480 REJECT */
#define MODE_SET_DB 0xDB /* 3420 REJECT,3480 OK */
#define MODE_SET_C3 0xC3 /* for 3420 */
#define MODE_SET_CB 0xCB /* for 3420 */
#define MODE_SET_D3 0xD3 /* for 3420 */
#define READ_BACKWARD 0x0C /* */
#define READ_BLOCK_ID 0x22 /* 3420 REJECT,3480 OK */
#define READ_BUFFER 0x12 /* 3420 REJECT,3480 OK */
#define READ_BUFF_LOG 0x24 /* 3420 REJECT,3480 OK */
#define RELEASE 0xD4 /* 3420 NOP, 3480 REJECT */
#define REQ_TRK_IN_ERROR 0x1B /* 3420 NOP, 3480 REJECT */
#define RESERVE 0xF4 /* 3420 NOP, 3480 REJECT */
#define SENSE_GROUP_ID 0x34 /* 3420 REJECT,3480 OK */
#define SENSE_ID 0xE4 /* 3420 REJECT,3480 OK */
#define READ_DEV_CHAR 0x64 /* Read device characteristics */
#define SET_DIAGNOSE 0x4B /* 3420 NOP, 3480 REJECT */
#define SET_GROUP_ID 0xAF /* 3420 REJECT,3480 OK */
#define SET_TAPE_WRITE_IMMED 0xC3 /* for 3480 */
#define SUSPEND 0x5B /* 3420 REJ, 3480 NOP */
#define SYNC 0x43 /* Synchronize (flush buffer) */
#define UNASSIGN 0xC7 /* 3420 REJECT,3480 OK */
#define PERF_SUBSYS_FUNC 0x77 /* 3490 CMD */
#define READ_CONFIG_DATA 0xFA /* 3490 CMD */
#define READ_MESSAGE_ID 0x4E /* 3490 CMD */
#define READ_SUBSYS_DATA 0x3E /* 3490 CMD */
#define SET_INTERFACE_ID 0x73 /* 3490 CMD */
#define SENSE_COMMAND_REJECT 0x80
#define SENSE_INTERVENTION_REQUIRED 0x40
#define SENSE_BUS_OUT_CHECK 0x20
#define SENSE_EQUIPMENT_CHECK 0x10
#define SENSE_DATA_CHECK 0x08
#define SENSE_OVERRUN 0x04
#define SENSE_DEFERRED_UNIT_CHECK 0x02
#define SENSE_ASSIGNED_ELSEWHERE 0x01
#define SENSE_LOCATE_FAILURE 0x80
#define SENSE_DRIVE_ONLINE 0x40
#define SENSE_RESERVED 0x20
#define SENSE_RECORD_SEQUENCE_ERR 0x10
#define SENSE_BEGINNING_OF_TAPE 0x08
#define SENSE_WRITE_MODE 0x04
#define SENSE_WRITE_PROTECT 0x02
#define SENSE_NOT_CAPABLE 0x01
#define SENSE_CHANNEL_ADAPTER_CODE 0xE0
#define SENSE_CHANNEL_ADAPTER_LOC 0x10
#define SENSE_REPORTING_CU 0x08
#define SENSE_AUTOMATIC_LOADER 0x04
#define SENSE_TAPE_SYNC_MODE 0x02
#define SENSE_TAPE_POSITIONING 0x01
/* discipline functions */
struct tape_request *tape_std_read_block(struct tape_device *, size_t);
void tape_std_read_backward(struct tape_device *device,
struct tape_request *request);
struct tape_request *tape_std_write_block(struct tape_device *, size_t);
struct tape_request *tape_std_bread(struct tape_device *, struct request *);
void tape_std_free_bread(struct tape_request *);
void tape_std_check_locate(struct tape_device *, struct tape_request *);
struct tape_request *tape_std_bwrite(struct request *,
struct tape_device *, int);
/* Some non-mtop commands. */
int tape_std_assign(struct tape_device *);
int tape_std_unassign(struct tape_device *);
int tape_std_read_block_id(struct tape_device *device, __u64 *id);
int tape_std_display(struct tape_device *, struct display_struct *disp);
int tape_std_terminate_write(struct tape_device *);
/* Standard magnetic tape commands. */
int tape_std_mtbsf(struct tape_device *, int);
int tape_std_mtbsfm(struct tape_device *, int);
int tape_std_mtbsr(struct tape_device *, int);
int tape_std_mtcompression(struct tape_device *, int);
int tape_std_mteom(struct tape_device *, int);
int tape_std_mterase(struct tape_device *, int);
int tape_std_mtfsf(struct tape_device *, int);
int tape_std_mtfsfm(struct tape_device *, int);
int tape_std_mtfsr(struct tape_device *, int);
int tape_std_mtload(struct tape_device *, int);
int tape_std_mtnop(struct tape_device *, int);
int tape_std_mtoffl(struct tape_device *, int);
int tape_std_mtreset(struct tape_device *, int);
int tape_std_mtreten(struct tape_device *, int);
int tape_std_mtrew(struct tape_device *, int);
int tape_std_mtsetblk(struct tape_device *, int);
int tape_std_mtunload(struct tape_device *, int);
int tape_std_mtweof(struct tape_device *, int);
/* Event handlers */
void tape_std_default_handler(struct tape_device *);
void tape_std_unexpect_uchk_handler(struct tape_device *);
void tape_std_irq(struct tape_device *);
void tape_std_process_eov(struct tape_device *);
// the error recovery stuff:
void tape_std_error_recovery(struct tape_device *);
void tape_std_error_recovery_has_failed(struct tape_device *,int error_id);
void tape_std_error_recovery_succeded(struct tape_device *);
void tape_std_error_recovery_do_retry(struct tape_device *);
void tape_std_error_recovery_read_opposite(struct tape_device *);
void tape_std_error_recovery_HWBUG(struct tape_device *, int condno);
#endif // _TAPE_STD_H

1836
drivers/s390/char/tty3270.c Normal file

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,920 @@
/*
* drivers/s390/char/vmlogrdr.c
* character device driver for reading z/VM system service records
*
*
* Copyright (C) 2004 IBM Corporation
* character device driver for reading z/VM system service records,
* Version 1.0
* Author(s): Xenia Tkatschow <xenia@us.ibm.com>
* Stefan Weinhuber <wein@de.ibm.com>
*
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/types.h>
#include <linux/interrupt.h>
#include <linux/spinlock.h>
#include <asm/atomic.h>
#include <asm/uaccess.h>
#include <asm/cpcmd.h>
#include <asm/debug.h>
#include <asm/ebcdic.h>
#include "../net/iucv.h"
#include <linux/kmod.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/string.h>
MODULE_AUTHOR
("(C) 2004 IBM Corporation by Xenia Tkatschow ([email protected])\n"
" Stefan Weinhuber ([email protected])");
MODULE_DESCRIPTION ("Character device driver for reading z/VM "
"system service records.");
MODULE_LICENSE("GPL");
/*
* The size of the buffer for iucv data transfer is one page,
* but in addition to the data we read from iucv we also
* place an integer and some characters into that buffer,
* so the maximum size for record data is a little less then
* one page.
*/
#define NET_BUFFER_SIZE (PAGE_SIZE - sizeof(int) - sizeof(FENCE))
/*
* The elements that are concurrently accessed by bottom halves are
* connection_established, iucv_path_severed, local_interrupt_buffer
* and receive_ready. The first three can be protected by
* priv_lock. receive_ready is atomic, so it can be incremented and
* decremented without holding a lock.
* The variable dev_in_use needs to be protected by the lock, since
* it's a flag used by open to make sure that the device is opened only
* by one user at the same time.
*/
struct vmlogrdr_priv_t {
char system_service[8];
char internal_name[8];
char recording_name[8];
u16 pathid;
int connection_established;
int iucv_path_severed;
iucv_MessagePending local_interrupt_buffer;
atomic_t receive_ready;
iucv_handle_t iucv_handle;
int minor_num;
char * buffer;
char * current_position;
int remaining;
ulong residual_length;
int buffer_free;
int dev_in_use; /* 1: already opened, 0: not opened*/
spinlock_t priv_lock;
struct device *device;
struct class_device *class_device;
int autorecording;
int autopurge;
};
/*
* File operation structure for vmlogrdr devices
*/
static int vmlogrdr_open(struct inode *, struct file *);
static int vmlogrdr_release(struct inode *, struct file *);
static ssize_t vmlogrdr_read (struct file *filp, char *data, size_t count,
loff_t * ppos);
static struct file_operations vmlogrdr_fops = {
.owner = THIS_MODULE,
.open = vmlogrdr_open,
.release = vmlogrdr_release,
.read = vmlogrdr_read,
};
static u8 iucvMagic[16] = {
0xF0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,
0xF0, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40
};
static u8 mask[] = {
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff
};
static u8 iucv_host[8] = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
static void
vmlogrdr_iucv_ConnectionComplete(iucv_ConnectionComplete *eib, void *pgm_data);
static void
vmlogrdr_iucv_ConnectionSevered(iucv_ConnectionSevered *eib, void *pgm_data);
static void
vmlogrdr_iucv_MessagePending(iucv_MessagePending *eib, void *pgm_data);
static iucv_interrupt_ops_t vmlogrdr_iucvops = {
.ConnectionComplete = vmlogrdr_iucv_ConnectionComplete,
.ConnectionSevered = vmlogrdr_iucv_ConnectionSevered,
.MessagePending = vmlogrdr_iucv_MessagePending,
};
DECLARE_WAIT_QUEUE_HEAD(conn_wait_queue);
DECLARE_WAIT_QUEUE_HEAD(read_wait_queue);
/*
* pointer to system service private structure
* minor number 0 --> logrec
* minor number 1 --> account
* minor number 2 --> symptom
*/
static struct vmlogrdr_priv_t sys_ser[] = {
{ .system_service = "*LOGREC ",
.internal_name = "logrec",
.recording_name = "EREP",
.minor_num = 0,
.buffer_free = 1,
.priv_lock = SPIN_LOCK_UNLOCKED,
.autorecording = 1,
.autopurge = 1,
},
{ .system_service = "*ACCOUNT",
.internal_name = "account",
.recording_name = "ACCOUNT",
.minor_num = 1,
.buffer_free = 1,
.priv_lock = SPIN_LOCK_UNLOCKED,
.autorecording = 1,
.autopurge = 1,
},
{ .system_service = "*SYMPTOM",
.internal_name = "symptom",
.recording_name = "SYMPTOM",
.minor_num = 2,
.buffer_free = 1,
.priv_lock = SPIN_LOCK_UNLOCKED,
.autorecording = 1,
.autopurge = 1,
}
};
#define MAXMINOR (sizeof(sys_ser)/sizeof(struct vmlogrdr_priv_t))
static char FENCE[] = {"EOR"};
static int vmlogrdr_major = 0;
static struct cdev *vmlogrdr_cdev = NULL;
static int recording_class_AB;
static void
vmlogrdr_iucv_ConnectionComplete (iucv_ConnectionComplete * eib,
void * pgm_data)
{
struct vmlogrdr_priv_t * logptr = pgm_data;
spin_lock(&logptr->priv_lock);
logptr->connection_established = 1;
spin_unlock(&logptr->priv_lock);
wake_up(&conn_wait_queue);
return;
}
static void
vmlogrdr_iucv_ConnectionSevered (iucv_ConnectionSevered * eib, void * pgm_data)
{
u8 reason = (u8) eib->ipuser[8];
struct vmlogrdr_priv_t * logptr = pgm_data;
printk (KERN_ERR "vmlogrdr: connection severed with"
" reason %i\n", reason);
spin_lock(&logptr->priv_lock);
logptr->connection_established = 0;
logptr->iucv_path_severed = 1;
spin_unlock(&logptr->priv_lock);
wake_up(&conn_wait_queue);
/* just in case we're sleeping waiting for a record */
wake_up_interruptible(&read_wait_queue);
}
static void
vmlogrdr_iucv_MessagePending (iucv_MessagePending * eib, void * pgm_data)
{
struct vmlogrdr_priv_t * logptr = pgm_data;
/*
* This function is the bottom half so it should be quick.
* Copy the external interrupt data into our local eib and increment
* the usage count
*/
spin_lock(&logptr->priv_lock);
memcpy(&(logptr->local_interrupt_buffer), eib, sizeof(*eib));
atomic_inc(&logptr->receive_ready);
spin_unlock(&logptr->priv_lock);
wake_up_interruptible(&read_wait_queue);
}
static int
vmlogrdr_get_recording_class_AB(void) {
char cp_command[]="QUERY COMMAND RECORDING ";
char cp_response[80];
char *tail;
int len,i;
printk (KERN_DEBUG "vmlogrdr: query command: %s\n", cp_command);
cpcmd(cp_command, cp_response, sizeof(cp_response));
printk (KERN_DEBUG "vmlogrdr: response: %s", cp_response);
len = strnlen(cp_response,sizeof(cp_response));
// now the parsing
tail=strnchr(cp_response,len,'=');
if (!tail)
return 0;
tail++;
if (!strncmp("ANY",tail,3))
return 1;
if (!strncmp("NONE",tail,4))
return 0;
/*
* expect comma separated list of classes here, if one of them
* is A or B return 1 otherwise 0
*/
for (i=tail-cp_response; i<len; i++)
if ( cp_response[i]=='A' || cp_response[i]=='B' )
return 1;
return 0;
}
static int
vmlogrdr_recording(struct vmlogrdr_priv_t * logptr, int action, int purge) {
char cp_command[80];
char cp_response[160];
char *onoff, *qid_string;
memset(cp_command, 0x00, sizeof(cp_command));
memset(cp_response, 0x00, sizeof(cp_response));
onoff = ((action == 1) ? "ON" : "OFF");
qid_string = ((recording_class_AB == 1) ? " QID * " : "");
/*
* The recording commands needs to be called with option QID
* for guests that have previlege classes A or B.
* Purging has to be done as separate step, because recording
* can't be switched on as long as records are on the queue.
* Doing both at the same time doesn't work.
*/
if (purge) {
snprintf(cp_command, sizeof(cp_command),
"RECORDING %s PURGE %s",
logptr->recording_name,
qid_string);
printk (KERN_DEBUG "vmlogrdr: recording command: %s\n",
cp_command);
cpcmd(cp_command, cp_response, sizeof(cp_response));
printk (KERN_DEBUG "vmlogrdr: recording response: %s",
cp_response);
}
memset(cp_command, 0x00, sizeof(cp_command));
memset(cp_response, 0x00, sizeof(cp_response));
snprintf(cp_command, sizeof(cp_command), "RECORDING %s %s %s",
logptr->recording_name,
onoff,
qid_string);
printk (KERN_DEBUG "vmlogrdr: recording command: %s\n", cp_command);
cpcmd(cp_command, cp_response, sizeof(cp_response));
printk (KERN_DEBUG "vmlogrdr: recording response: %s",
cp_response);
/* The recording command will usually answer with 'Command complete'
* on success, but when the specific service was never connected
* before then there might be an additional informational message
* 'HCPCRC8072I Recording entry not found' before the
* 'Command complete'. So I use strstr rather then the strncmp.
*/
if (strstr(cp_response,"Command complete"))
return 0;
else
return -EIO;
}
static int
vmlogrdr_open (struct inode *inode, struct file *filp)
{
int dev_num = 0;
struct vmlogrdr_priv_t * logptr = NULL;
int connect_rc = 0;
int ret;
dev_num = iminor(inode);
if (dev_num > MAXMINOR)
return -ENODEV;
logptr = &sys_ser[dev_num];
if (logptr == NULL)
return -ENODEV;
/*
* only allow for blocking reads to be open
*/
if (filp->f_flags & O_NONBLOCK)
return -ENOSYS;
/* Besure this device hasn't already been opened */
spin_lock_bh(&logptr->priv_lock);
if (logptr->dev_in_use) {
spin_unlock_bh(&logptr->priv_lock);
return -EBUSY;
} else {
logptr->dev_in_use = 1;
spin_unlock_bh(&logptr->priv_lock);
}
atomic_set(&logptr->receive_ready, 0);
logptr->buffer_free = 1;
/* set the file options */
filp->private_data = logptr;
filp->f_op = &vmlogrdr_fops;
/* start recording for this service*/
ret=0;
if (logptr->autorecording)
ret = vmlogrdr_recording(logptr,1,logptr->autopurge);
if (ret)
printk (KERN_WARNING "vmlogrdr: failed to start "
"recording automatically\n");
/* Register with iucv driver */
logptr->iucv_handle = iucv_register_program(iucvMagic,
logptr->system_service, mask, &vmlogrdr_iucvops,
logptr);
if (logptr->iucv_handle == NULL) {
printk (KERN_ERR "vmlogrdr: failed to register with"
"iucv driver\n");
goto not_registered;
}
/* create connection to the system service */
spin_lock_bh(&logptr->priv_lock);
logptr->connection_established = 0;
logptr->iucv_path_severed = 0;
spin_unlock_bh(&logptr->priv_lock);
connect_rc = iucv_connect (&(logptr->pathid), 10, iucvMagic,
logptr->system_service, iucv_host, 0,
NULL, NULL,
logptr->iucv_handle, NULL);
if (connect_rc) {
printk (KERN_ERR "vmlogrdr: iucv connection to %s "
"failed with rc %i \n", logptr->system_service,
connect_rc);
goto not_connected;
}
/* We've issued the connect and now we must wait for a
* ConnectionComplete or ConnectinSevered Interrupt
* before we can continue to process.
*/
wait_event(conn_wait_queue, (logptr->connection_established)
|| (logptr->iucv_path_severed));
if (logptr->iucv_path_severed) {
goto not_connected;
}
return nonseekable_open(inode, filp);
not_connected:
iucv_unregister_program(logptr->iucv_handle);
logptr->iucv_handle = NULL;
not_registered:
if (logptr->autorecording)
vmlogrdr_recording(logptr,0,logptr->autopurge);
logptr->dev_in_use = 0;
return -EIO;
}
static int
vmlogrdr_release (struct inode *inode, struct file *filp)
{
int ret;
struct vmlogrdr_priv_t * logptr = filp->private_data;
iucv_unregister_program(logptr->iucv_handle);
logptr->iucv_handle = NULL;
if (logptr->autorecording) {
ret = vmlogrdr_recording(logptr,0,logptr->autopurge);
if (ret)
printk (KERN_WARNING "vmlogrdr: failed to stop "
"recording automatically\n");
}
logptr->dev_in_use = 0;
return 0;
}
static int
vmlogrdr_receive_data(struct vmlogrdr_priv_t *priv) {
int rc, *temp;
/* we need to keep track of two data sizes here:
* The number of bytes we need to receive from iucv and
* the total number of bytes we actually write into the buffer.
*/
int user_data_count, iucv_data_count;
char * buffer;
if (atomic_read(&priv->receive_ready)) {
spin_lock_bh(&priv->priv_lock);
if (priv->residual_length){
/* receive second half of a record */
iucv_data_count = priv->residual_length;
user_data_count = 0;
buffer = priv->buffer;
} else {
/* receive a new record:
* We need to return the total length of the record
* + size of FENCE in the first 4 bytes of the buffer.
*/
iucv_data_count =
priv->local_interrupt_buffer.ln1msg2.ipbfln1f;
user_data_count = sizeof(int);
temp = (int*)priv->buffer;
*temp= iucv_data_count + sizeof(FENCE);
buffer = priv->buffer + sizeof(int);
}
/*
* If the record is bigger then our buffer, we receive only
* a part of it. We can get the rest later.
*/
if (iucv_data_count > NET_BUFFER_SIZE)
iucv_data_count = NET_BUFFER_SIZE;
rc = iucv_receive(priv->pathid,
priv->local_interrupt_buffer.ipmsgid,
priv->local_interrupt_buffer.iptrgcls,
buffer,
iucv_data_count,
NULL,
NULL,
&priv->residual_length);
spin_unlock_bh(&priv->priv_lock);
/* An rc of 5 indicates that the record was bigger then
* the buffer, which is OK for us. A 9 indicates that the
* record was purged befor we could receive it.
*/
if (rc == 5)
rc = 0;
if (rc == 9)
atomic_set(&priv->receive_ready, 0);
} else {
rc = 1;
}
if (!rc) {
priv->buffer_free = 0;
user_data_count += iucv_data_count;
priv->current_position = priv->buffer;
if (priv->residual_length == 0){
/* the whole record has been captured,
* now add the fence */
atomic_dec(&priv->receive_ready);
buffer = priv->buffer + user_data_count;
memcpy(buffer, FENCE, sizeof(FENCE));
user_data_count += sizeof(FENCE);
}
priv->remaining = user_data_count;
}
return rc;
}
static ssize_t
vmlogrdr_read (struct file *filp, char *data, size_t count, loff_t * ppos)
{
int rc;
struct vmlogrdr_priv_t * priv = filp->private_data;
while (priv->buffer_free) {
rc = vmlogrdr_receive_data(priv);
if (rc) {
rc = wait_event_interruptible(read_wait_queue,
atomic_read(&priv->receive_ready));
if (rc)
return rc;
}
}
/* copy only up to end of record */
if (count > priv->remaining)
count = priv->remaining;
if (copy_to_user(data, priv->current_position, count))
return -EFAULT;
*ppos += count;
priv->current_position += count;
priv->remaining -= count;
/* if all data has been transferred, set buffer free */
if (priv->remaining == 0)
priv->buffer_free = 1;
return count;
}
static ssize_t
vmlogrdr_autopurge_store(struct device * dev, const char * buf, size_t count) {
struct vmlogrdr_priv_t *priv = dev->driver_data;
ssize_t ret = count;
switch (buf[0]) {
case '0':
priv->autopurge=0;
break;
case '1':
priv->autopurge=1;
break;
default:
ret = -EINVAL;
}
return ret;
}
static ssize_t
vmlogrdr_autopurge_show(struct device *dev, char *buf) {
struct vmlogrdr_priv_t *priv = dev->driver_data;
return sprintf(buf, "%u\n", priv->autopurge);
}
static DEVICE_ATTR(autopurge, 0644, vmlogrdr_autopurge_show,
vmlogrdr_autopurge_store);
static ssize_t
vmlogrdr_purge_store(struct device * dev, const char * buf, size_t count) {
char cp_command[80];
char cp_response[80];
struct vmlogrdr_priv_t *priv = dev->driver_data;
if (buf[0] != '1')
return -EINVAL;
memset(cp_command, 0x00, sizeof(cp_command));
memset(cp_response, 0x00, sizeof(cp_response));
/*
* The recording command needs to be called with option QID
* for guests that have previlege classes A or B.
* Other guests will not recognize the command and we have to
* issue the same command without the QID parameter.
*/
if (recording_class_AB)
snprintf(cp_command, sizeof(cp_command),
"RECORDING %s PURGE QID * ",
priv->recording_name);
else
snprintf(cp_command, sizeof(cp_command),
"RECORDING %s PURGE ",
priv->recording_name);
printk (KERN_DEBUG "vmlogrdr: recording command: %s\n", cp_command);
cpcmd(cp_command, cp_response, sizeof(cp_response));
printk (KERN_DEBUG "vmlogrdr: recording response: %s",
cp_response);
return count;
}
static DEVICE_ATTR(purge, 0200, NULL, vmlogrdr_purge_store);
static ssize_t
vmlogrdr_autorecording_store(struct device *dev, const char *buf,
size_t count) {
struct vmlogrdr_priv_t *priv = dev->driver_data;
ssize_t ret = count;
switch (buf[0]) {
case '0':
priv->autorecording=0;
break;
case '1':
priv->autorecording=1;
break;
default:
ret = -EINVAL;
}
return ret;
}
static ssize_t
vmlogrdr_autorecording_show(struct device *dev, char *buf) {
struct vmlogrdr_priv_t *priv = dev->driver_data;
return sprintf(buf, "%u\n", priv->autorecording);
}
static DEVICE_ATTR(autorecording, 0644, vmlogrdr_autorecording_show,
vmlogrdr_autorecording_store);
static ssize_t
vmlogrdr_recording_store(struct device * dev, const char * buf, size_t count) {
struct vmlogrdr_priv_t *priv = dev->driver_data;
ssize_t ret;
switch (buf[0]) {
case '0':
ret = vmlogrdr_recording(priv,0,0);
break;
case '1':
ret = vmlogrdr_recording(priv,1,0);
break;
default:
ret = -EINVAL;
}
if (ret)
return ret;
else
return count;
}
static DEVICE_ATTR(recording, 0200, NULL, vmlogrdr_recording_store);
static ssize_t
vmlogrdr_recording_status_show(struct device_driver *driver, char *buf) {
char cp_command[] = "QUERY RECORDING ";
int len;
cpcmd(cp_command, buf, 4096);
len = strlen(buf);
return len;
}
static DRIVER_ATTR(recording_status, 0444, vmlogrdr_recording_status_show,
NULL);
static struct attribute *vmlogrdr_attrs[] = {
&dev_attr_autopurge.attr,
&dev_attr_purge.attr,
&dev_attr_autorecording.attr,
&dev_attr_recording.attr,
NULL,
};
static struct attribute_group vmlogrdr_attr_group = {
.attrs = vmlogrdr_attrs,
};
static struct class_simple *vmlogrdr_class;
static struct device_driver vmlogrdr_driver = {
.name = "vmlogrdr",
.bus = &iucv_bus,
};
static int
vmlogrdr_register_driver(void) {
int ret;
ret = driver_register(&vmlogrdr_driver);
if (ret) {
printk(KERN_ERR "vmlogrdr: failed to register driver.\n");
return ret;
}
ret = driver_create_file(&vmlogrdr_driver,
&driver_attr_recording_status);
if (ret) {
printk(KERN_ERR "vmlogrdr: failed to add driver attribute.\n");
goto unregdriver;
}
vmlogrdr_class = class_simple_create(THIS_MODULE, "vmlogrdr");
if (IS_ERR(vmlogrdr_class)) {
printk(KERN_ERR "vmlogrdr: failed to create class.\n");
ret=PTR_ERR(vmlogrdr_class);
vmlogrdr_class=NULL;
goto unregattr;
}
return 0;
unregattr:
driver_remove_file(&vmlogrdr_driver, &driver_attr_recording_status);
unregdriver:
driver_unregister(&vmlogrdr_driver);
return ret;
}
static void
vmlogrdr_unregister_driver(void) {
class_simple_destroy(vmlogrdr_class);
vmlogrdr_class = NULL;
driver_remove_file(&vmlogrdr_driver, &driver_attr_recording_status);
driver_unregister(&vmlogrdr_driver);
return;
}
static int
vmlogrdr_register_device(struct vmlogrdr_priv_t *priv) {
struct device *dev;
int ret;
dev = kmalloc(sizeof(struct device), GFP_KERNEL);
if (dev) {
memset(dev, 0, sizeof(struct device));
snprintf(dev->bus_id, BUS_ID_SIZE, "%s",
priv->internal_name);
dev->bus = &iucv_bus;
dev->parent = iucv_root;
dev->driver = &vmlogrdr_driver;
/*
* The release function could be called after the
* module has been unloaded. It's _only_ task is to
* free the struct. Therefore, we specify kfree()
* directly here. (Probably a little bit obfuscating
* but legitime ...).
*/
dev->release = (void (*)(struct device *))kfree;
} else
return -ENOMEM;
ret = device_register(dev);
if (ret)
return ret;
ret = sysfs_create_group(&dev->kobj, &vmlogrdr_attr_group);
if (ret) {
device_unregister(dev);
return ret;
}
priv->class_device = class_simple_device_add(
vmlogrdr_class,
MKDEV(vmlogrdr_major, priv->minor_num),
dev,
"%s", dev->bus_id );
if (IS_ERR(priv->class_device)) {
ret = PTR_ERR(priv->class_device);
priv->class_device=NULL;
sysfs_remove_group(&dev->kobj, &vmlogrdr_attr_group);
device_unregister(dev);
return ret;
}
dev->driver_data = priv;
priv->device = dev;
return 0;
}
static int
vmlogrdr_unregister_device(struct vmlogrdr_priv_t *priv ) {
class_simple_device_remove(MKDEV(vmlogrdr_major, priv->minor_num));
if (priv->device != NULL) {
sysfs_remove_group(&priv->device->kobj, &vmlogrdr_attr_group);
device_unregister(priv->device);
priv->device=NULL;
}
return 0;
}
static int
vmlogrdr_register_cdev(dev_t dev) {
int rc = 0;
vmlogrdr_cdev = cdev_alloc();
if (!vmlogrdr_cdev) {
return -ENOMEM;
}
vmlogrdr_cdev->owner = THIS_MODULE;
vmlogrdr_cdev->ops = &vmlogrdr_fops;
vmlogrdr_cdev->dev = dev;
rc = cdev_add(vmlogrdr_cdev, vmlogrdr_cdev->dev, MAXMINOR);
if (!rc)
return 0;
// cleanup: cdev is not fully registered, no cdev_del here!
kobject_put(&vmlogrdr_cdev->kobj);
vmlogrdr_cdev=NULL;
return rc;
}
static void
vmlogrdr_cleanup(void) {
int i;
if (vmlogrdr_cdev) {
cdev_del(vmlogrdr_cdev);
vmlogrdr_cdev=NULL;
}
for (i=0; i < MAXMINOR; ++i ) {
vmlogrdr_unregister_device(&sys_ser[i]);
free_page((unsigned long)sys_ser[i].buffer);
}
vmlogrdr_unregister_driver();
if (vmlogrdr_major) {
unregister_chrdev_region(MKDEV(vmlogrdr_major, 0), MAXMINOR);
vmlogrdr_major=0;
}
}
static int
vmlogrdr_init(void)
{
int rc;
int i;
dev_t dev;
if (! MACHINE_IS_VM) {
printk (KERN_ERR "vmlogrdr: not running under VM, "
"driver not loaded.\n");
return -ENODEV;
}
recording_class_AB = vmlogrdr_get_recording_class_AB();
rc = alloc_chrdev_region(&dev, 0, MAXMINOR, "vmlogrdr");
if (rc)
return rc;
vmlogrdr_major = MAJOR(dev);
rc=vmlogrdr_register_driver();
if (rc)
goto cleanup;
for (i=0; i < MAXMINOR; ++i ) {
sys_ser[i].buffer = (char *) get_zeroed_page(GFP_KERNEL);
if (!sys_ser[i].buffer) {
rc = ENOMEM;
break;
}
sys_ser[i].current_position = sys_ser[i].buffer;
rc=vmlogrdr_register_device(&sys_ser[i]);
if (rc)
break;
}
if (rc)
goto cleanup;
rc = vmlogrdr_register_cdev(dev);
if (rc)
goto cleanup;
printk (KERN_INFO "vmlogrdr: driver loaded\n");
return 0;
cleanup:
vmlogrdr_cleanup();
printk (KERN_ERR "vmlogrdr: driver not loaded.\n");
return rc;
}
static void
vmlogrdr_exit(void)
{
vmlogrdr_cleanup();
printk (KERN_INFO "vmlogrdr: driver unloaded\n");
return;
}
module_init(vmlogrdr_init);
module_exit(vmlogrdr_exit);

View file

@ -0,0 +1,292 @@
/*
* Watchdog implementation based on z/VM Watchdog Timer API
*
* The user space watchdog daemon can use this driver as
* /dev/vmwatchdog to have z/VM execute the specified CP
* command when the timeout expires. The default command is
* "IPL", which which cause an immediate reboot.
*/
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/kernel.h>
#include <linux/miscdevice.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/watchdog.h>
#include <asm/ebcdic.h>
#include <asm/io.h>
#include <asm/uaccess.h>
#define MAX_CMDLEN 240
#define MIN_INTERVAL 15
static char vmwdt_cmd[MAX_CMDLEN] = "IPL";
static int vmwdt_conceal;
#ifdef CONFIG_WATCHDOG_NOWAYOUT
static int vmwdt_nowayout = 1;
#else
static int vmwdt_nowayout = 0;
#endif
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Arnd Bergmann <[email protected]>");
MODULE_DESCRIPTION("z/VM Watchdog Timer");
module_param_string(cmd, vmwdt_cmd, MAX_CMDLEN, 0644);
MODULE_PARM_DESC(cmd, "CP command that is run when the watchdog triggers");
module_param_named(conceal, vmwdt_conceal, bool, 0644);
MODULE_PARM_DESC(conceal, "Enable the CONCEAL CP option while the watchdog "
" is active");
module_param_named(nowayout, vmwdt_nowayout, bool, 0);
MODULE_PARM_DESC(nowayout, "Watchdog cannot be stopped once started"
" (default=CONFIG_WATCHDOG_NOWAYOUT)");
MODULE_ALIAS_MISCDEV(WATCHDOG_MINOR);
static unsigned int vmwdt_interval = 60;
static unsigned long vmwdt_is_open;
static int vmwdt_expect_close;
enum vmwdt_func {
/* function codes */
wdt_init = 0,
wdt_change = 1,
wdt_cancel = 2,
/* flags */
wdt_conceal = 0x80000000,
};
static int __diag288(enum vmwdt_func func, unsigned int timeout,
char *cmd, size_t len)
{
register unsigned long __func asm("2");
register unsigned long __timeout asm("3");
register unsigned long __cmdp asm("4");
register unsigned long __cmdl asm("5");
int err;
__func = func;
__timeout = timeout;
__cmdp = virt_to_phys(cmd);
__cmdl = len;
err = 0;
asm volatile (
#ifdef __s390x__
"diag %2,%4,0x288\n"
"1: \n"
".section .fixup,\"ax\"\n"
"2: lghi %0,%1\n"
" jg 1b\n"
".previous\n"
".section __ex_table,\"a\"\n"
" .align 8\n"
" .quad 1b,2b\n"
".previous\n"
#else
"diag %2,%4,0x288\n"
"1: \n"
".section .fixup,\"ax\"\n"
"2: lhi %0,%1\n"
" bras 1,3f\n"
" .long 1b\n"
"3: l 1,0(1)\n"
" br 1\n"
".previous\n"
".section __ex_table,\"a\"\n"
" .align 4\n"
" .long 1b,2b\n"
".previous\n"
#endif
: "+&d"(err)
: "i"(-EINVAL), "d"(__func), "d"(__timeout),
"d"(__cmdp), "d"(__cmdl)
: "1", "cc");
return err;
}
static int vmwdt_keepalive(void)
{
/* we allocate new memory every time to avoid having
* to track the state. static allocation is not an
* option since that might not be contiguous in real
* storage in case of a modular build */
static char *ebc_cmd;
size_t len;
int ret;
unsigned int func;
ebc_cmd = kmalloc(MAX_CMDLEN, GFP_KERNEL);
if (!ebc_cmd)
return -ENOMEM;
len = strlcpy(ebc_cmd, vmwdt_cmd, MAX_CMDLEN);
ASCEBC(ebc_cmd, MAX_CMDLEN);
EBC_TOUPPER(ebc_cmd, MAX_CMDLEN);
func = vmwdt_conceal ? (wdt_init | wdt_conceal) : wdt_init;
ret = __diag288(func, vmwdt_interval, ebc_cmd, len);
kfree(ebc_cmd);
if (ret) {
printk(KERN_WARNING "%s: problem setting interval %d, "
"cmd %s\n", __FUNCTION__, vmwdt_interval,
vmwdt_cmd);
}
return ret;
}
static int vmwdt_disable(void)
{
int ret = __diag288(wdt_cancel, 0, "", 0);
if (ret) {
printk(KERN_WARNING "%s: problem disabling watchdog\n",
__FUNCTION__);
}
return ret;
}
static int __init vmwdt_probe(void)
{
/* there is no real way to see if the watchdog is supported,
* so we try initializing it with a NOP command ("BEGIN")
* that won't cause any harm even if the following disable
* fails for some reason */
static char __initdata ebc_begin[] = {
194, 197, 199, 201, 213
};
if (__diag288(wdt_init, 15, ebc_begin, sizeof(ebc_begin)) != 0) {
printk(KERN_INFO "z/VM watchdog not available\n");
return -EINVAL;
}
return vmwdt_disable();
}
static int vmwdt_open(struct inode *i, struct file *f)
{
int ret;
if (test_and_set_bit(0, &vmwdt_is_open))
return -EBUSY;
ret = vmwdt_keepalive();
if (ret)
clear_bit(0, &vmwdt_is_open);
return ret ? ret : nonseekable_open(i, f);
}
static int vmwdt_close(struct inode *i, struct file *f)
{
if (vmwdt_expect_close == 42)
vmwdt_disable();
vmwdt_expect_close = 0;
clear_bit(0, &vmwdt_is_open);
return 0;
}
static struct watchdog_info vmwdt_info = {
.options = WDIOF_SETTIMEOUT | WDIOF_KEEPALIVEPING | WDIOF_MAGICCLOSE,
.firmware_version = 0,
.identity = "z/VM Watchdog Timer",
};
static int vmwdt_ioctl(struct inode *i, struct file *f,
unsigned int cmd, unsigned long arg)
{
switch (cmd) {
case WDIOC_GETSUPPORT:
if (copy_to_user((void __user *)arg, &vmwdt_info,
sizeof(vmwdt_info)))
return -EFAULT;
return 0;
case WDIOC_GETSTATUS:
case WDIOC_GETBOOTSTATUS:
return put_user(0, (int *)arg);
case WDIOC_GETTEMP:
return -EINVAL;
case WDIOC_SETOPTIONS:
{
int options, ret;
if (get_user(options, (int __user *)arg))
return -EFAULT;
ret = -EINVAL;
if (options & WDIOS_DISABLECARD) {
ret = vmwdt_disable();
if (ret)
return ret;
}
if (options & WDIOS_ENABLECARD) {
ret = vmwdt_keepalive();
}
return ret;
}
case WDIOC_GETTIMEOUT:
return put_user(vmwdt_interval, (int __user *)arg);
case WDIOC_SETTIMEOUT:
{
int interval;
if (get_user(interval, (int __user *)arg))
return -EFAULT;
if (interval < MIN_INTERVAL)
return -EINVAL;
vmwdt_interval = interval;
}
return vmwdt_keepalive();
case WDIOC_KEEPALIVE:
return vmwdt_keepalive();
}
return -EINVAL;
}
static ssize_t vmwdt_write(struct file *f, const char __user *buf,
size_t count, loff_t *ppos)
{
if(count) {
if (!vmwdt_nowayout) {
size_t i;
/* note: just in case someone wrote the magic character
* five months ago... */
vmwdt_expect_close = 0;
for (i = 0; i != count; i++) {
char c;
if (get_user(c, buf+i))
return -EFAULT;
if (c == 'V')
vmwdt_expect_close = 42;
}
}
/* someone wrote to us, we should restart timer */
vmwdt_keepalive();
}
return count;
}
static struct file_operations vmwdt_fops = {
.open = &vmwdt_open,
.release = &vmwdt_close,
.ioctl = &vmwdt_ioctl,
.write = &vmwdt_write,
.owner = THIS_MODULE,
};
static struct miscdevice vmwdt_dev = {
.minor = WATCHDOG_MINOR,
.name = "watchdog",
.fops = &vmwdt_fops,
};
static int __init vmwdt_init(void)
{
int ret;
ret = vmwdt_probe();
if (ret)
return ret;
return misc_register(&vmwdt_dev);
}
module_init(vmwdt_init);
static void __exit vmwdt_exit(void)
{
WARN_ON(misc_deregister(&vmwdt_dev) != 0);
}
module_exit(vmwdt_exit);

10
drivers/s390/cio/Makefile Normal file
View file

@ -0,0 +1,10 @@
#
# Makefile for the S/390 common i/o drivers
#
obj-y += airq.o blacklist.o chsc.o cio.o css.o
ccw_device-objs += device.o device_fsm.o device_ops.o
ccw_device-objs += device_id.o device_pgid.o device_status.o
obj-y += ccw_device.o cmf.o
obj-$(CONFIG_CCWGROUP) += ccwgroup.o
obj-$(CONFIG_QDIO) += qdio.o

87
drivers/s390/cio/airq.c Normal file
View file

@ -0,0 +1,87 @@
/*
* drivers/s390/cio/airq.c
* S/390 common I/O routines -- support for adapter interruptions
*
* $Revision: 1.12 $
*
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Ingo Adlung (adlung@de.ibm.com)
* Cornelia Huck (cohuck@de.ibm.com)
* Arnd Bergmann (arndb@de.ibm.com)
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/slab.h>
#include <linux/rcupdate.h>
#include "cio_debug.h"
#include "airq.h"
static adapter_int_handler_t adapter_handler;
/*
* register for adapter interrupts
*
* With HiperSockets the zSeries architecture provides for
* means of adapter interrups, pseudo I/O interrupts that are
* not tied to an I/O subchannel, but to an adapter. However,
* it doesn't disclose the info how to enable/disable them, but
* to recognize them only. Perhaps we should consider them
* being shared interrupts, and thus build a linked list
* of adapter handlers ... to be evaluated ...
*/
int
s390_register_adapter_interrupt (adapter_int_handler_t handler)
{
int ret;
char dbf_txt[15];
CIO_TRACE_EVENT (4, "rgaint");
if (handler == NULL)
ret = -EINVAL;
else
ret = (cmpxchg(&adapter_handler, NULL, handler) ? -EBUSY : 0);
if (!ret)
synchronize_kernel();
sprintf (dbf_txt, "ret:%d", ret);
CIO_TRACE_EVENT (4, dbf_txt);
return ret;
}
int
s390_unregister_adapter_interrupt (adapter_int_handler_t handler)
{
int ret;
char dbf_txt[15];
CIO_TRACE_EVENT (4, "urgaint");
if (handler == NULL)
ret = -EINVAL;
else {
adapter_handler = NULL;
synchronize_kernel();
ret = 0;
}
sprintf (dbf_txt, "ret:%d", ret);
CIO_TRACE_EVENT (4, dbf_txt);
return ret;
}
void
do_adapter_IO (void)
{
CIO_TRACE_EVENT (6, "doaio");
if (adapter_handler)
(*adapter_handler) ();
}
EXPORT_SYMBOL (s390_register_adapter_interrupt);
EXPORT_SYMBOL (s390_unregister_adapter_interrupt);

10
drivers/s390/cio/airq.h Normal file
View file

@ -0,0 +1,10 @@
#ifndef S390_AINTERRUPT_H
#define S390_AINTERRUPT_H
typedef int (*adapter_int_handler_t)(void);
extern int s390_register_adapter_interrupt(adapter_int_handler_t handler);
extern int s390_unregister_adapter_interrupt(adapter_int_handler_t handler);
extern void do_adapter_IO (void);
#endif

View file

@ -0,0 +1,351 @@
/*
* drivers/s390/cio/blacklist.c
* S/390 common I/O routines -- blacklisting of specific devices
* $Revision: 1.33 $
*
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Ingo Adlung (adlung@de.ibm.com)
* Cornelia Huck (cohuck@de.ibm.com)
* Arnd Bergmann (arndb@de.ibm.com)
*/
#include <linux/config.h>
#include <linux/init.h>
#include <linux/vmalloc.h>
#include <linux/slab.h>
#include <linux/proc_fs.h>
#include <linux/ctype.h>
#include <linux/device.h>
#include <asm/cio.h>
#include <asm/uaccess.h>
#include "blacklist.h"
#include "cio.h"
#include "cio_debug.h"
#include "css.h"
/*
* "Blacklisting" of certain devices:
* Device numbers given in the commandline as cio_ignore=... won't be known
* to Linux.
*
* These can be single devices or ranges of devices
*/
/* 65536 bits to indicate if a devno is blacklisted or not */
#define __BL_DEV_WORDS (__MAX_SUBCHANNELS + (8*sizeof(long) - 1) / \
(8*sizeof(long)))
static unsigned long bl_dev[__BL_DEV_WORDS];
typedef enum {add, free} range_action;
/*
* Function: blacklist_range
* (Un-)blacklist the devices from-to
*/
static inline void
blacklist_range (range_action action, unsigned int from, unsigned int to)
{
if (!to)
to = from;
if (from > to || to > __MAX_SUBCHANNELS) {
printk (KERN_WARNING "Invalid blacklist range "
"0x%04x to 0x%04x, skipping\n", from, to);
return;
}
for (; from <= to; from++) {
if (action == add)
set_bit (from, bl_dev);
else
clear_bit (from, bl_dev);
}
}
/*
* Function: blacklist_busid
* Get devno/busid from given string.
* Shamelessly grabbed from dasd_devmap.c.
*/
static inline int
blacklist_busid(char **str, int *id0, int *id1, int *devno)
{
int val, old_style;
char *sav;
sav = *str;
/* check for leading '0x' */
old_style = 0;
if ((*str)[0] == '0' && (*str)[1] == 'x') {
*str += 2;
old_style = 1;
}
if (!isxdigit((*str)[0])) /* We require at least one hex digit */
goto confused;
val = simple_strtoul(*str, str, 16);
if (old_style || (*str)[0] != '.') {
*id0 = *id1 = 0;
if (val < 0 || val > 0xffff)
goto confused;
*devno = val;
if ((*str)[0] != ',' && (*str)[0] != '-' &&
(*str)[0] != '\n' && (*str)[0] != '\0')
goto confused;
return 0;
}
/* New style x.y.z busid */
if (val < 0 || val > 0xff)
goto confused;
*id0 = val;
(*str)++;
if (!isxdigit((*str)[0])) /* We require at least one hex digit */
goto confused;
val = simple_strtoul(*str, str, 16);
if (val < 0 || val > 0xff || (*str)++[0] != '.')
goto confused;
*id1 = val;
if (!isxdigit((*str)[0])) /* We require at least one hex digit */
goto confused;
val = simple_strtoul(*str, str, 16);
if (val < 0 || val > 0xffff)
goto confused;
*devno = val;
if ((*str)[0] != ',' && (*str)[0] != '-' &&
(*str)[0] != '\n' && (*str)[0] != '\0')
goto confused;
return 0;
confused:
strsep(str, ",\n");
printk(KERN_WARNING "Invalid cio_ignore parameter '%s'\n", sav);
return 1;
}
static inline int
blacklist_parse_parameters (char *str, range_action action)
{
unsigned int from, to, from_id0, to_id0, from_id1, to_id1;
while (*str != 0 && *str != '\n') {
range_action ra = action;
while(*str == ',')
str++;
if (*str == '!') {
ra = !action;
++str;
}
/*
* Since we have to parse the proc commands and the
* kernel arguments we have to check four cases
*/
if (strncmp(str,"all,",4) == 0 || strcmp(str,"all") == 0 ||
strncmp(str,"all\n",4) == 0 || strncmp(str,"all ",4) == 0) {
from = 0;
to = __MAX_SUBCHANNELS;
str += 3;
} else {
int rc;
rc = blacklist_busid(&str, &from_id0,
&from_id1, &from);
if (rc)
continue;
to = from;
to_id0 = from_id0;
to_id1 = from_id1;
if (*str == '-') {
str++;
rc = blacklist_busid(&str, &to_id0,
&to_id1, &to);
if (rc)
continue;
}
if (*str == '-') {
printk(KERN_WARNING "invalid cio_ignore "
"parameter '%s'\n",
strsep(&str, ",\n"));
continue;
}
if ((from_id0 != to_id0) || (from_id1 != to_id1)) {
printk(KERN_WARNING "invalid cio_ignore range "
"%x.%x.%04x-%x.%x.%04x\n",
from_id0, from_id1, from,
to_id0, to_id1, to);
continue;
}
}
/* FIXME: ignoring id0 and id1 here. */
pr_debug("blacklist_setup: adding range "
"from 0.0.%04x to 0.0.%04x\n", from, to);
blacklist_range (ra, from, to);
}
return 1;
}
/* Parsing the commandline for blacklist parameters, e.g. to blacklist
* bus ids 0.0.1234, 0.0.1235 and 0.0.1236, you could use any of:
* - cio_ignore=1234-1236
* - cio_ignore=0x1234-0x1235,1236
* - cio_ignore=0x1234,1235-1236
* - cio_ignore=1236 cio_ignore=1234-0x1236
* - cio_ignore=1234 cio_ignore=1236 cio_ignore=0x1235
* - cio_ignore=0.0.1234-0.0.1236
* - cio_ignore=0.0.1234,0x1235,1236
* - ...
*/
static int __init
blacklist_setup (char *str)
{
CIO_MSG_EVENT(6, "Reading blacklist parameters\n");
return blacklist_parse_parameters (str, add);
}
__setup ("cio_ignore=", blacklist_setup);
/* Checking if devices are blacklisted */
/*
* Function: is_blacklisted
* Returns 1 if the given devicenumber can be found in the blacklist,
* otherwise 0.
* Used by validate_subchannel()
*/
int
is_blacklisted (int devno)
{
return test_bit (devno, bl_dev);
}
#ifdef CONFIG_PROC_FS
/*
* Function: s390_redo_validation
* Look for no longer blacklisted devices
* FIXME: there must be a better way to do this */
static inline void
s390_redo_validation (void)
{
unsigned int irq;
CIO_TRACE_EVENT (0, "redoval");
for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
int ret;
struct subchannel *sch;
sch = get_subchannel_by_schid(irq);
if (sch) {
/* Already known. */
put_device(&sch->dev);
continue;
}
ret = css_probe_device(irq);
if (ret == -ENXIO)
break; /* We're through. */
if (ret == -ENOMEM)
/*
* Stop validation for now. Bad, but no need for a
* panic.
*/
break;
}
}
/*
* Function: blacklist_parse_proc_parameters
* parse the stuff which is piped to /proc/cio_ignore
*/
static inline void
blacklist_parse_proc_parameters (char *buf)
{
if (strncmp (buf, "free ", 5) == 0) {
blacklist_parse_parameters (buf + 5, free);
} else if (strncmp (buf, "add ", 4) == 0) {
/*
* We don't need to check for known devices since
* css_probe_device will handle this correctly.
*/
blacklist_parse_parameters (buf + 4, add);
} else {
printk (KERN_WARNING "cio_ignore: Parse error; \n"
KERN_WARNING "try using 'free all|<devno-range>,"
"<devno-range>,...'\n"
KERN_WARNING "or 'add <devno-range>,"
"<devno-range>,...'\n");
return;
}
s390_redo_validation ();
}
/* FIXME: These should be real bus ids and not home-grown ones! */
static int cio_ignore_read (char *page, char **start, off_t off,
int count, int *eof, void *data)
{
const unsigned int entry_size = 18; /* "0.0.ABCD-0.0.EFGH\n" */
long devno;
int len;
len = 0;
for (devno = off; /* abuse the page variable
* as counter, see fs/proc/generic.c */
devno <= __MAX_SUBCHANNELS && len + entry_size < count; devno++) {
if (!test_bit(devno, bl_dev))
continue;
len += sprintf(page + len, "0.0.%04lx", devno);
if (test_bit(devno + 1, bl_dev)) { /* print range */
while (++devno < __MAX_SUBCHANNELS)
if (!test_bit(devno, bl_dev))
break;
len += sprintf(page + len, "-0.0.%04lx", --devno);
}
len += sprintf(page + len, "\n");
}
if (devno <= __MAX_SUBCHANNELS)
*eof = 1;
*start = (char *) (devno - off); /* number of checked entries */
return len;
}
static int cio_ignore_write(struct file *file, const char __user *user_buf,
unsigned long user_len, void *data)
{
char *buf;
if (user_len > 65536)
user_len = 65536;
buf = vmalloc (user_len + 1); /* maybe better use the stack? */
if (buf == NULL)
return -ENOMEM;
if (strncpy_from_user (buf, user_buf, user_len) < 0) {
vfree (buf);
return -EFAULT;
}
buf[user_len] = '\0';
blacklist_parse_proc_parameters (buf);
vfree (buf);
return user_len;
}
static int
cio_ignore_proc_init (void)
{
struct proc_dir_entry *entry;
entry = create_proc_entry ("cio_ignore", S_IFREG | S_IRUGO | S_IWUSR,
&proc_root);
if (!entry)
return 0;
entry->read_proc = cio_ignore_read;
entry->write_proc = cio_ignore_write;
return 1;
}
__initcall (cio_ignore_proc_init);
#endif /* CONFIG_PROC_FS */

View file

@ -0,0 +1,6 @@
#ifndef S390_BLACKLIST_H
#define S390_BLACKLIST_H
extern int is_blacklisted (int devno);
#endif

482
drivers/s390/cio/ccwgroup.c Normal file
View file

@ -0,0 +1,482 @@
/*
* drivers/s390/cio/ccwgroup.c
* bus driver for ccwgroup
* $Revision: 1.29 $
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Arnd Bergmann (arndb@de.ibm.com)
* Cornelia Huck (cohuck@de.ibm.com)
*/
#include <linux/module.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/init.h>
#include <linux/ctype.h>
#include <linux/dcache.h>
#include <asm/semaphore.h>
#include <asm/ccwdev.h>
#include <asm/ccwgroup.h>
/* In Linux 2.4, we had a channel device layer called "chandev"
* that did all sorts of obscure stuff for networking devices.
* This is another driver that serves as a replacement for just
* one of its functions, namely the translation of single subchannels
* to devices that use multiple subchannels.
*/
/* a device matches a driver if all its slave devices match the same
* entry of the driver */
static int
ccwgroup_bus_match (struct device * dev, struct device_driver * drv)
{
struct ccwgroup_device *gdev;
struct ccwgroup_driver *gdrv;
gdev = container_of(dev, struct ccwgroup_device, dev);
gdrv = container_of(drv, struct ccwgroup_driver, driver);
if (gdev->creator_id == gdrv->driver_id)
return 1;
return 0;
}
static int
ccwgroup_hotplug (struct device *dev, char **envp, int num_envp, char *buffer,
int buffer_size)
{
/* TODO */
return 0;
}
static struct bus_type ccwgroup_bus_type = {
.name = "ccwgroup",
.match = ccwgroup_bus_match,
.hotplug = ccwgroup_hotplug,
};
static inline void
__ccwgroup_remove_symlinks(struct ccwgroup_device *gdev)
{
int i;
char str[8];
for (i = 0; i < gdev->count; i++) {
sprintf(str, "cdev%d", i);
sysfs_remove_link(&gdev->dev.kobj, str);
sysfs_remove_link(&gdev->cdev[i]->dev.kobj, "group_device");
}
}
/*
* Provide an 'ungroup' attribute so the user can remove group devices no
* longer needed or accidentially created. Saves memory :)
*/
static ssize_t
ccwgroup_ungroup_store(struct device *dev, const char *buf, size_t count)
{
struct ccwgroup_device *gdev;
gdev = to_ccwgroupdev(dev);
if (gdev->state != CCWGROUP_OFFLINE)
return -EINVAL;
__ccwgroup_remove_symlinks(gdev);
device_unregister(dev);
return count;
}
static DEVICE_ATTR(ungroup, 0200, NULL, ccwgroup_ungroup_store);
static void
ccwgroup_release (struct device *dev)
{
struct ccwgroup_device *gdev;
int i;
gdev = to_ccwgroupdev(dev);
for (i = 0; i < gdev->count; i++) {
gdev->cdev[i]->dev.driver_data = NULL;
put_device(&gdev->cdev[i]->dev);
}
kfree(gdev);
}
static inline int
__ccwgroup_create_symlinks(struct ccwgroup_device *gdev)
{
char str[8];
int i, rc;
for (i = 0; i < gdev->count; i++) {
rc = sysfs_create_link(&gdev->cdev[i]->dev.kobj, &gdev->dev.kobj,
"group_device");
if (rc) {
for (--i; i >= 0; i--)
sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
"group_device");
return rc;
}
}
for (i = 0; i < gdev->count; i++) {
sprintf(str, "cdev%d", i);
rc = sysfs_create_link(&gdev->dev.kobj, &gdev->cdev[i]->dev.kobj,
str);
if (rc) {
for (--i; i >= 0; i--) {
sprintf(str, "cdev%d", i);
sysfs_remove_link(&gdev->dev.kobj, str);
}
for (i = 0; i < gdev->count; i++)
sysfs_remove_link(&gdev->cdev[i]->dev.kobj,
"group_device");
return rc;
}
}
return 0;
}
/*
* try to add a new ccwgroup device for one driver
* argc and argv[] are a list of bus_id's of devices
* belonging to the driver.
*/
int
ccwgroup_create(struct device *root,
unsigned int creator_id,
struct ccw_driver *cdrv,
int argc, char *argv[])
{
struct ccwgroup_device *gdev;
int i;
int rc;
int del_drvdata;
if (argc > 256) /* disallow dumb users */
return -EINVAL;
gdev = kmalloc(sizeof(*gdev) + argc*sizeof(gdev->cdev[0]), GFP_KERNEL);
if (!gdev)
return -ENOMEM;
memset(gdev, 0, sizeof(*gdev) + argc*sizeof(gdev->cdev[0]));
atomic_set(&gdev->onoff, 0);
del_drvdata = 0;
for (i = 0; i < argc; i++) {
gdev->cdev[i] = get_ccwdev_by_busid(cdrv, argv[i]);
/* all devices have to be of the same type in
* order to be grouped */
if (!gdev->cdev[i]
|| gdev->cdev[i]->id.driver_info !=
gdev->cdev[0]->id.driver_info) {
rc = -EINVAL;
goto free_dev;
}
/* Don't allow a device to belong to more than one group. */
if (gdev->cdev[i]->dev.driver_data) {
rc = -EINVAL;
goto free_dev;
}
}
for (i = 0; i < argc; i++)
gdev->cdev[i]->dev.driver_data = gdev;
del_drvdata = 1;
gdev->creator_id = creator_id;
gdev->count = argc;
gdev->dev = (struct device ) {
.bus = &ccwgroup_bus_type,
.parent = root,
.release = ccwgroup_release,
};
snprintf (gdev->dev.bus_id, BUS_ID_SIZE, "%s",
gdev->cdev[0]->dev.bus_id);
rc = device_register(&gdev->dev);
if (rc)
goto free_dev;
get_device(&gdev->dev);
rc = device_create_file(&gdev->dev, &dev_attr_ungroup);
if (rc) {
device_unregister(&gdev->dev);
goto error;
}
rc = __ccwgroup_create_symlinks(gdev);
if (!rc) {
put_device(&gdev->dev);
return 0;
}
device_remove_file(&gdev->dev, &dev_attr_ungroup);
device_unregister(&gdev->dev);
error:
for (i = 0; i < argc; i++)
if (gdev->cdev[i]) {
put_device(&gdev->cdev[i]->dev);
gdev->cdev[i]->dev.driver_data = NULL;
}
put_device(&gdev->dev);
return rc;
free_dev:
for (i = 0; i < argc; i++)
if (gdev->cdev[i]) {
put_device(&gdev->cdev[i]->dev);
if (del_drvdata)
gdev->cdev[i]->dev.driver_data = NULL;
}
kfree(gdev);
return rc;
}
static int __init
init_ccwgroup (void)
{
return bus_register (&ccwgroup_bus_type);
}
static void __exit
cleanup_ccwgroup (void)
{
bus_unregister (&ccwgroup_bus_type);
}
module_init(init_ccwgroup);
module_exit(cleanup_ccwgroup);
/************************** driver stuff ******************************/
static int
ccwgroup_set_online(struct ccwgroup_device *gdev)
{
struct ccwgroup_driver *gdrv;
int ret;
if (atomic_compare_and_swap(0, 1, &gdev->onoff))
return -EAGAIN;
if (gdev->state == CCWGROUP_ONLINE) {
ret = 0;
goto out;
}
if (!gdev->dev.driver) {
ret = -EINVAL;
goto out;
}
gdrv = to_ccwgroupdrv (gdev->dev.driver);
if ((ret = gdrv->set_online(gdev)))
goto out;
gdev->state = CCWGROUP_ONLINE;
out:
atomic_set(&gdev->onoff, 0);
return ret;
}
static int
ccwgroup_set_offline(struct ccwgroup_device *gdev)
{
struct ccwgroup_driver *gdrv;
int ret;
if (atomic_compare_and_swap(0, 1, &gdev->onoff))
return -EAGAIN;
if (gdev->state == CCWGROUP_OFFLINE) {
ret = 0;
goto out;
}
if (!gdev->dev.driver) {
ret = -EINVAL;
goto out;
}
gdrv = to_ccwgroupdrv (gdev->dev.driver);
if ((ret = gdrv->set_offline(gdev)))
goto out;
gdev->state = CCWGROUP_OFFLINE;
out:
atomic_set(&gdev->onoff, 0);
return ret;
}
static ssize_t
ccwgroup_online_store (struct device *dev, const char *buf, size_t count)
{
struct ccwgroup_device *gdev;
struct ccwgroup_driver *gdrv;
unsigned int value;
int ret;
gdev = to_ccwgroupdev(dev);
if (!dev->driver)
return count;
gdrv = to_ccwgroupdrv (gdev->dev.driver);
if (!try_module_get(gdrv->owner))
return -EINVAL;
value = simple_strtoul(buf, 0, 0);
ret = count;
if (value == 1)
ccwgroup_set_online(gdev);
else if (value == 0)
ccwgroup_set_offline(gdev);
else
ret = -EINVAL;
module_put(gdrv->owner);
return ret;
}
static ssize_t
ccwgroup_online_show (struct device *dev, char *buf)
{
int online;
online = (to_ccwgroupdev(dev)->state == CCWGROUP_ONLINE);
return sprintf(buf, online ? "1\n" : "0\n");
}
static DEVICE_ATTR(online, 0644, ccwgroup_online_show, ccwgroup_online_store);
static int
ccwgroup_probe (struct device *dev)
{
struct ccwgroup_device *gdev;
struct ccwgroup_driver *gdrv;
int ret;
gdev = to_ccwgroupdev(dev);
gdrv = to_ccwgroupdrv(dev->driver);
if ((ret = device_create_file(dev, &dev_attr_online)))
return ret;
pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id);
ret = gdrv->probe ? gdrv->probe(gdev) : -ENODEV;
if (ret)
device_remove_file(dev, &dev_attr_online);
return ret;
}
static int
ccwgroup_remove (struct device *dev)
{
struct ccwgroup_device *gdev;
struct ccwgroup_driver *gdrv;
gdev = to_ccwgroupdev(dev);
gdrv = to_ccwgroupdrv(dev->driver);
pr_debug("%s: device %s\n", __func__, gdev->dev.bus_id);
device_remove_file(dev, &dev_attr_online);
if (gdrv && gdrv->remove)
gdrv->remove(gdev);
return 0;
}
int
ccwgroup_driver_register (struct ccwgroup_driver *cdriver)
{
/* register our new driver with the core */
cdriver->driver = (struct device_driver) {
.bus = &ccwgroup_bus_type,
.name = cdriver->name,
.probe = ccwgroup_probe,
.remove = ccwgroup_remove,
};
return driver_register(&cdriver->driver);
}
static inline struct device *
__get_next_ccwgroup_device(struct device_driver *drv)
{
struct device *dev, *d;
down_read(&drv->bus->subsys.rwsem);
dev = NULL;
list_for_each_entry(d, &drv->devices, driver_list) {
dev = get_device(d);
if (dev)
break;
}
up_read(&drv->bus->subsys.rwsem);
return dev;
}
void
ccwgroup_driver_unregister (struct ccwgroup_driver *cdriver)
{
struct device *dev;
/* We don't want ccwgroup devices to live longer than their driver. */
get_driver(&cdriver->driver);
while ((dev = __get_next_ccwgroup_device(&cdriver->driver))) {
__ccwgroup_remove_symlinks(to_ccwgroupdev(dev));
device_unregister(dev);
put_device(dev);
};
put_driver(&cdriver->driver);
driver_unregister(&cdriver->driver);
}
int
ccwgroup_probe_ccwdev(struct ccw_device *cdev)
{
return 0;
}
static inline struct ccwgroup_device *
__ccwgroup_get_gdev_by_cdev(struct ccw_device *cdev)
{
struct ccwgroup_device *gdev;
if (cdev->dev.driver_data) {
gdev = (struct ccwgroup_device *)cdev->dev.driver_data;
if (get_device(&gdev->dev)) {
if (!list_empty(&gdev->dev.node))
return gdev;
put_device(&gdev->dev);
}
return NULL;
}
return NULL;
}
void
ccwgroup_remove_ccwdev(struct ccw_device *cdev)
{
struct ccwgroup_device *gdev;
/* Ignore offlining errors, device is gone anyway. */
ccw_device_set_offline(cdev);
/* If one of its devices is gone, the whole group is done for. */
gdev = __ccwgroup_get_gdev_by_cdev(cdev);
if (gdev) {
__ccwgroup_remove_symlinks(gdev);
device_unregister(&gdev->dev);
put_device(&gdev->dev);
}
}
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(ccwgroup_driver_register);
EXPORT_SYMBOL(ccwgroup_driver_unregister);
EXPORT_SYMBOL(ccwgroup_create);
EXPORT_SYMBOL(ccwgroup_probe_ccwdev);
EXPORT_SYMBOL(ccwgroup_remove_ccwdev);

1114
drivers/s390/cio/chsc.c Normal file

File diff suppressed because it is too large Load diff

66
drivers/s390/cio/chsc.h Normal file
View file

@ -0,0 +1,66 @@
#ifndef S390_CHSC_H
#define S390_CHSC_H
#define NR_CHPIDS 256
#define CHSC_SEI_ACC_CHPID 1
#define CHSC_SEI_ACC_LINKADDR 2
#define CHSC_SEI_ACC_FULLLINKADDR 3
struct chsc_header {
u16 length;
u16 code;
};
struct channel_path_desc {
u8 flags;
u8 lsn;
u8 desc;
u8 chpid;
u8 swla;
u8 zeroes;
u8 chla;
u8 chpp;
};
struct channel_path {
int id;
int state;
struct channel_path_desc desc;
struct device dev;
};
extern void s390_process_css( void );
extern void chsc_validate_chpids(struct subchannel *);
extern void chpid_is_actually_online(int);
struct css_general_char {
u64 : 41;
u32 aif : 1; /* bit 41 */
u32 : 3;
u32 mcss : 1; /* bit 45 */
u32 : 2;
u32 ext_mb : 1; /* bit 48 */
u32 : 7;
u32 aif_tdd : 1; /* bit 56 */
u32 : 10;
u32 aif_osa : 1; /* bit 67 */
u32 : 28;
}__attribute__((packed));
struct css_chsc_char {
u64 res;
u64 : 43;
u32 scssc : 1; /* bit 107 */
u32 scsscf : 1; /* bit 108 */
u32 : 19;
}__attribute__((packed));
extern struct css_general_char css_general_characteristics;
extern struct css_chsc_char css_chsc_characteristics;
extern int chsc_determine_css_characteristics(void);
extern int css_characteristics_avail;
extern void *chsc_get_chp_desc(struct subchannel*, int);
#endif

860
drivers/s390/cio/cio.c Normal file
View file

@ -0,0 +1,860 @@
/*
* drivers/s390/cio/cio.c
* S/390 common I/O routines -- low level i/o calls
* $Revision: 1.131 $
*
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Ingo Adlung (adlung@de.ibm.com)
* Cornelia Huck (cohuck@de.ibm.com)
* Arnd Bergmann (arndb@de.ibm.com)
* Martin Schwidefsky (schwidefsky@de.ibm.com)
*/
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <linux/slab.h>
#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 "airq.h"
#include "cio.h"
#include "css.h"
#include "chsc.h"
#include "ioasm.h"
#include "blacklist.h"
#include "cio_debug.h"
debug_info_t *cio_debug_msg_id;
debug_info_t *cio_debug_trace_id;
debug_info_t *cio_debug_crw_id;
int cio_show_msg;
static int __init
cio_setup (char *parm)
{
if (!strcmp (parm, "yes"))
cio_show_msg = 1;
else if (!strcmp (parm, "no"))
cio_show_msg = 0;
else
printk (KERN_ERR "cio_setup : invalid cio_msg parameter '%s'",
parm);
return 1;
}
__setup ("cio_msg=", cio_setup);
/*
* Function: cio_debug_init
* Initializes three debug logs (under /proc/s390dbf) for common I/O:
* - cio_msg logs the messages which are printk'ed when CONFIG_DEBUG_IO is on
* - cio_trace logs the calling of different functions
* - cio_crw logs the messages which are printk'ed when CONFIG_DEBUG_CRW is on
* debug levels depend on CONFIG_DEBUG_IO resp. CONFIG_DEBUG_CRW
*/
static int __init
cio_debug_init (void)
{
cio_debug_msg_id = debug_register ("cio_msg", 4, 4, 16*sizeof (long));
if (!cio_debug_msg_id)
goto out_unregister;
debug_register_view (cio_debug_msg_id, &debug_sprintf_view);
debug_set_level (cio_debug_msg_id, 2);
cio_debug_trace_id = debug_register ("cio_trace", 4, 4, 8);
if (!cio_debug_trace_id)
goto out_unregister;
debug_register_view (cio_debug_trace_id, &debug_hex_ascii_view);
debug_set_level (cio_debug_trace_id, 2);
cio_debug_crw_id = debug_register ("cio_crw", 2, 4, 16*sizeof (long));
if (!cio_debug_crw_id)
goto out_unregister;
debug_register_view (cio_debug_crw_id, &debug_sprintf_view);
debug_set_level (cio_debug_crw_id, 2);
pr_debug("debugging initialized\n");
return 0;
out_unregister:
if (cio_debug_msg_id)
debug_unregister (cio_debug_msg_id);
if (cio_debug_trace_id)
debug_unregister (cio_debug_trace_id);
if (cio_debug_crw_id)
debug_unregister (cio_debug_crw_id);
pr_debug("could not initialize debugging\n");
return -1;
}
arch_initcall (cio_debug_init);
int
cio_set_options (struct subchannel *sch, int flags)
{
sch->options.suspend = (flags & DOIO_ALLOW_SUSPEND) != 0;
sch->options.prefetch = (flags & DOIO_DENY_PREFETCH) != 0;
sch->options.inter = (flags & DOIO_SUPPRESS_INTER) != 0;
return 0;
}
/* FIXME: who wants to use this? */
int
cio_get_options (struct subchannel *sch)
{
int flags;
flags = 0;
if (sch->options.suspend)
flags |= DOIO_ALLOW_SUSPEND;
if (sch->options.prefetch)
flags |= DOIO_DENY_PREFETCH;
if (sch->options.inter)
flags |= DOIO_SUPPRESS_INTER;
return flags;
}
/*
* Use tpi to get a pending interrupt, call the interrupt handler and
* return a pointer to the subchannel structure.
*/
static inline int
cio_tpi(void)
{
struct tpi_info *tpi_info;
struct subchannel *sch;
struct irb *irb;
tpi_info = (struct tpi_info *) __LC_SUBCHANNEL_ID;
if (tpi (NULL) != 1)
return 0;
irb = (struct irb *) __LC_IRB;
/* Store interrupt response block to lowcore. */
if (tsch (tpi_info->irq, irb) != 0)
/* Not status pending or not operational. */
return 1;
sch = (struct subchannel *)(unsigned long)tpi_info->intparm;
if (!sch)
return 1;
local_bh_disable();
irq_enter ();
spin_lock(&sch->lock);
memcpy (&sch->schib.scsw, &irb->scsw, sizeof (struct scsw));
if (sch->driver && sch->driver->irq)
sch->driver->irq(&sch->dev);
spin_unlock(&sch->lock);
irq_exit ();
__local_bh_enable();
return 1;
}
static inline int
cio_start_handle_notoper(struct subchannel *sch, __u8 lpm)
{
char dbf_text[15];
if (lpm != 0)
sch->lpm &= ~lpm;
else
sch->lpm = 0;
stsch (sch->irq, &sch->schib);
CIO_MSG_EVENT(0, "cio_start: 'not oper' status for "
"subchannel %04x!\n", sch->irq);
sprintf(dbf_text, "no%s", sch->dev.bus_id);
CIO_TRACE_EVENT(0, dbf_text);
CIO_HEX_EVENT(0, &sch->schib, sizeof (struct schib));
return (sch->lpm ? -EACCES : -ENODEV);
}
int
cio_start_key (struct subchannel *sch, /* subchannel structure */
struct ccw1 * cpa, /* logical channel prog addr */
__u8 lpm, /* logical path mask */
__u8 key) /* storage key */
{
char dbf_txt[15];
int ccode;
CIO_TRACE_EVENT (4, "stIO");
CIO_TRACE_EVENT (4, sch->dev.bus_id);
/* sch is always under 2G. */
sch->orb.intparm = (__u32)(unsigned long)sch;
sch->orb.fmt = 1;
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;
#ifdef CONFIG_ARCH_S390X
/*
* for 64 bit we always support 64 bit IDAWs with 4k page size only
*/
sch->orb.c64 = 1;
sch->orb.i2k = 0;
#endif
sch->orb.key = key >> 4;
/* issue "Start Subchannel" */
sch->orb.cpa = (__u32) __pa (cpa);
ccode = ssch (sch->irq, &sch->orb);
/* process condition code */
sprintf (dbf_txt, "ccode:%d", ccode);
CIO_TRACE_EVENT (4, dbf_txt);
switch (ccode) {
case 0:
/*
* initialize device status information
*/
sch->schib.scsw.actl |= SCSW_ACTL_START_PEND;
return 0;
case 1: /* status pending */
case 2: /* busy */
return -EBUSY;
default: /* device/path not operational */
return cio_start_handle_notoper(sch, lpm);
}
}
int
cio_start (struct subchannel *sch, struct ccw1 *cpa, __u8 lpm)
{
return cio_start_key(sch, cpa, lpm, default_storage_key);
}
/*
* resume suspended I/O operation
*/
int
cio_resume (struct subchannel *sch)
{
char dbf_txt[15];
int ccode;
CIO_TRACE_EVENT (4, "resIO");
CIO_TRACE_EVENT (4, sch->dev.bus_id);
ccode = rsch (sch->irq);
sprintf (dbf_txt, "ccode:%d", ccode);
CIO_TRACE_EVENT (4, dbf_txt);
switch (ccode) {
case 0:
sch->schib.scsw.actl |= SCSW_ACTL_RESUME_PEND;
return 0;
case 1:
return -EBUSY;
case 2:
return -EINVAL;
default:
/*
* useless to wait for request completion
* as device is no longer operational !
*/
return -ENODEV;
}
}
/*
* halt I/O operation
*/
int
cio_halt(struct subchannel *sch)
{
char dbf_txt[15];
int ccode;
if (!sch)
return -ENODEV;
CIO_TRACE_EVENT (2, "haltIO");
CIO_TRACE_EVENT (2, sch->dev.bus_id);
/*
* Issue "Halt subchannel" and process condition code
*/
ccode = hsch (sch->irq);
sprintf (dbf_txt, "ccode:%d", ccode);
CIO_TRACE_EVENT (2, dbf_txt);
switch (ccode) {
case 0:
sch->schib.scsw.actl |= SCSW_ACTL_HALT_PEND;
return 0;
case 1: /* status pending */
case 2: /* busy */
return -EBUSY;
default: /* device not operational */
return -ENODEV;
}
}
/*
* Clear I/O operation
*/
int
cio_clear(struct subchannel *sch)
{
char dbf_txt[15];
int ccode;
if (!sch)
return -ENODEV;
CIO_TRACE_EVENT (2, "clearIO");
CIO_TRACE_EVENT (2, sch->dev.bus_id);
/*
* Issue "Clear subchannel" and process condition code
*/
ccode = csch (sch->irq);
sprintf (dbf_txt, "ccode:%d", ccode);
CIO_TRACE_EVENT (2, dbf_txt);
switch (ccode) {
case 0:
sch->schib.scsw.actl |= SCSW_ACTL_CLEAR_PEND;
return 0;
default: /* device not operational */
return -ENODEV;
}
}
/*
* Function: cio_cancel
* Issues a "Cancel Subchannel" on the specified subchannel
* Note: We don't need any fancy intparms and flags here
* since xsch is executed synchronously.
* Only for common I/O internal use as for now.
*/
int
cio_cancel (struct subchannel *sch)
{
char dbf_txt[15];
int ccode;
if (!sch)
return -ENODEV;
CIO_TRACE_EVENT (2, "cancelIO");
CIO_TRACE_EVENT (2, sch->dev.bus_id);
ccode = xsch (sch->irq);
sprintf (dbf_txt, "ccode:%d", ccode);
CIO_TRACE_EVENT (2, dbf_txt);
switch (ccode) {
case 0: /* success */
/* Update information in scsw. */
stsch (sch->irq, &sch->schib);
return 0;
case 1: /* status pending */
return -EBUSY;
case 2: /* not applicable */
return -EINVAL;
default: /* not oper */
return -ENODEV;
}
}
/*
* Function: cio_modify
* Issues a "Modify Subchannel" on the specified subchannel
*/
int
cio_modify (struct subchannel *sch)
{
int ccode, retry, ret;
ret = 0;
for (retry = 0; retry < 5; retry++) {
ccode = msch_err (sch->irq, &sch->schib);
if (ccode < 0) /* -EIO if msch gets a program check. */
return ccode;
switch (ccode) {
case 0: /* successfull */
return 0;
case 1: /* status pending */
return -EBUSY;
case 2: /* busy */
udelay (100); /* allow for recovery */
ret = -EBUSY;
break;
case 3: /* not operational */
return -ENODEV;
}
}
return ret;
}
/*
* Enable subchannel.
*/
int
cio_enable_subchannel (struct subchannel *sch, unsigned int isc)
{
char dbf_txt[15];
int ccode;
int retry;
int ret;
CIO_TRACE_EVENT (2, "ensch");
CIO_TRACE_EVENT (2, sch->dev.bus_id);
ccode = stsch (sch->irq, &sch->schib);
if (ccode)
return -ENODEV;
for (retry = 5, ret = 0; retry > 0; retry--) {
sch->schib.pmcw.ena = 1;
sch->schib.pmcw.isc = isc;
sch->schib.pmcw.intparm = (__u32)(unsigned long)sch;
ret = cio_modify(sch);
if (ret == -ENODEV)
break;
if (ret == -EIO)
/*
* Got a program check in cio_modify. Try without
* the concurrent sense bit the next time.
*/
sch->schib.pmcw.csense = 0;
if (ret == 0) {
stsch (sch->irq, &sch->schib);
if (sch->schib.pmcw.ena)
break;
}
if (ret == -EBUSY) {
struct irb irb;
if (tsch(sch->irq, &irb) != 0)
break;
}
}
sprintf (dbf_txt, "ret:%d", ret);
CIO_TRACE_EVENT (2, dbf_txt);
return ret;
}
/*
* Disable subchannel.
*/
int
cio_disable_subchannel (struct subchannel *sch)
{
char dbf_txt[15];
int ccode;
int retry;
int ret;
CIO_TRACE_EVENT (2, "dissch");
CIO_TRACE_EVENT (2, sch->dev.bus_id);
ccode = stsch (sch->irq, &sch->schib);
if (ccode == 3) /* Not operational. */
return -ENODEV;
if (sch->schib.scsw.actl != 0)
/*
* the disable function must not be called while there are
* requests pending for completion !
*/
return -EBUSY;
for (retry = 5, ret = 0; retry > 0; retry--) {
sch->schib.pmcw.ena = 0;
ret = cio_modify(sch);
if (ret == -ENODEV)
break;
if (ret == -EBUSY)
/*
* The subchannel is busy or status pending.
* We'll disable when the next interrupt was delivered
* via the state machine.
*/
break;
if (ret == 0) {
stsch (sch->irq, &sch->schib);
if (!sch->schib.pmcw.ena)
break;
}
}
sprintf (dbf_txt, "ret:%d", ret);
CIO_TRACE_EVENT (2, dbf_txt);
return ret;
}
/*
* cio_validate_subchannel()
*
* Find out subchannel type and initialize struct subchannel.
* Return codes:
* SUBCHANNEL_TYPE_IO for a normal io subchannel
* SUBCHANNEL_TYPE_CHSC for a chsc subchannel
* SUBCHANNEL_TYPE_MESSAGE for a messaging subchannel
* SUBCHANNEL_TYPE_ADM for a adm(?) subchannel
* -ENXIO for non-defined subchannels
* -ENODEV for subchannels with invalid device number or blacklisted devices
*/
int
cio_validate_subchannel (struct subchannel *sch, unsigned int irq)
{
char dbf_txt[15];
int ccode;
sprintf (dbf_txt, "valsch%x", irq);
CIO_TRACE_EVENT (4, dbf_txt);
/* Nuke all fields. */
memset(sch, 0, sizeof(struct subchannel));
spin_lock_init(&sch->lock);
/* Set a name for the subchannel */
snprintf (sch->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", irq);
/*
* The first subchannel that is not-operational (ccode==3)
* indicates that there aren't any more devices available.
*/
sch->irq = irq;
ccode = stsch (irq, &sch->schib);
if (ccode)
return -ENXIO;
/* Copy subchannel type from path management control word. */
sch->st = sch->schib.pmcw.st;
/*
* ... just being curious we check for non I/O subchannels
*/
if (sch->st != 0) {
CIO_DEBUG(KERN_INFO, 0,
"Subchannel %04X reports "
"non-I/O subchannel type %04X\n",
sch->irq, sch->st);
/* We stop here for non-io subchannels. */
return sch->st;
}
/* Initialization for io subchannels. */
if (!sch->schib.pmcw.dnv)
/* io subchannel but device number is invalid. */
return -ENODEV;
/* Devno is valid. */
if (is_blacklisted (sch->schib.pmcw.dev)) {
/*
* This device must not be known to Linux. So we simply
* say that there is no device and return ENODEV.
*/
CIO_MSG_EVENT(0, "Blacklisted device detected "
"at devno %04X\n", sch->schib.pmcw.dev);
return -ENODEV;
}
sch->opm = 0xff;
chsc_validate_chpids(sch);
sch->lpm = sch->schib.pmcw.pim &
sch->schib.pmcw.pam &
sch->schib.pmcw.pom &
sch->opm;
CIO_DEBUG(KERN_INFO, 0,
"Detected device %04X on subchannel %04X"
" - PIM = %02X, PAM = %02X, POM = %02X\n",
sch->schib.pmcw.dev, sch->irq, sch->schib.pmcw.pim,
sch->schib.pmcw.pam, sch->schib.pmcw.pom);
/*
* We now have to initially ...
* ... set "interruption subclass"
* ... enable "concurrent sense"
* ... enable "multipath mode" if more than one
* CHPID is available. This is done regardless
* whether multiple paths are available for us.
*/
sch->schib.pmcw.isc = 3; /* could be smth. else */
sch->schib.pmcw.csense = 1; /* concurrent sense */
sch->schib.pmcw.ena = 0;
if ((sch->lpm & (sch->lpm - 1)) != 0)
sch->schib.pmcw.mp = 1; /* multipath mode */
return 0;
}
/*
* do_IRQ() handles all normal I/O device IRQ's (the special
* SMP cross-CPU interrupts have their own specific
* handlers).
*
*/
void
do_IRQ (struct pt_regs *regs)
{
struct tpi_info *tpi_info;
struct subchannel *sch;
struct irb *irb;
irq_enter ();
asm volatile ("mc 0,0");
if (S390_lowcore.int_clock >= S390_lowcore.jiffy_timer)
/**
* Make sure that the i/o interrupt did not "overtake"
* the last HZ timer interrupt.
*/
account_ticks(regs);
/*
* Get interrupt information from lowcore
*/
tpi_info = (struct tpi_info *) __LC_SUBCHANNEL_ID;
irb = (struct irb *) __LC_IRB;
do {
kstat_cpu(smp_processor_id()).irqs[IO_INTERRUPT]++;
/*
* Non I/O-subchannel thin interrupts are processed differently
*/
if (tpi_info->adapter_IO == 1 &&
tpi_info->int_type == IO_INTERRUPT_TYPE) {
do_adapter_IO();
continue;
}
sch = (struct subchannel *)(unsigned long)tpi_info->intparm;
if (sch)
spin_lock(&sch->lock);
/* Store interrupt response block to lowcore. */
if (tsch (tpi_info->irq, irb) == 0 && sch) {
/* Keep subchannel information word up to date. */
memcpy (&sch->schib.scsw, &irb->scsw,
sizeof (irb->scsw));
/* Call interrupt handler if there is one. */
if (sch->driver && sch->driver->irq)
sch->driver->irq(&sch->dev);
}
if (sch)
spin_unlock(&sch->lock);
/*
* Are more interrupts pending?
* If so, the tpi instruction will update the lowcore
* to hold the info for the next interrupt.
* We don't do this for VM because a tpi drops the cpu
* out of the sie which costs more cycles than it saves.
*/
} while (!MACHINE_IS_VM && tpi (NULL) != 0);
irq_exit ();
}
#ifdef CONFIG_CCW_CONSOLE
static struct subchannel console_subchannel;
static int console_subchannel_in_use;
/*
* busy wait for the next interrupt on the console
*/
void
wait_cons_dev (void)
{
unsigned long cr6 __attribute__ ((aligned (8)));
unsigned long save_cr6 __attribute__ ((aligned (8)));
/*
* before entering the spinlock we may already have
* processed the interrupt on a different CPU...
*/
if (!console_subchannel_in_use)
return;
/* disable all but isc 7 (console device) */
__ctl_store (save_cr6, 6, 6);
cr6 = 0x01000000;
__ctl_load (cr6, 6, 6);
do {
spin_unlock(&console_subchannel.lock);
if (!cio_tpi())
cpu_relax();
spin_lock(&console_subchannel.lock);
} while (console_subchannel.schib.scsw.actl != 0);
/*
* restore previous isc value
*/
__ctl_load (save_cr6, 6, 6);
}
static int
cio_console_irq(void)
{
int irq;
if (console_irq != -1) {
/* VM provided us with the irq number of the console. */
if (stsch(console_irq, &console_subchannel.schib) != 0 ||
!console_subchannel.schib.pmcw.dnv)
return -1;
console_devno = console_subchannel.schib.pmcw.dev;
} else if (console_devno != -1) {
/* At least the console device number is known. */
for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
if (stsch(irq, &console_subchannel.schib) != 0)
break;
if (console_subchannel.schib.pmcw.dnv &&
console_subchannel.schib.pmcw.dev ==
console_devno) {
console_irq = irq;
break;
}
}
if (console_irq == -1)
return -1;
} else {
/* unlike in 2.4, we cannot autoprobe here, since
* the channel subsystem is not fully initialized.
* With some luck, the HWC console can take over */
printk(KERN_WARNING "No ccw console found!\n");
return -1;
}
return console_irq;
}
struct subchannel *
cio_probe_console(void)
{
int irq, ret;
if (xchg(&console_subchannel_in_use, 1) != 0)
return ERR_PTR(-EBUSY);
irq = cio_console_irq();
if (irq == -1) {
console_subchannel_in_use = 0;
return ERR_PTR(-ENODEV);
}
memset(&console_subchannel, 0, sizeof(struct subchannel));
ret = cio_validate_subchannel(&console_subchannel, irq);
if (ret) {
console_subchannel_in_use = 0;
return ERR_PTR(-ENODEV);
}
/*
* enable console I/O-interrupt subclass 7
*/
ctl_set_bit(6, 24);
console_subchannel.schib.pmcw.isc = 7;
console_subchannel.schib.pmcw.intparm =
(__u32)(unsigned long)&console_subchannel;
ret = cio_modify(&console_subchannel);
if (ret) {
console_subchannel_in_use = 0;
return ERR_PTR(ret);
}
return &console_subchannel;
}
void
cio_release_console(void)
{
console_subchannel.schib.pmcw.intparm = 0;
cio_modify(&console_subchannel);
ctl_clear_bit(6, 24);
console_subchannel_in_use = 0;
}
/* Bah... hack to catch console special sausages. */
int
cio_is_console(int irq)
{
if (!console_subchannel_in_use)
return 0;
return (irq == console_subchannel.irq);
}
struct subchannel *
cio_get_console_subchannel(void)
{
if (!console_subchannel_in_use)
return 0;
return &console_subchannel;
}
#endif
static inline int
__disable_subchannel_easy(unsigned int schid, struct schib *schib)
{
int retry, cc;
cc = 0;
for (retry=0;retry<3;retry++) {
schib->pmcw.ena = 0;
cc = msch(schid, schib);
if (cc)
return (cc==3?-ENODEV:-EBUSY);
stsch(schid, schib);
if (!schib->pmcw.ena)
return 0;
}
return -EBUSY; /* uhm... */
}
static inline int
__clear_subchannel_easy(unsigned int schid)
{
int retry;
if (csch(schid))
return -ENODEV;
for (retry=0;retry<20;retry++) {
struct tpi_info ti;
if (tpi(&ti)) {
tsch(schid, (struct irb *)__LC_IRB);
return 0;
}
udelay(100);
}
return -EBUSY;
}
extern void do_reipl(unsigned long devno);
/* Clear all subchannels. */
void
clear_all_subchannels(void)
{
unsigned int schid;
local_irq_disable();
for (schid=0;schid<=highest_subchannel;schid++) {
struct schib schib;
if (stsch(schid, &schib))
break; /* break out of the loop */
if (!schib.pmcw.ena)
continue;
switch(__disable_subchannel_easy(schid, &schib)) {
case 0:
case -ENODEV:
break;
default: /* -EBUSY */
if (__clear_subchannel_easy(schid))
break; /* give up... jump out of switch */
stsch(schid, &schib);
__disable_subchannel_easy(schid, &schib);
}
}
}
/* Make sure all subchannels are quiet before we re-ipl an lpar. */
void
reipl(unsigned long devno)
{
clear_all_subchannels();
do_reipl(devno);
}

143
drivers/s390/cio/cio.h Normal file
View file

@ -0,0 +1,143 @@
#ifndef S390_CIO_H
#define S390_CIO_H
/*
* where we put the ssd info
*/
struct ssd_info {
__u8 valid:1;
__u8 type:7; /* subchannel type */
__u8 chpid[8]; /* chpids */
__u16 fla[8]; /* full link addresses */
} __attribute__ ((packed));
/*
* path management control word
*/
struct pmcw {
__u32 intparm; /* interruption parameter */
__u32 qf : 1; /* qdio facility */
__u32 res0 : 1; /* reserved zeros */
__u32 isc : 3; /* interruption sublass */
__u32 res5 : 3; /* reserved zeros */
__u32 ena : 1; /* enabled */
__u32 lm : 2; /* limit mode */
__u32 mme : 2; /* measurement-mode enable */
__u32 mp : 1; /* multipath mode */
__u32 tf : 1; /* timing facility */
__u32 dnv : 1; /* device number valid */
__u32 dev : 16; /* device number */
__u8 lpm; /* logical path mask */
__u8 pnom; /* path not operational mask */
__u8 lpum; /* last path used mask */
__u8 pim; /* path installed mask */
__u16 mbi; /* measurement-block index */
__u8 pom; /* path operational mask */
__u8 pam; /* path available mask */
__u8 chpid[8]; /* CHPID 0-7 (if available) */
__u32 unused1 : 8; /* reserved zeros */
__u32 st : 3; /* subchannel type */
__u32 unused2 : 18; /* reserved zeros */
__u32 mbfc : 1; /* measurement block format control */
__u32 xmwme : 1; /* extended measurement word mode enable */
__u32 csense : 1; /* concurrent sense; can be enabled ...*/
/* ... per MSCH, however, if facility */
/* ... is not installed, this results */
/* ... in an operand exception. */
} __attribute__ ((packed));
/*
* subchannel information block
*/
struct schib {
struct pmcw pmcw; /* path management control word */
struct scsw scsw; /* subchannel status word */
__u64 mba; /* measurement block address */
__u8 mda[4]; /* model dependent area */
} __attribute__ ((packed,aligned(4)));
/*
* operation request block
*/
struct orb {
__u32 intparm; /* interruption parameter */
__u32 key : 4; /* flags, like key, suspend control, etc. */
__u32 spnd : 1; /* suspend control */
__u32 res1 : 1; /* reserved */
__u32 mod : 1; /* modification control */
__u32 sync : 1; /* synchronize control */
__u32 fmt : 1; /* format control */
__u32 pfch : 1; /* prefetch control */
__u32 isic : 1; /* initial-status-interruption control */
__u32 alcc : 1; /* address-limit-checking control */
__u32 ssic : 1; /* suppress-suspended-interr. control */
__u32 res2 : 1; /* reserved */
__u32 c64 : 1; /* IDAW/QDIO 64 bit control */
__u32 i2k : 1; /* IDAW 2/4kB block size control */
__u32 lpm : 8; /* logical path mask */
__u32 ils : 1; /* incorrect length */
__u32 zero : 6; /* reserved zeros */
__u32 orbx : 1; /* ORB extension control */
__u32 cpa; /* channel program address */
} __attribute__ ((packed,aligned(4)));
/* subchannel data structure used by I/O subroutines */
struct subchannel {
unsigned int irq; /* aka. subchannel number */
spinlock_t lock; /* subchannel lock */
enum {
SUBCHANNEL_TYPE_IO = 0,
SUBCHANNEL_TYPE_CHSC = 1,
SUBCHANNEL_TYPE_MESSAGE = 2,
SUBCHANNEL_TYPE_ADM = 3,
} st; /* subchannel type */
struct {
unsigned int suspend:1; /* allow suspend */
unsigned int prefetch:1;/* deny prefetch */
unsigned int inter:1; /* suppress intermediate interrupts */
} __attribute__ ((packed)) options;
__u8 vpm; /* verified path mask */
__u8 lpm; /* logical path mask */
__u8 opm; /* operational path mask */
struct schib schib; /* subchannel information block */
struct orb orb; /* operation request block */
struct ccw1 sense_ccw; /* static ccw for sense command */
struct ssd_info ssd_info; /* subchannel description */
struct device dev; /* entry in device tree */
struct css_driver *driver;
} __attribute__ ((aligned(8)));
#define IO_INTERRUPT_TYPE 0 /* I/O interrupt type */
#define to_subchannel(n) container_of(n, struct subchannel, dev)
extern int cio_validate_subchannel (struct subchannel *, unsigned int);
extern int cio_enable_subchannel (struct subchannel *, unsigned int);
extern int cio_disable_subchannel (struct subchannel *);
extern int cio_cancel (struct subchannel *);
extern int cio_clear (struct subchannel *);
extern int cio_resume (struct subchannel *);
extern int cio_halt (struct subchannel *);
extern int cio_start (struct subchannel *, struct ccw1 *, __u8);
extern int cio_start_key (struct subchannel *, struct ccw1 *, __u8, __u8);
extern int cio_cancel (struct subchannel *);
extern int cio_set_options (struct subchannel *, int);
extern int cio_get_options (struct subchannel *);
extern int cio_modify (struct subchannel *);
/* Use with care. */
#ifdef CONFIG_CCW_CONSOLE
extern struct subchannel *cio_probe_console(void);
extern void cio_release_console(void);
extern int cio_is_console(int irq);
extern struct subchannel *cio_get_console_subchannel(void);
#else
#define cio_is_console(irq) 0
#define cio_get_console_subchannel() NULL
#endif
extern int cio_show_msg;
#endif

View file

@ -0,0 +1,32 @@
#ifndef CIO_DEBUG_H
#define CIO_DEBUG_H
#include <asm/debug.h>
#define CIO_TRACE_EVENT(imp, txt) do { \
debug_text_event(cio_debug_trace_id, imp, txt); \
} while (0)
#define CIO_MSG_EVENT(imp, args...) do { \
debug_sprintf_event(cio_debug_msg_id, imp , ##args); \
} while (0)
#define CIO_CRW_EVENT(imp, args...) do { \
debug_sprintf_event(cio_debug_crw_id, imp , ##args); \
} while (0)
#define CIO_HEX_EVENT(imp, args...) do { \
debug_event(cio_debug_trace_id, imp, ##args); \
} while (0)
#define CIO_DEBUG(printk_level,event_level,msg...) ({ \
if (cio_show_msg) printk(printk_level msg); \
CIO_MSG_EVENT (event_level, msg); \
})
/* for use of debug feature */
extern debug_info_t *cio_debug_msg_id;
extern debug_info_t *cio_debug_trace_id;
extern debug_info_t *cio_debug_crw_id;
#endif

1042
drivers/s390/cio/cmf.c Normal file

File diff suppressed because it is too large Load diff

575
drivers/s390/cio/css.c Normal file
View file

@ -0,0 +1,575 @@
/*
* drivers/s390/cio/css.c
* driver for channel subsystem
* $Revision: 1.85 $
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Arnd Bergmann (arndb@de.ibm.com)
* Cornelia Huck (cohuck@de.ibm.com)
*/
#include <linux/module.h>
#include <linux/init.h>
#include <linux/device.h>
#include <linux/slab.h>
#include <linux/errno.h>
#include <linux/list.h>
#include "css.h"
#include "cio.h"
#include "cio_debug.h"
#include "ioasm.h"
#include "chsc.h"
unsigned int highest_subchannel;
int need_rescan = 0;
int css_init_done = 0;
struct pgid global_pgid;
int css_characteristics_avail = 0;
struct device css_bus_device = {
.bus_id = "css0",
};
static struct subchannel *
css_alloc_subchannel(int irq)
{
struct subchannel *sch;
int ret;
sch = kmalloc (sizeof (*sch), GFP_KERNEL | GFP_DMA);
if (sch == NULL)
return ERR_PTR(-ENOMEM);
ret = cio_validate_subchannel (sch, irq);
if (ret < 0) {
kfree(sch);
return ERR_PTR(ret);
}
if (irq > highest_subchannel)
highest_subchannel = irq;
if (sch->st != SUBCHANNEL_TYPE_IO) {
/* For now we ignore all non-io subchannels. */
kfree(sch);
return ERR_PTR(-EINVAL);
}
/*
* Set intparm to subchannel address.
* This is fine even on 64bit since the subchannel is always located
* under 2G.
*/
sch->schib.pmcw.intparm = (__u32)(unsigned long)sch;
ret = cio_modify(sch);
if (ret) {
kfree(sch);
return ERR_PTR(ret);
}
return sch;
}
static void
css_free_subchannel(struct subchannel *sch)
{
if (sch) {
/* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0;
cio_modify(sch);
kfree(sch);
}
}
static void
css_subchannel_release(struct device *dev)
{
struct subchannel *sch;
sch = to_subchannel(dev);
if (!cio_is_console(sch->irq))
kfree(sch);
}
extern int css_get_ssd_info(struct subchannel *sch);
static int
css_register_subchannel(struct subchannel *sch)
{
int ret;
/* Initialize the subchannel structure */
sch->dev.parent = &css_bus_device;
sch->dev.bus = &css_bus_type;
sch->dev.release = &css_subchannel_release;
/* make it known to the system */
ret = device_register(&sch->dev);
if (ret)
printk (KERN_WARNING "%s: could not register %s\n",
__func__, sch->dev.bus_id);
else
css_get_ssd_info(sch);
return ret;
}
int
css_probe_device(int irq)
{
int ret;
struct subchannel *sch;
sch = css_alloc_subchannel(irq);
if (IS_ERR(sch))
return PTR_ERR(sch);
ret = css_register_subchannel(sch);
if (ret)
css_free_subchannel(sch);
return ret;
}
struct subchannel *
get_subchannel_by_schid(int irq)
{
struct subchannel *sch;
struct list_head *entry;
struct device *dev;
if (!get_bus(&css_bus_type))
return NULL;
down_read(&css_bus_type.subsys.rwsem);
sch = NULL;
list_for_each(entry, &css_bus_type.devices.list) {
dev = get_device(container_of(entry,
struct device, bus_list));
if (!dev)
continue;
sch = to_subchannel(dev);
if (sch->irq == irq)
break;
put_device(dev);
sch = NULL;
}
up_read(&css_bus_type.subsys.rwsem);
put_bus(&css_bus_type);
return sch;
}
static inline int
css_get_subchannel_status(struct subchannel *sch, int schid)
{
struct schib schib;
int cc;
cc = stsch(schid, &schib);
if (cc)
return CIO_GONE;
if (!schib.pmcw.dnv)
return CIO_GONE;
if (sch && sch->schib.pmcw.dnv &&
(schib.pmcw.dev != sch->schib.pmcw.dev))
return CIO_REVALIDATE;
if (sch && !sch->lpm)
return CIO_NO_PATH;
return CIO_OPER;
}
static int
css_evaluate_subchannel(int irq, int slow)
{
int event, ret, disc;
struct subchannel *sch;
unsigned long flags;
sch = get_subchannel_by_schid(irq);
disc = sch ? device_is_disconnected(sch) : 0;
if (disc && slow) {
if (sch)
put_device(&sch->dev);
return 0; /* Already processed. */
}
/*
* 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);
if (!disc && !slow) {
if (sch)
put_device(&sch->dev);
return -EAGAIN; /* Will be done on the slow path. */
}
event = css_get_subchannel_status(sch, irq);
CIO_MSG_EVENT(4, "Evaluating schid %04x, event %d, %s, %s path.\n",
irq, event, sch?(disc?"disconnected":"normal"):"unknown",
slow?"slow":"fast");
switch (event) {
case CIO_NO_PATH:
case CIO_GONE:
if (!sch) {
/* Never used this subchannel. Ignore. */
ret = 0;
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);
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;
}
/*
* Unregister subchannel.
* The device will be killed automatically.
*/
cio_disable_subchannel(sch);
device_unregister(&sch->dev);
/* 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) {
device_unregister(&sch->dev);
/* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0;
cio_modify(sch);
put_device(&sch->dev);
ret = css_probe_device(irq);
} 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;
}
break;
case CIO_OPER:
if (disc) {
spin_lock_irqsave(&sch->lock, flags);
/* Get device operational again. */
device_trigger_reprobe(sch);
spin_unlock_irqrestore(&sch->lock, flags);
}
ret = sch ? 0 : css_probe_device(irq);
break;
default:
BUG();
ret = 0;
}
return ret;
}
static void
css_rescan_devices(void)
{
int irq, ret;
for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
ret = css_evaluate_subchannel(irq, 1);
/* No more memory. It doesn't make sense to continue. No
* panic because this can happen in midflight and just
* because we can't use a new device is no reason to crash
* the system. */
if (ret == -ENOMEM)
break;
/* -ENXIO indicates that there are no more subchannels. */
if (ret == -ENXIO)
break;
}
}
struct slow_subchannel {
struct list_head slow_list;
unsigned long schid;
};
static LIST_HEAD(slow_subchannels_head);
static DEFINE_SPINLOCK(slow_subchannel_lock);
static void
css_trigger_slow_path(void)
{
CIO_TRACE_EVENT(4, "slowpath");
if (need_rescan) {
need_rescan = 0;
css_rescan_devices();
return;
}
spin_lock_irq(&slow_subchannel_lock);
while (!list_empty(&slow_subchannels_head)) {
struct slow_subchannel *slow_sch =
list_entry(slow_subchannels_head.next,
struct slow_subchannel, slow_list);
list_del_init(slow_subchannels_head.next);
spin_unlock_irq(&slow_subchannel_lock);
css_evaluate_subchannel(slow_sch->schid, 1);
spin_lock_irq(&slow_subchannel_lock);
kfree(slow_sch);
}
spin_unlock_irq(&slow_subchannel_lock);
}
typedef void (*workfunc)(void *);
DECLARE_WORK(slow_path_work, (workfunc)css_trigger_slow_path, NULL);
struct workqueue_struct *slow_path_wq;
/*
* Rescan for new devices. FIXME: This is slow.
* This function is called when we have lost CRWs due to overflows and we have
* to do subchannel housekeeping.
*/
void
css_reiterate_subchannels(void)
{
css_clear_subchannel_slow_list();
need_rescan = 1;
}
/*
* Called from the machine check handler for subchannel report words.
*/
int
css_process_crw(int irq)
{
int ret;
CIO_CRW_EVENT(2, "source is subchannel %04X\n", irq);
if (need_rescan)
/* We need to iterate all subchannels anyway. */
return -EAGAIN;
/*
* Since we are always presented with IPI in the CRW, we have to
* use stsch() to find out if the subchannel in question has come
* or gone.
*/
ret = css_evaluate_subchannel(irq, 0);
if (ret == -EAGAIN) {
if (css_enqueue_subchannel_slow(irq)) {
css_clear_subchannel_slow_list();
need_rescan = 1;
}
}
return ret;
}
static void __init
css_generate_pgid(void)
{
/* Let's build our path group ID here. */
if (css_characteristics_avail && css_general_characteristics.mcss)
global_pgid.cpu_addr = 0x8000;
else {
#ifdef CONFIG_SMP
global_pgid.cpu_addr = hard_smp_processor_id();
#else
global_pgid.cpu_addr = 0;
#endif
}
global_pgid.cpu_id = ((cpuid_t *) __LC_CPUID)->ident;
global_pgid.cpu_model = ((cpuid_t *) __LC_CPUID)->machine;
global_pgid.tod_high = (__u32) (get_clock() >> 32);
}
/*
* Now that the driver core is running, we can setup our channel subsystem.
* The struct subchannel's are created during probing (except for the
* static console subchannel).
*/
static int __init
init_channel_subsystem (void)
{
int ret, irq;
if (chsc_determine_css_characteristics() == 0)
css_characteristics_avail = 1;
css_generate_pgid();
if ((ret = bus_register(&css_bus_type)))
goto out;
if ((ret = device_register (&css_bus_device)))
goto out_bus;
css_init_done = 1;
ctl_set_bit(6, 28);
for (irq = 0; irq < __MAX_SUBCHANNELS; irq++) {
struct subchannel *sch;
if (cio_is_console(irq))
sch = cio_get_console_subchannel();
else {
sch = css_alloc_subchannel(irq);
if (IS_ERR(sch))
ret = PTR_ERR(sch);
else
ret = 0;
if (ret == -ENOMEM)
panic("Out of memory in "
"init_channel_subsystem\n");
/* -ENXIO: no more subchannels. */
if (ret == -ENXIO)
break;
if (ret)
continue;
}
/*
* We register ALL valid subchannels in ioinfo, even those
* that have been present before init_channel_subsystem.
* These subchannels can't have been registered yet (kmalloc
* not working) so we do it now. This is true e.g. for the
* console subchannel.
*/
css_register_subchannel(sch);
}
return 0;
out_bus:
bus_unregister(&css_bus_type);
out:
return ret;
}
/*
* find a driver for a subchannel. They identify by the subchannel
* type with the exception that the console subchannel driver has its own
* subchannel type although the device is an i/o subchannel
*/
static int
css_bus_match (struct device *dev, struct device_driver *drv)
{
struct subchannel *sch = container_of (dev, struct subchannel, dev);
struct css_driver *driver = container_of (drv, struct css_driver, drv);
if (sch->st == driver->subchannel_type)
return 1;
return 0;
}
struct bus_type css_bus_type = {
.name = "css",
.match = &css_bus_match,
};
subsys_initcall(init_channel_subsystem);
/*
* Register root devices for some drivers. The release function must not be
* in the device drivers, so we do it here.
*/
static void
s390_root_dev_release(struct device *dev)
{
kfree(dev);
}
struct device *
s390_root_dev_register(const char *name)
{
struct device *dev;
int ret;
if (!strlen(name))
return ERR_PTR(-EINVAL);
dev = kmalloc(sizeof(struct device), GFP_KERNEL);
if (!dev)
return ERR_PTR(-ENOMEM);
memset(dev, 0, sizeof(struct device));
strncpy(dev->bus_id, name, min(strlen(name), (size_t)BUS_ID_SIZE));
dev->release = s390_root_dev_release;
ret = device_register(dev);
if (ret) {
kfree(dev);
return ERR_PTR(ret);
}
return dev;
}
void
s390_root_dev_unregister(struct device *dev)
{
if (dev)
device_unregister(dev);
}
int
css_enqueue_subchannel_slow(unsigned long schid)
{
struct slow_subchannel *new_slow_sch;
unsigned long flags;
new_slow_sch = kmalloc(sizeof(struct slow_subchannel), GFP_ATOMIC);
if (!new_slow_sch)
return -ENOMEM;
memset(new_slow_sch, 0, sizeof(struct slow_subchannel));
new_slow_sch->schid = schid;
spin_lock_irqsave(&slow_subchannel_lock, flags);
list_add_tail(&new_slow_sch->slow_list, &slow_subchannels_head);
spin_unlock_irqrestore(&slow_subchannel_lock, flags);
return 0;
}
void
css_clear_subchannel_slow_list(void)
{
unsigned long flags;
spin_lock_irqsave(&slow_subchannel_lock, flags);
while (!list_empty(&slow_subchannels_head)) {
struct slow_subchannel *slow_sch =
list_entry(slow_subchannels_head.next,
struct slow_subchannel, slow_list);
list_del_init(slow_subchannels_head.next);
kfree(slow_sch);
}
spin_unlock_irqrestore(&slow_subchannel_lock, flags);
}
int
css_slow_subchannels_exist(void)
{
return (!list_empty(&slow_subchannels_head));
}
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(css_bus_type);
EXPORT_SYMBOL(s390_root_dev_register);
EXPORT_SYMBOL(s390_root_dev_unregister);
EXPORT_SYMBOL_GPL(css_characteristics_avail);

155
drivers/s390/cio/css.h Normal file
View file

@ -0,0 +1,155 @@
#ifndef _CSS_H
#define _CSS_H
#include <linux/wait.h>
#include <linux/workqueue.h>
#include <asm/cio.h>
/*
* path grouping stuff
*/
#define SPID_FUNC_SINGLE_PATH 0x00
#define SPID_FUNC_MULTI_PATH 0x80
#define SPID_FUNC_ESTABLISH 0x00
#define SPID_FUNC_RESIGN 0x40
#define SPID_FUNC_DISBAND 0x20
#define SNID_STATE1_RESET 0
#define SNID_STATE1_UNGROUPED 2
#define SNID_STATE1_GROUPED 3
#define SNID_STATE2_NOT_RESVD 0
#define SNID_STATE2_RESVD_ELSE 2
#define SNID_STATE2_RESVD_SELF 3
#define SNID_STATE3_MULTI_PATH 1
#define SNID_STATE3_SINGLE_PATH 0
struct path_state {
__u8 state1 : 2; /* path state value 1 */
__u8 state2 : 2; /* path state value 2 */
__u8 state3 : 1; /* path state value 3 */
__u8 resvd : 3; /* reserved */
} __attribute__ ((packed));
struct pgid {
union {
__u8 fc; /* SPID function code */
struct path_state ps; /* SNID path state */
} inf;
__u32 cpu_addr : 16; /* CPU address */
__u32 cpu_id : 24; /* CPU identification */
__u32 cpu_model : 16; /* CPU model */
__u32 tod_high; /* high word TOD clock */
} __attribute__ ((packed));
extern struct pgid global_pgid;
#define MAX_CIWS 8
/*
* sense-id response buffer layout
*/
struct senseid {
/* common part */
__u8 reserved; /* always 0x'FF' */
__u16 cu_type; /* control unit type */
__u8 cu_model; /* control unit model */
__u16 dev_type; /* device type */
__u8 dev_model; /* device model */
__u8 unused; /* padding byte */
/* extended part */
struct ciw ciw[MAX_CIWS]; /* variable # of CIWs */
} __attribute__ ((packed,aligned(4)));
struct ccw_device_private {
int state; /* device state */
atomic_t onoff;
unsigned long registered;
__u16 devno; /* device number */
__u16 irq; /* subchannel number */
__u8 imask; /* lpm mask for SNID/SID/SPGID */
int iretry; /* retry counter SNID/SID/SPGID */
struct {
unsigned int fast:1; /* post with "channel end" */
unsigned int repall:1; /* report every interrupt status */
unsigned int pgroup:1; /* do path grouping */
unsigned int force:1; /* allow forced online */
} __attribute__ ((packed)) options;
struct {
unsigned int pgid_single:1; /* use single path for Set PGID */
unsigned int esid:1; /* Ext. SenseID supported by HW */
unsigned int dosense:1; /* delayed SENSE required */
unsigned int doverify:1; /* delayed path verification */
unsigned int donotify:1; /* call notify function */
unsigned int recog_done:1; /* dev. recog. complete */
unsigned int fake_irb:1; /* deliver faked irb */
} __attribute__((packed)) flags;
unsigned long intparm; /* user interruption parameter */
struct qdio_irq *qdio_data;
struct irb irb; /* device status */
struct senseid senseid; /* SenseID info */
struct pgid pgid; /* path group ID */
struct ccw1 iccws[2]; /* ccws for SNID/SID/SPGID commands */
struct work_struct kick_work;
wait_queue_head_t wait_q;
struct timer_list timer;
void *cmb; /* measurement information */
struct list_head cmb_list; /* list of measured devices */
u64 cmb_start_time; /* clock value of cmb reset */
void *cmb_wait; /* deferred cmb enable/disable */
};
/*
* A css driver handles all subchannels of one type.
* Currently, we only care about I/O subchannels (type 0), these
* have a ccw_device connected to them.
*/
struct css_driver {
unsigned int subchannel_type;
struct device_driver drv;
void (*irq)(struct device *);
int (*notify)(struct device *, int);
void (*verify)(struct device *);
void (*termination)(struct device *);
};
/*
* all css_drivers have the css_bus_type
*/
extern struct bus_type css_bus_type;
extern struct css_driver io_subchannel_driver;
int css_probe_device(int irq);
extern struct subchannel * get_subchannel_by_schid(int irq);
extern unsigned int highest_subchannel;
extern int css_init_done;
#define __MAX_SUBCHANNELS 65536
extern struct bus_type css_bus_type;
extern struct device css_bus_device;
/* Some helper functions for disconnected state. */
int device_is_disconnected(struct subchannel *);
void device_set_disconnected(struct subchannel *);
void device_trigger_reprobe(struct subchannel *);
/* Helper functions for vary on/off. */
int device_is_online(struct subchannel *);
void device_set_waiting(struct subchannel *);
/* Machine check helper function. */
void device_kill_pending_timer(struct subchannel *);
/* Helper functions to build lists for the slow path. */
int css_enqueue_subchannel_slow(unsigned long schid);
void css_walk_subchannel_slow_list(void (*fn)(unsigned long));
void css_clear_subchannel_slow_list(void);
int css_slow_subchannels_exist(void);
extern int need_rescan;
extern struct workqueue_struct *slow_path_wq;
extern struct work_struct slow_path_work;
#endif

1135
drivers/s390/cio/device.c Normal file

File diff suppressed because it is too large Load diff

115
drivers/s390/cio/device.h Normal file
View file

@ -0,0 +1,115 @@
#ifndef S390_DEVICE_H
#define S390_DEVICE_H
/*
* states of the device statemachine
*/
enum dev_state {
DEV_STATE_NOT_OPER,
DEV_STATE_SENSE_PGID,
DEV_STATE_SENSE_ID,
DEV_STATE_OFFLINE,
DEV_STATE_VERIFY,
DEV_STATE_ONLINE,
DEV_STATE_W4SENSE,
DEV_STATE_DISBAND_PGID,
DEV_STATE_BOXED,
/* states to wait for i/o completion before doing something */
DEV_STATE_CLEAR_VERIFY,
DEV_STATE_TIMEOUT_KILL,
DEV_STATE_WAIT4IO,
DEV_STATE_QUIESCE,
/* special states for devices gone not operational */
DEV_STATE_DISCONNECTED,
DEV_STATE_DISCONNECTED_SENSE_ID,
DEV_STATE_CMFCHANGE,
/* last element! */
NR_DEV_STATES
};
/*
* asynchronous events of the device statemachine
*/
enum dev_event {
DEV_EVENT_NOTOPER,
DEV_EVENT_INTERRUPT,
DEV_EVENT_TIMEOUT,
DEV_EVENT_VERIFY,
/* last element! */
NR_DEV_EVENTS
};
struct ccw_device;
/*
* action called through jumptable
*/
typedef void (fsm_func_t)(struct ccw_device *, enum dev_event);
extern fsm_func_t *dev_jumptable[NR_DEV_STATES][NR_DEV_EVENTS];
static inline void
dev_fsm_event(struct ccw_device *cdev, enum dev_event dev_event)
{
dev_jumptable[cdev->private->state][dev_event](cdev, dev_event);
}
/*
* Delivers 1 if the device state is final.
*/
static inline int
dev_fsm_final_state(struct ccw_device *cdev)
{
return (cdev->private->state == DEV_STATE_NOT_OPER ||
cdev->private->state == DEV_STATE_OFFLINE ||
cdev->private->state == DEV_STATE_ONLINE ||
cdev->private->state == DEV_STATE_BOXED);
}
extern struct workqueue_struct *ccw_device_work;
extern struct workqueue_struct *ccw_device_notify_work;
void io_subchannel_recog_done(struct ccw_device *cdev);
int ccw_device_cancel_halt_clear(struct ccw_device *);
int ccw_device_register(struct ccw_device *);
void ccw_device_do_unreg_rereg(void *);
void ccw_device_call_sch_unregister(void *);
int ccw_device_recognition(struct ccw_device *);
int ccw_device_online(struct ccw_device *);
int ccw_device_offline(struct ccw_device *);
/* Function prototypes for device status and basic sense stuff. */
void ccw_device_accumulate_irb(struct ccw_device *, struct irb *);
void ccw_device_accumulate_basic_sense(struct ccw_device *, struct irb *);
int ccw_device_accumulate_and_sense(struct ccw_device *, struct irb *);
int ccw_device_do_sense(struct ccw_device *, struct irb *);
/* Function prototypes for sense id stuff. */
void ccw_device_sense_id_start(struct ccw_device *);
void ccw_device_sense_id_irq(struct ccw_device *, enum dev_event);
void ccw_device_sense_id_done(struct ccw_device *, int);
/* Function prototypes for path grouping stuff. */
void ccw_device_sense_pgid_start(struct ccw_device *);
void ccw_device_sense_pgid_irq(struct ccw_device *, enum dev_event);
void ccw_device_sense_pgid_done(struct ccw_device *, int);
void ccw_device_verify_start(struct ccw_device *);
void ccw_device_verify_irq(struct ccw_device *, enum dev_event);
void ccw_device_verify_done(struct ccw_device *, int);
void ccw_device_disband_start(struct ccw_device *);
void ccw_device_disband_irq(struct ccw_device *, enum dev_event);
void ccw_device_disband_done(struct ccw_device *, int);
int ccw_device_call_handler(struct ccw_device *);
int ccw_device_stlck(struct ccw_device *);
/* qdio needs this. */
void ccw_device_set_timeout(struct ccw_device *, int);
void retry_set_schib(struct ccw_device *cdev);
#endif

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,355 @@
/*
* drivers/s390/cio/device_id.c
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Cornelia Huck(cohuck@de.ibm.com)
* Martin Schwidefsky (schwidefsky@de.ibm.com)
*
* Sense ID functions.
*/
#include <linux/module.h>
#include <linux/config.h>
#include <linux/init.h>
#include <asm/ccwdev.h>
#include <asm/delay.h>
#include <asm/cio.h>
#include <asm/lowcore.h>
#include "cio.h"
#include "cio_debug.h"
#include "css.h"
#include "device.h"
#include "ioasm.h"
/*
* diag210 is used under VM to get information about a virtual device
*/
#ifdef CONFIG_ARCH_S390X
int
diag210(struct diag210 * addr)
{
/*
* diag 210 needs its data below the 2GB border, so we
* use a static data area to be sure
*/
static struct diag210 diag210_tmp;
static DEFINE_SPINLOCK(diag210_lock);
unsigned long flags;
int ccode;
spin_lock_irqsave(&diag210_lock, flags);
diag210_tmp = *addr;
asm volatile (
" lhi %0,-1\n"
" sam31\n"
" diag %1,0,0x210\n"
"0: ipm %0\n"
" srl %0,28\n"
"1: sam64\n"
".section __ex_table,\"a\"\n"
" .align 8\n"
" .quad 0b,1b\n"
".previous"
: "=&d" (ccode) : "a" (__pa(&diag210_tmp)) : "cc", "memory" );
*addr = diag210_tmp;
spin_unlock_irqrestore(&diag210_lock, flags);
return ccode;
}
#else
int
diag210(struct diag210 * addr)
{
int ccode;
asm volatile (
" lhi %0,-1\n"
" diag %1,0,0x210\n"
"0: ipm %0\n"
" srl %0,28\n"
"1:\n"
".section __ex_table,\"a\"\n"
" .align 4\n"
" .long 0b,1b\n"
".previous"
: "=&d" (ccode) : "a" (__pa(addr)) : "cc", "memory" );
return ccode;
}
#endif
/*
* Input :
* devno - device number
* ps - pointer to sense ID data area
* Output : none
*/
static void
VM_virtual_device_info (__u16 devno, struct senseid *ps)
{
static struct {
int vrdcvcla, vrdcvtyp, cu_type;
} vm_devices[] = {
{ 0x08, 0x01, 0x3480 },
{ 0x08, 0x02, 0x3430 },
{ 0x08, 0x10, 0x3420 },
{ 0x08, 0x42, 0x3424 },
{ 0x08, 0x44, 0x9348 },
{ 0x08, 0x81, 0x3490 },
{ 0x08, 0x82, 0x3422 },
{ 0x10, 0x41, 0x1403 },
{ 0x10, 0x42, 0x3211 },
{ 0x10, 0x43, 0x3203 },
{ 0x10, 0x45, 0x3800 },
{ 0x10, 0x47, 0x3262 },
{ 0x10, 0x48, 0x3820 },
{ 0x10, 0x49, 0x3800 },
{ 0x10, 0x4a, 0x4245 },
{ 0x10, 0x4b, 0x4248 },
{ 0x10, 0x4d, 0x3800 },
{ 0x10, 0x4e, 0x3820 },
{ 0x10, 0x4f, 0x3820 },
{ 0x10, 0x82, 0x2540 },
{ 0x10, 0x84, 0x3525 },
{ 0x20, 0x81, 0x2501 },
{ 0x20, 0x82, 0x2540 },
{ 0x20, 0x84, 0x3505 },
{ 0x40, 0x01, 0x3278 },
{ 0x40, 0x04, 0x3277 },
{ 0x40, 0x80, 0x2250 },
{ 0x40, 0xc0, 0x5080 },
{ 0x80, 0x00, 0x3215 },
};
struct diag210 diag_data;
int ccode, i;
CIO_TRACE_EVENT (4, "VMvdinf");
diag_data = (struct diag210) {
.vrdcdvno = devno,
.vrdclen = sizeof (diag_data),
};
ccode = diag210 (&diag_data);
ps->reserved = 0xff;
/* Special case for bloody osa devices. */
if (diag_data.vrdcvcla == 0x02 &&
diag_data.vrdcvtyp == 0x20) {
ps->cu_type = 0x3088;
ps->cu_model = 0x60;
return;
}
for (i = 0; i < sizeof(vm_devices) / sizeof(vm_devices[0]); i++)
if (diag_data.vrdcvcla == vm_devices[i].vrdcvcla &&
diag_data.vrdcvtyp == vm_devices[i].vrdcvtyp) {
ps->cu_type = vm_devices[i].cu_type;
return;
}
CIO_MSG_EVENT(0, "DIAG X'210' for device %04X returned (cc = %d):"
"vdev class : %02X, vdev type : %04X \n ... "
"rdev class : %02X, rdev type : %04X, "
"rdev model: %02X\n",
devno, ccode,
diag_data.vrdcvcla, diag_data.vrdcvtyp,
diag_data.vrdcrccl, diag_data.vrdccrty,
diag_data.vrdccrmd);
}
/*
* Start Sense ID helper function.
* Try to obtain the 'control unit'/'device type' information
* associated with the subchannel.
*/
static int
__ccw_device_sense_id_start(struct ccw_device *cdev)
{
struct subchannel *sch;
struct ccw1 *ccw;
int ret;
sch = to_subchannel(cdev->dev.parent);
/* Setup sense channel program. */
ccw = cdev->private->iccws;
if (sch->schib.pmcw.pim != 0x80) {
/* more than one path installed. */
ccw->cmd_code = CCW_CMD_SUSPEND_RECONN;
ccw->cda = 0;
ccw->count = 0;
ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC;
ccw++;
}
ccw->cmd_code = CCW_CMD_SENSE_ID;
ccw->cda = (__u32) __pa (&cdev->private->senseid);
ccw->count = sizeof (struct senseid);
ccw->flags = CCW_FLAG_SLI;
/* Reset device status. */
memset(&cdev->private->irb, 0, sizeof(struct irb));
/* Try on every path. */
ret = -ENODEV;
while (cdev->private->imask != 0) {
if ((sch->opm & cdev->private->imask) != 0 &&
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)
return ret;
}
cdev->private->imask >>= 1;
cdev->private->iretry = 5;
}
return ret;
}
void
ccw_device_sense_id_start(struct ccw_device *cdev)
{
int ret;
memset (&cdev->private->senseid, 0, sizeof (struct senseid));
cdev->private->senseid.cu_type = 0xFFFF;
cdev->private->imask = 0x80;
cdev->private->iretry = 5;
ret = __ccw_device_sense_id_start(cdev);
if (ret && ret != -EBUSY)
ccw_device_sense_id_done(cdev, ret);
}
/*
* Called from interrupt context to check if a valid answer
* to Sense ID was received.
*/
static int
ccw_device_check_sense_id(struct ccw_device *cdev)
{
struct subchannel *sch;
struct irb *irb;
sch = to_subchannel(cdev->dev.parent);
irb = &cdev->private->irb;
/* Did we get a proper answer ? */
if (cdev->private->senseid.cu_type != 0xFFFF &&
cdev->private->senseid.reserved == 0xFF) {
if (irb->scsw.count < sizeof (struct senseid) - 8)
cdev->private->flags.esid = 1;
return 0; /* Success */
}
/* Check the error cases. */
if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
return -ETIME;
if (irb->esw.esw0.erw.cons && (irb->ecw[0] & SNS0_CMD_REJECT)) {
/*
* if the device doesn't support the SenseID
* command further retries wouldn't help ...
* NB: We don't check here for intervention required like we
* did before, because tape devices with no tape inserted
* may present this status *in conjunction with* the
* sense id information. So, for intervention required,
* we use the "whack it until it talks" strategy...
*/
CIO_MSG_EVENT(2, "SenseID : device %04x on Subchannel %04x "
"reports cmd reject\n",
cdev->private->devno, sch->irq);
return -EOPNOTSUPP;
}
if (irb->esw.esw0.erw.cons) {
CIO_MSG_EVENT(2, "SenseID : UC on dev %04x, "
"lpum %02X, cnt %02d, sns :"
" %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
cdev->private->devno,
irb->esw.esw0.sublog.lpum,
irb->esw.esw0.erw.scnt,
irb->ecw[0], irb->ecw[1],
irb->ecw[2], irb->ecw[3],
irb->ecw[4], irb->ecw[5],
irb->ecw[6], irb->ecw[7]);
return -EAGAIN;
}
if (irb->scsw.cc == 3) {
if ((sch->orb.lpm &
sch->schib.pmcw.pim & sch->schib.pmcw.pam) != 0)
CIO_MSG_EVENT(2, "SenseID : path %02X for device %04x on"
" subchannel %04x is 'not operational'\n",
sch->orb.lpm, cdev->private->devno,
sch->irq);
return -EACCES;
}
/* Hmm, whatever happened, try again. */
CIO_MSG_EVENT(2, "SenseID : start_IO() for device %04x on "
"subchannel %04x returns status %02X%02X\n",
cdev->private->devno, sch->irq,
irb->scsw.dstat, irb->scsw.cstat);
return -EAGAIN;
}
/*
* Got interrupt for Sense ID.
*/
void
ccw_device_sense_id_irq(struct ccw_device *cdev, enum dev_event dev_event)
{
struct subchannel *sch;
struct irb *irb;
int ret;
sch = to_subchannel(cdev->dev.parent);
irb = (struct irb *) __LC_IRB;
/* Retry sense id, if needed. */
if (irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
if ((irb->scsw.cc == 1) || !irb->scsw.actl) {
ret = __ccw_device_sense_id_start(cdev);
if (ret && ret != -EBUSY)
ccw_device_sense_id_done(cdev, ret);
}
return;
}
if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
return;
ret = ccw_device_check_sense_id(cdev);
memset(&cdev->private->irb, 0, sizeof(struct irb));
switch (ret) {
/* 0, -ETIME, -EOPNOTSUPP, -EAGAIN or -EACCES */
case 0: /* Sense id succeeded. */
case -ETIME: /* Sense id stopped by timeout. */
ccw_device_sense_id_done(cdev, ret);
break;
case -EACCES: /* channel is not operational. */
sch->lpm &= ~cdev->private->imask;
cdev->private->imask >>= 1;
cdev->private->iretry = 5;
/* fall through. */
case -EAGAIN: /* try again. */
ret = __ccw_device_sense_id_start(cdev);
if (ret == 0 || ret == -EBUSY)
break;
/* fall through. */
default: /* Sense ID failed. Try asking VM. */
if (MACHINE_IS_VM) {
VM_virtual_device_info (cdev->private->devno,
&cdev->private->senseid);
if (cdev->private->senseid.cu_type != 0xFFFF) {
/* Got the device information from VM. */
ccw_device_sense_id_done(cdev, 0);
return;
}
}
/*
* If we can't couldn't identify the device type we
* consider the device "not operational".
*/
ccw_device_sense_id_done(cdev, -ENODEV);
break;
}
}
EXPORT_SYMBOL(diag210);

View file

@ -0,0 +1,603 @@
/*
* drivers/s390/cio/device_ops.c
*
* $Revision: 1.55 $
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Martin Schwidefsky (schwidefsky@de.ibm.com)
* Cornelia Huck (cohuck@de.ibm.com)
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/errno.h>
#include <linux/slab.h>
#include <linux/list.h>
#include <linux/device.h>
#include <linux/delay.h>
#include <asm/ccwdev.h>
#include <asm/idals.h>
#include <asm/qdio.h>
#include "cio.h"
#include "cio_debug.h"
#include "css.h"
#include "chsc.h"
#include "device.h"
#include "qdio.h"
int
ccw_device_set_options(struct ccw_device *cdev, unsigned long flags)
{
/*
* The flag usage is mutal exclusive ...
*/
if ((flags & CCWDEV_EARLY_NOTIFICATION) &&
(flags & CCWDEV_REPORT_ALL))
return -EINVAL;
cdev->private->options.fast = (flags & CCWDEV_EARLY_NOTIFICATION) != 0;
cdev->private->options.repall = (flags & CCWDEV_REPORT_ALL) != 0;
cdev->private->options.pgroup = (flags & CCWDEV_DO_PATHGROUP) != 0;
cdev->private->options.force = (flags & CCWDEV_ALLOW_FORCE) != 0;
return 0;
}
int
ccw_device_clear(struct ccw_device *cdev, unsigned long intparm)
{
struct subchannel *sch;
int ret;
if (!cdev)
return -ENODEV;
if (cdev->private->state == DEV_STATE_NOT_OPER)
return -ENODEV;
if (cdev->private->state != DEV_STATE_ONLINE &&
cdev->private->state != DEV_STATE_WAIT4IO &&
cdev->private->state != DEV_STATE_W4SENSE)
return -EINVAL;
sch = to_subchannel(cdev->dev.parent);
if (!sch)
return -ENODEV;
ret = cio_clear(sch);
if (ret == 0)
cdev->private->intparm = intparm;
return ret;
}
int
ccw_device_start_key(struct ccw_device *cdev, struct ccw1 *cpa,
unsigned long intparm, __u8 lpm, __u8 key,
unsigned long flags)
{
struct subchannel *sch;
int ret;
if (!cdev)
return -ENODEV;
sch = to_subchannel(cdev->dev.parent);
if (!sch)
return -ENODEV;
if (cdev->private->state == DEV_STATE_NOT_OPER)
return -ENODEV;
if (cdev->private->state == DEV_STATE_VERIFY) {
/* Remember to fake irb when finished. */
if (!cdev->private->flags.fake_irb) {
cdev->private->flags.fake_irb = 1;
cdev->private->intparm = intparm;
return 0;
} else
/* There's already a fake I/O around. */
return -EBUSY;
}
if (cdev->private->state != DEV_STATE_ONLINE ||
((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) &&
!(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) ||
cdev->private->flags.doverify)
return -EBUSY;
ret = cio_set_options (sch, flags);
if (ret)
return ret;
ret = cio_start_key (sch, cpa, lpm, key);
if (ret == 0)
cdev->private->intparm = intparm;
return ret;
}
int
ccw_device_start_timeout_key(struct ccw_device *cdev, struct ccw1 *cpa,
unsigned long intparm, __u8 lpm, __u8 key,
unsigned long flags, int expires)
{
int ret;
if (!cdev)
return -ENODEV;
ccw_device_set_timeout(cdev, expires);
ret = ccw_device_start_key(cdev, cpa, intparm, lpm, key, flags);
if (ret != 0)
ccw_device_set_timeout(cdev, 0);
return ret;
}
int
ccw_device_start(struct ccw_device *cdev, struct ccw1 *cpa,
unsigned long intparm, __u8 lpm, unsigned long flags)
{
return ccw_device_start_key(cdev, cpa, intparm, lpm,
default_storage_key, flags);
}
int
ccw_device_start_timeout(struct ccw_device *cdev, struct ccw1 *cpa,
unsigned long intparm, __u8 lpm, unsigned long flags,
int expires)
{
return ccw_device_start_timeout_key(cdev, cpa, intparm, lpm,
default_storage_key, flags,
expires);
}
int
ccw_device_halt(struct ccw_device *cdev, unsigned long intparm)
{
struct subchannel *sch;
int ret;
if (!cdev)
return -ENODEV;
if (cdev->private->state == DEV_STATE_NOT_OPER)
return -ENODEV;
if (cdev->private->state != DEV_STATE_ONLINE &&
cdev->private->state != DEV_STATE_WAIT4IO &&
cdev->private->state != DEV_STATE_W4SENSE)
return -EINVAL;
sch = to_subchannel(cdev->dev.parent);
if (!sch)
return -ENODEV;
ret = cio_halt(sch);
if (ret == 0)
cdev->private->intparm = intparm;
return ret;
}
int
ccw_device_resume(struct ccw_device *cdev)
{
struct subchannel *sch;
if (!cdev)
return -ENODEV;
sch = to_subchannel(cdev->dev.parent);
if (!sch)
return -ENODEV;
if (cdev->private->state == DEV_STATE_NOT_OPER)
return -ENODEV;
if (cdev->private->state != DEV_STATE_ONLINE ||
!(sch->schib.scsw.actl & SCSW_ACTL_SUSPENDED))
return -EINVAL;
return cio_resume(sch);
}
/*
* Pass interrupt to device driver.
*/
int
ccw_device_call_handler(struct ccw_device *cdev)
{
struct subchannel *sch;
unsigned int stctl;
int ending_status;
sch = to_subchannel(cdev->dev.parent);
/*
* we allow for the device action handler if .
* - we received ending status
* - the action handler requested to see all interrupts
* - we received an intermediate status
* - fast notification was requested (primary status)
* - unsolicited interrupts
*/
stctl = cdev->private->irb.scsw.stctl;
ending_status = (stctl & SCSW_STCTL_SEC_STATUS) ||
(stctl == (SCSW_STCTL_ALERT_STATUS | SCSW_STCTL_STATUS_PEND)) ||
(stctl == SCSW_STCTL_STATUS_PEND);
if (!ending_status &&
!cdev->private->options.repall &&
!(stctl & SCSW_STCTL_INTER_STATUS) &&
!(cdev->private->options.fast &&
(stctl & SCSW_STCTL_PRIM_STATUS)))
return 0;
/*
* Now we are ready to call the device driver interrupt handler.
*/
if (cdev->handler)
cdev->handler(cdev, cdev->private->intparm,
&cdev->private->irb);
/*
* Clear the old and now useless interrupt response block.
*/
memset(&cdev->private->irb, 0, sizeof(struct irb));
return 1;
}
/*
* Search for CIW command in extended sense data.
*/
struct ciw *
ccw_device_get_ciw(struct ccw_device *cdev, __u32 ct)
{
int ciw_cnt;
if (cdev->private->flags.esid == 0)
return NULL;
for (ciw_cnt = 0; ciw_cnt < MAX_CIWS; ciw_cnt++)
if (cdev->private->senseid.ciw[ciw_cnt].ct == ct)
return cdev->private->senseid.ciw + ciw_cnt;
return NULL;
}
__u8
ccw_device_get_path_mask(struct ccw_device *cdev)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
if (!sch)
return 0;
else
return sch->vpm;
}
static void
ccw_device_wake_up(struct ccw_device *cdev, unsigned long ip, struct irb *irb)
{
if (!ip)
/* unsolicited interrupt */
return;
/* Abuse intparm for error reporting. */
if (IS_ERR(irb))
cdev->private->intparm = -EIO;
else if ((irb->scsw.dstat !=
(DEV_STAT_CHN_END|DEV_STAT_DEV_END)) ||
(irb->scsw.cstat != 0)) {
/*
* We didn't get channel end / device end. Check if path
* verification has been started; we can retry after it has
* finished. We also retry unit checks except for command reject
* or intervention required.
*/
if (cdev->private->flags.doverify ||
cdev->private->state == DEV_STATE_VERIFY)
cdev->private->intparm = -EAGAIN;
if ((irb->scsw.dstat & DEV_STAT_UNIT_CHECK) &&
!(irb->ecw[0] &
(SNS0_CMD_REJECT | SNS0_INTERVENTION_REQ)))
cdev->private->intparm = -EAGAIN;
else
cdev->private->intparm = -EIO;
} else
cdev->private->intparm = 0;
wake_up(&cdev->private->wait_q);
}
static inline int
__ccw_device_retry_loop(struct ccw_device *cdev, struct ccw1 *ccw, long magic, __u8 lpm)
{
int ret;
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
do {
ret = cio_start (sch, ccw, lpm);
if ((ret == -EBUSY) || (ret == -EACCES)) {
/* Try again later. */
spin_unlock_irq(&sch->lock);
msleep(10);
spin_lock_irq(&sch->lock);
continue;
}
if (ret != 0)
/* Non-retryable error. */
break;
/* Wait for end of request. */
cdev->private->intparm = magic;
spin_unlock_irq(&sch->lock);
wait_event(cdev->private->wait_q,
(cdev->private->intparm == -EIO) ||
(cdev->private->intparm == -EAGAIN) ||
(cdev->private->intparm == 0));
spin_lock_irq(&sch->lock);
/* Check at least for channel end / device end */
if (cdev->private->intparm == -EIO) {
/* Non-retryable error. */
ret = -EIO;
break;
}
if (cdev->private->intparm == 0)
/* Success. */
break;
/* Try again later. */
spin_unlock_irq(&sch->lock);
msleep(10);
spin_lock_irq(&sch->lock);
} while (1);
return ret;
}
/**
* read_dev_chars() - read device characteristics
* @param cdev target ccw device
* @param buffer pointer to buffer for rdc data
* @param length size of rdc data
* @returns 0 for success, negative error value on failure
*
* Context:
* called for online device, lock not held
**/
int
read_dev_chars (struct ccw_device *cdev, void **buffer, int length)
{
void (*handler)(struct ccw_device *, unsigned long, struct irb *);
struct subchannel *sch;
int ret;
struct ccw1 *rdc_ccw;
if (!cdev)
return -ENODEV;
if (!buffer || !length)
return -EINVAL;
sch = to_subchannel(cdev->dev.parent);
CIO_TRACE_EVENT (4, "rddevch");
CIO_TRACE_EVENT (4, sch->dev.bus_id);
rdc_ccw = kmalloc(sizeof(struct ccw1), GFP_KERNEL | GFP_DMA);
if (!rdc_ccw)
return -ENOMEM;
memset(rdc_ccw, 0, sizeof(struct ccw1));
rdc_ccw->cmd_code = CCW_CMD_RDC;
rdc_ccw->count = length;
rdc_ccw->flags = CCW_FLAG_SLI;
ret = set_normalized_cda (rdc_ccw, (*buffer));
if (ret != 0) {
kfree(rdc_ccw);
return ret;
}
spin_lock_irq(&sch->lock);
/* Save interrupt handler. */
handler = cdev->handler;
/* Temporarily install own handler. */
cdev->handler = ccw_device_wake_up;
if (cdev->private->state != DEV_STATE_ONLINE)
ret = -ENODEV;
else if (((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) &&
!(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) ||
cdev->private->flags.doverify)
ret = -EBUSY;
else
/* 0x00D9C4C3 == ebcdic "RDC" */
ret = __ccw_device_retry_loop(cdev, rdc_ccw, 0x00D9C4C3, 0);
/* Restore interrupt handler. */
cdev->handler = handler;
spin_unlock_irq(&sch->lock);
clear_normalized_cda (rdc_ccw);
kfree(rdc_ccw);
return ret;
}
/*
* Read Configuration data using path mask
*/
int
read_conf_data_lpm (struct ccw_device *cdev, void **buffer, int *length, __u8 lpm)
{
void (*handler)(struct ccw_device *, unsigned long, struct irb *);
struct subchannel *sch;
struct ciw *ciw;
char *rcd_buf;
int ret;
struct ccw1 *rcd_ccw;
if (!cdev)
return -ENODEV;
if (!buffer || !length)
return -EINVAL;
sch = to_subchannel(cdev->dev.parent);
CIO_TRACE_EVENT (4, "rdconf");
CIO_TRACE_EVENT (4, sch->dev.bus_id);
/*
* scan for RCD command in extended SenseID data
*/
ciw = ccw_device_get_ciw(cdev, CIW_TYPE_RCD);
if (!ciw || ciw->cmd == 0)
return -EOPNOTSUPP;
rcd_ccw = kmalloc(sizeof(struct ccw1), GFP_KERNEL | GFP_DMA);
if (!rcd_ccw)
return -ENOMEM;
memset(rcd_ccw, 0, sizeof(struct ccw1));
rcd_buf = kmalloc(ciw->count, GFP_KERNEL | GFP_DMA);
if (!rcd_buf) {
kfree(rcd_ccw);
return -ENOMEM;
}
memset (rcd_buf, 0, ciw->count);
rcd_ccw->cmd_code = ciw->cmd;
rcd_ccw->cda = (__u32) __pa (rcd_buf);
rcd_ccw->count = ciw->count;
rcd_ccw->flags = CCW_FLAG_SLI;
spin_lock_irq(&sch->lock);
/* Save interrupt handler. */
handler = cdev->handler;
/* Temporarily install own handler. */
cdev->handler = ccw_device_wake_up;
if (cdev->private->state != DEV_STATE_ONLINE)
ret = -ENODEV;
else if (((sch->schib.scsw.stctl & SCSW_STCTL_PRIM_STATUS) &&
!(sch->schib.scsw.stctl & SCSW_STCTL_SEC_STATUS)) ||
cdev->private->flags.doverify)
ret = -EBUSY;
else
/* 0x00D9C3C4 == ebcdic "RCD" */
ret = __ccw_device_retry_loop(cdev, rcd_ccw, 0x00D9C3C4, lpm);
/* Restore interrupt handler. */
cdev->handler = handler;
spin_unlock_irq(&sch->lock);
/*
* on success we update the user input parms
*/
if (ret) {
kfree (rcd_buf);
*buffer = NULL;
*length = 0;
} else {
*length = ciw->count;
*buffer = rcd_buf;
}
kfree(rcd_ccw);
return ret;
}
/*
* Read Configuration data
*/
int
read_conf_data (struct ccw_device *cdev, void **buffer, int *length)
{
return read_conf_data_lpm (cdev, buffer, length, 0);
}
/*
* Try to break the lock on a boxed device.
*/
int
ccw_device_stlck(struct ccw_device *cdev)
{
void *buf, *buf2;
unsigned long flags;
struct subchannel *sch;
int ret;
if (!cdev)
return -ENODEV;
if (cdev->drv && !cdev->private->options.force)
return -EINVAL;
sch = to_subchannel(cdev->dev.parent);
CIO_TRACE_EVENT(2, "stl lock");
CIO_TRACE_EVENT(2, cdev->dev.bus_id);
buf = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL);
if (!buf)
return -ENOMEM;
buf2 = kmalloc(32*sizeof(char), GFP_DMA|GFP_KERNEL);
if (!buf2) {
kfree(buf);
return -ENOMEM;
}
spin_lock_irqsave(&sch->lock, flags);
ret = cio_enable_subchannel(sch, 3);
if (ret)
goto out_unlock;
/*
* Setup ccw. We chain an unconditional reserve and a release so we
* only break the lock.
*/
cdev->private->iccws[0].cmd_code = CCW_CMD_STLCK;
cdev->private->iccws[0].cda = (__u32) __pa(buf);
cdev->private->iccws[0].count = 32;
cdev->private->iccws[0].flags = CCW_FLAG_CC;
cdev->private->iccws[1].cmd_code = CCW_CMD_RELEASE;
cdev->private->iccws[1].cda = (__u32) __pa(buf2);
cdev->private->iccws[1].count = 32;
cdev->private->iccws[1].flags = 0;
ret = cio_start(sch, cdev->private->iccws, 0);
if (ret) {
cio_disable_subchannel(sch); //FIXME: return code?
goto out_unlock;
}
cdev->private->irb.scsw.actl |= SCSW_ACTL_START_PEND;
spin_unlock_irqrestore(&sch->lock, flags);
wait_event(cdev->private->wait_q, cdev->private->irb.scsw.actl == 0);
spin_lock_irqsave(&sch->lock, flags);
cio_disable_subchannel(sch); //FIXME: return code?
if ((cdev->private->irb.scsw.dstat !=
(DEV_STAT_CHN_END|DEV_STAT_DEV_END)) ||
(cdev->private->irb.scsw.cstat != 0))
ret = -EIO;
/* Clear irb. */
memset(&cdev->private->irb, 0, sizeof(struct irb));
out_unlock:
if (buf)
kfree(buf);
if (buf2)
kfree(buf2);
spin_unlock_irqrestore(&sch->lock, flags);
return ret;
}
void *
ccw_device_get_chp_desc(struct ccw_device *cdev, int chp_no)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
return chsc_get_chp_desc(sch, chp_no);
}
// FIXME: these have to go:
int
_ccw_device_get_subchannel_number(struct ccw_device *cdev)
{
return cdev->private->irq;
}
int
_ccw_device_get_device_number(struct ccw_device *cdev)
{
return cdev->private->devno;
}
MODULE_LICENSE("GPL");
EXPORT_SYMBOL(ccw_device_set_options);
EXPORT_SYMBOL(ccw_device_clear);
EXPORT_SYMBOL(ccw_device_halt);
EXPORT_SYMBOL(ccw_device_resume);
EXPORT_SYMBOL(ccw_device_start_timeout);
EXPORT_SYMBOL(ccw_device_start);
EXPORT_SYMBOL(ccw_device_start_timeout_key);
EXPORT_SYMBOL(ccw_device_start_key);
EXPORT_SYMBOL(ccw_device_get_ciw);
EXPORT_SYMBOL(ccw_device_get_path_mask);
EXPORT_SYMBOL(read_conf_data);
EXPORT_SYMBOL(read_dev_chars);
EXPORT_SYMBOL(_ccw_device_get_subchannel_number);
EXPORT_SYMBOL(_ccw_device_get_device_number);
EXPORT_SYMBOL_GPL(ccw_device_get_chp_desc);
EXPORT_SYMBOL_GPL(read_conf_data_lpm);

View file

@ -0,0 +1,448 @@
/*
* drivers/s390/cio/device_pgid.c
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Cornelia Huck(cohuck@de.ibm.com)
* Martin Schwidefsky (schwidefsky@de.ibm.com)
*
* Path Group ID functions.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/ccwdev.h>
#include <asm/cio.h>
#include <asm/delay.h>
#include <asm/lowcore.h>
#include "cio.h"
#include "cio_debug.h"
#include "css.h"
#include "device.h"
/*
* Start Sense Path Group ID helper function. Used in ccw_device_recog
* and ccw_device_sense_pgid.
*/
static int
__ccw_device_sense_pgid_start(struct ccw_device *cdev)
{
struct subchannel *sch;
struct ccw1 *ccw;
int ret;
sch = to_subchannel(cdev->dev.parent);
/* Setup sense path group id channel program. */
ccw = cdev->private->iccws;
ccw->cmd_code = CCW_CMD_SENSE_PGID;
ccw->cda = (__u32) __pa (&cdev->private->pgid);
ccw->count = sizeof (struct pgid);
ccw->flags = CCW_FLAG_SLI;
/* Reset device status. */
memset(&cdev->private->irb, 0, sizeof(struct irb));
/* Try on every path. */
ret = -ENODEV;
while (cdev->private->imask != 0) {
/* Try every path multiple times. */
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)
return ret;
CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel "
"%04x, lpm %02X, became 'not "
"operational'\n",
cdev->private->devno, sch->irq,
cdev->private->imask);
}
cdev->private->imask >>= 1;
cdev->private->iretry = 5;
}
return ret;
}
void
ccw_device_sense_pgid_start(struct ccw_device *cdev)
{
int ret;
cdev->private->state = DEV_STATE_SENSE_PGID;
cdev->private->imask = 0x80;
cdev->private->iretry = 5;
memset (&cdev->private->pgid, 0, sizeof (struct pgid));
ret = __ccw_device_sense_pgid_start(cdev);
if (ret && ret != -EBUSY)
ccw_device_sense_pgid_done(cdev, ret);
}
/*
* Called from interrupt context to check if a valid answer
* to Sense Path Group ID was received.
*/
static int
__ccw_device_check_sense_pgid(struct ccw_device *cdev)
{
struct subchannel *sch;
struct irb *irb;
sch = to_subchannel(cdev->dev.parent);
irb = &cdev->private->irb;
if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
return -ETIME;
if (irb->esw.esw0.erw.cons &&
(irb->ecw[0]&(SNS0_CMD_REJECT|SNS0_INTERVENTION_REQ))) {
/*
* If the device doesn't support the Sense Path Group ID
* command further retries wouldn't help ...
*/
return -EOPNOTSUPP;
}
if (irb->esw.esw0.erw.cons) {
CIO_MSG_EVENT(2, "SNID - device %04x, unit check, "
"lpum %02X, cnt %02d, sns : "
"%02X%02X%02X%02X %02X%02X%02X%02X ...\n",
cdev->private->devno,
irb->esw.esw0.sublog.lpum,
irb->esw.esw0.erw.scnt,
irb->ecw[0], irb->ecw[1],
irb->ecw[2], irb->ecw[3],
irb->ecw[4], irb->ecw[5],
irb->ecw[6], irb->ecw[7]);
return -EAGAIN;
}
if (irb->scsw.cc == 3) {
CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel "
"%04x, lpm %02X, became 'not operational'\n",
cdev->private->devno, sch->irq, sch->orb.lpm);
return -EACCES;
}
if (cdev->private->pgid.inf.ps.state2 == SNID_STATE2_RESVD_ELSE) {
CIO_MSG_EVENT(2, "SNID - Device %04x on Subchannel %04x "
"is reserved by someone else\n",
cdev->private->devno, sch->irq);
return -EUSERS;
}
return 0;
}
/*
* Got interrupt for Sense Path Group ID.
*/
void
ccw_device_sense_pgid_irq(struct ccw_device *cdev, enum dev_event dev_event)
{
struct subchannel *sch;
struct irb *irb;
int ret;
irb = (struct irb *) __LC_IRB;
/* Retry sense pgid for cc=1. */
if (irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
if (irb->scsw.cc == 1) {
ret = __ccw_device_sense_pgid_start(cdev);
if (ret && ret != -EBUSY)
ccw_device_sense_pgid_done(cdev, ret);
}
return;
}
if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
return;
sch = to_subchannel(cdev->dev.parent);
ret = __ccw_device_check_sense_pgid(cdev);
memset(&cdev->private->irb, 0, sizeof(struct irb));
switch (ret) {
/* 0, -ETIME, -EOPNOTSUPP, -EAGAIN, -EACCES or -EUSERS */
case 0: /* Sense Path Group ID successful. */
if (cdev->private->pgid.inf.ps.state1 == SNID_STATE1_RESET)
memcpy(&cdev->private->pgid, &global_pgid,
sizeof(struct pgid));
ccw_device_sense_pgid_done(cdev, 0);
break;
case -EOPNOTSUPP: /* Sense Path Group ID not supported */
ccw_device_sense_pgid_done(cdev, -EOPNOTSUPP);
break;
case -ETIME: /* Sense path group id stopped by timeout. */
ccw_device_sense_pgid_done(cdev, -ETIME);
break;
case -EACCES: /* channel is not operational. */
sch->lpm &= ~cdev->private->imask;
cdev->private->imask >>= 1;
cdev->private->iretry = 5;
/* Fall through. */
case -EAGAIN: /* Try again. */
ret = __ccw_device_sense_pgid_start(cdev);
if (ret != 0 && ret != -EBUSY)
ccw_device_sense_pgid_done(cdev, -ENODEV);
break;
case -EUSERS: /* device is reserved for someone else. */
ccw_device_sense_pgid_done(cdev, -EUSERS);
break;
}
}
/*
* Path Group ID helper function.
*/
static int
__ccw_device_do_pgid(struct ccw_device *cdev, __u8 func)
{
struct subchannel *sch;
struct ccw1 *ccw;
int ret;
sch = to_subchannel(cdev->dev.parent);
/* Setup sense path group id channel program. */
cdev->private->pgid.inf.fc = func;
ccw = cdev->private->iccws;
if (!cdev->private->flags.pgid_single) {
cdev->private->pgid.inf.fc |= SPID_FUNC_MULTI_PATH;
ccw->cmd_code = CCW_CMD_SUSPEND_RECONN;
ccw->cda = 0;
ccw->count = 0;
ccw->flags = CCW_FLAG_SLI | CCW_FLAG_CC;
ccw++;
} else
cdev->private->pgid.inf.fc |= SPID_FUNC_SINGLE_PATH;
ccw->cmd_code = CCW_CMD_SET_PGID;
ccw->cda = (__u32) __pa (&cdev->private->pgid);
ccw->count = sizeof (struct pgid);
ccw->flags = CCW_FLAG_SLI;
/* Reset device status. */
memset(&cdev->private->irb, 0, sizeof(struct irb));
/* Try multiple times. */
ret = -ENODEV;
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))
return ret;
}
/* PGID command failed on this path. Switch it off. */
sch->lpm &= ~cdev->private->imask;
sch->vpm &= ~cdev->private->imask;
CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel "
"%04x, lpm %02X, became 'not operational'\n",
cdev->private->devno, sch->irq, cdev->private->imask);
return ret;
}
/*
* Called from interrupt context to check if a valid answer
* to Set Path Group ID was received.
*/
static int
__ccw_device_check_pgid(struct ccw_device *cdev)
{
struct subchannel *sch;
struct irb *irb;
sch = to_subchannel(cdev->dev.parent);
irb = &cdev->private->irb;
if (irb->scsw.fctl & (SCSW_FCTL_HALT_FUNC | SCSW_FCTL_CLEAR_FUNC))
return -ETIME;
if (irb->esw.esw0.erw.cons) {
if (irb->ecw[0] & SNS0_CMD_REJECT)
return -EOPNOTSUPP;
/* Hmm, whatever happened, try again. */
CIO_MSG_EVENT(2, "SPID - device %04x, unit check, cnt %02d, "
"sns : %02X%02X%02X%02X %02X%02X%02X%02X ...\n",
cdev->private->devno, irb->esw.esw0.erw.scnt,
irb->ecw[0], irb->ecw[1],
irb->ecw[2], irb->ecw[3],
irb->ecw[4], irb->ecw[5],
irb->ecw[6], irb->ecw[7]);
return -EAGAIN;
}
if (irb->scsw.cc == 3) {
CIO_MSG_EVENT(2, "SPID - Device %04x on Subchannel "
"%04x, lpm %02X, became 'not operational'\n",
cdev->private->devno, sch->irq,
cdev->private->imask);
return -EACCES;
}
return 0;
}
static void
__ccw_device_verify_start(struct ccw_device *cdev)
{
struct subchannel *sch;
__u8 imask, 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;
func = (sch->vpm & imask) ?
SPID_FUNC_RESIGN : SPID_FUNC_ESTABLISH;
ret = __ccw_device_do_pgid(cdev, func);
if (ret == 0 || ret == -EBUSY)
return;
cdev->private->iretry = 5;
}
ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
}
/*
* Got interrupt for Set Path Group ID.
*/
void
ccw_device_verify_irq(struct ccw_device *cdev, enum dev_event dev_event)
{
struct subchannel *sch;
struct irb *irb;
int ret;
irb = (struct irb *) __LC_IRB;
/* Retry set pgid for cc=1. */
if (irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
if (irb->scsw.cc == 1)
__ccw_device_verify_start(cdev);
return;
}
if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
return;
sch = to_subchannel(cdev->dev.parent);
ret = __ccw_device_check_pgid(cdev);
memset(&cdev->private->irb, 0, sizeof(struct irb));
switch (ret) {
/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
case 0:
/* 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;
cdev->private->iretry = 5;
__ccw_device_verify_start(cdev);
break;
case -EOPNOTSUPP:
/*
* One of those strange devices which claim to be able
* to do multipathing but not for Set Path Group ID.
*/
if (cdev->private->flags.pgid_single) {
ccw_device_verify_done(cdev, -EOPNOTSUPP);
break;
}
cdev->private->flags.pgid_single = 1;
/* fall through. */
case -EAGAIN: /* Try again. */
__ccw_device_verify_start(cdev);
break;
case -ETIME: /* Set path group id stopped by timeout. */
ccw_device_verify_done(cdev, -ETIME);
break;
case -EACCES: /* channel is not operational. */
sch->lpm &= ~cdev->private->imask;
sch->vpm &= ~cdev->private->imask;
cdev->private->iretry = 5;
__ccw_device_verify_start(cdev);
break;
}
}
void
ccw_device_verify_start(struct ccw_device *cdev)
{
cdev->private->flags.pgid_single = 0;
cdev->private->iretry = 5;
__ccw_device_verify_start(cdev);
}
static void
__ccw_device_disband_start(struct ccw_device *cdev)
{
struct subchannel *sch;
int ret;
sch = to_subchannel(cdev->dev.parent);
while (cdev->private->imask != 0) {
if (sch->lpm & cdev->private->imask) {
ret = __ccw_device_do_pgid(cdev, SPID_FUNC_DISBAND);
if (ret == 0)
return;
}
cdev->private->iretry = 5;
cdev->private->imask >>= 1;
}
ccw_device_verify_done(cdev, (sch->lpm != 0) ? 0 : -ENODEV);
}
/*
* Got interrupt for Unset Path Group ID.
*/
void
ccw_device_disband_irq(struct ccw_device *cdev, enum dev_event dev_event)
{
struct subchannel *sch;
struct irb *irb;
int ret;
irb = (struct irb *) __LC_IRB;
/* Retry set pgid for cc=1. */
if (irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) {
if (irb->scsw.cc == 1)
__ccw_device_disband_start(cdev);
return;
}
if (ccw_device_accumulate_and_sense(cdev, irb) != 0)
return;
sch = to_subchannel(cdev->dev.parent);
ret = __ccw_device_check_pgid(cdev);
memset(&cdev->private->irb, 0, sizeof(struct irb));
switch (ret) {
/* 0, -ETIME, -EAGAIN, -EOPNOTSUPP or -EACCES */
case 0: /* disband successful. */
sch->vpm = 0;
ccw_device_disband_done(cdev, ret);
break;
case -EOPNOTSUPP:
/*
* One of those strange devices which claim to be able
* to do multipathing but not for Unset Path Group ID.
*/
cdev->private->flags.pgid_single = 1;
/* fall through. */
case -EAGAIN: /* Try again. */
__ccw_device_disband_start(cdev);
break;
case -ETIME: /* Set path group id stopped by timeout. */
ccw_device_disband_done(cdev, -ETIME);
break;
case -EACCES: /* channel is not operational. */
cdev->private->imask >>= 1;
cdev->private->iretry = 5;
__ccw_device_disband_start(cdev);
break;
}
}
void
ccw_device_disband_start(struct ccw_device *cdev)
{
cdev->private->flags.pgid_single = 0;
cdev->private->iretry = 5;
cdev->private->imask = 0x80;
__ccw_device_disband_start(cdev);
}

View file

@ -0,0 +1,385 @@
/*
* drivers/s390/cio/device_status.c
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Author(s): Cornelia Huck(cohuck@de.ibm.com)
* Martin Schwidefsky (schwidefsky@de.ibm.com)
*
* Status accumulation and basic sense functions.
*/
#include <linux/config.h>
#include <linux/module.h>
#include <linux/init.h>
#include <asm/ccwdev.h>
#include <asm/cio.h>
#include "cio.h"
#include "cio_debug.h"
#include "css.h"
#include "device.h"
#include "ioasm.h"
/*
* Check for any kind of channel or interface control check but don't
* issue the message for the console device
*/
static inline void
ccw_device_msg_control_check(struct ccw_device *cdev, struct irb *irb)
{
if (!(irb->scsw.cstat & (SCHN_STAT_CHN_DATA_CHK |
SCHN_STAT_CHN_CTRL_CHK |
SCHN_STAT_INTF_CTRL_CHK)))
return;
CIO_MSG_EVENT(0, "Channel-Check or Interface-Control-Check "
"received"
" ... device %04X on subchannel %04X, dev_stat "
": %02X sch_stat : %02X\n",
cdev->private->devno, cdev->private->irq,
cdev->private->irb.scsw.dstat,
cdev->private->irb.scsw.cstat);
if (irb->scsw.cc != 3) {
char dbf_text[15];
sprintf(dbf_text, "chk%x", cdev->private->irq);
CIO_TRACE_EVENT(0, dbf_text);
CIO_HEX_EVENT(0, &cdev->private->irb, sizeof (struct irb));
}
}
/*
* Some paths became not operational (pno bit in scsw is set).
*/
static void
ccw_device_path_notoper(struct ccw_device *cdev)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
stsch (sch->irq, &sch->schib);
CIO_MSG_EVENT(0, "%s(%04x) - path(s) %02x are "
"not operational \n", __FUNCTION__, sch->irq,
sch->schib.pmcw.pnom);
sch->lpm &= ~sch->schib.pmcw.pnom;
if (cdev->private->options.pgroup)
cdev->private->flags.doverify = 1;
}
/*
* Copy valid bits from the extended control word to device irb.
*/
static inline void
ccw_device_accumulate_ecw(struct ccw_device *cdev, struct irb *irb)
{
/*
* Copy extended control bit if it is valid... yes there
* are condition that have to be met for the extended control
* bit to have meaning. Sick.
*/
cdev->private->irb.scsw.ectl = 0;
if ((irb->scsw.stctl & SCSW_STCTL_ALERT_STATUS) &&
!(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS))
cdev->private->irb.scsw.ectl = irb->scsw.ectl;
/* Check if extended control word is valid. */
if (!cdev->private->irb.scsw.ectl)
return;
/* Copy concurrent sense / model dependent information. */
memcpy (&cdev->private->irb.ecw, irb->ecw, sizeof (irb->ecw));
}
/*
* Check if extended status word is valid.
*/
static inline int
ccw_device_accumulate_esw_valid(struct irb *irb)
{
if (!irb->scsw.eswf && irb->scsw.stctl == SCSW_STCTL_STATUS_PEND)
return 0;
if (irb->scsw.stctl ==
(SCSW_STCTL_INTER_STATUS|SCSW_STCTL_STATUS_PEND) &&
!(irb->scsw.actl & SCSW_ACTL_SUSPENDED))
return 0;
return 1;
}
/*
* Copy valid bits from the extended status word to device irb.
*/
static inline void
ccw_device_accumulate_esw(struct ccw_device *cdev, struct irb *irb)
{
struct irb *cdev_irb;
struct sublog *cdev_sublog, *sublog;
if (!ccw_device_accumulate_esw_valid(irb))
return;
cdev_irb = &cdev->private->irb;
/* Copy last path used mask. */
cdev_irb->esw.esw1.lpum = irb->esw.esw1.lpum;
/* Copy subchannel logout information if esw is of format 0. */
if (irb->scsw.eswf) {
cdev_sublog = &cdev_irb->esw.esw0.sublog;
sublog = &irb->esw.esw0.sublog;
/* Copy extended status flags. */
cdev_sublog->esf = sublog->esf;
/*
* Copy fields that have a meaning for channel data check
* channel control check and interface control check.
*/
if (irb->scsw.cstat & (SCHN_STAT_CHN_DATA_CHK |
SCHN_STAT_CHN_CTRL_CHK |
SCHN_STAT_INTF_CTRL_CHK)) {
/* Copy ancillary report bit. */
cdev_sublog->arep = sublog->arep;
/* Copy field-validity-flags. */
cdev_sublog->fvf = sublog->fvf;
/* Copy storage access code. */
cdev_sublog->sacc = sublog->sacc;
/* Copy termination code. */
cdev_sublog->termc = sublog->termc;
/* Copy sequence code. */
cdev_sublog->seqc = sublog->seqc;
}
/* Copy device status check. */
cdev_sublog->devsc = sublog->devsc;
/* Copy secondary error. */
cdev_sublog->serr = sublog->serr;
/* Copy i/o-error alert. */
cdev_sublog->ioerr = sublog->ioerr;
/* Copy channel path timeout bit. */
if (irb->scsw.cstat & SCHN_STAT_INTF_CTRL_CHK)
cdev_irb->esw.esw0.erw.cpt = irb->esw.esw0.erw.cpt;
/* Copy failing storage address validity flag. */
cdev_irb->esw.esw0.erw.fsavf = irb->esw.esw0.erw.fsavf;
if (cdev_irb->esw.esw0.erw.fsavf) {
/* ... and copy the failing storage address. */
memcpy(cdev_irb->esw.esw0.faddr, irb->esw.esw0.faddr,
sizeof (irb->esw.esw0.faddr));
/* ... and copy the failing storage address format. */
cdev_irb->esw.esw0.erw.fsaf = irb->esw.esw0.erw.fsaf;
}
/* Copy secondary ccw address validity bit. */
cdev_irb->esw.esw0.erw.scavf = irb->esw.esw0.erw.scavf;
if (irb->esw.esw0.erw.scavf)
/* ... and copy the secondary ccw address. */
cdev_irb->esw.esw0.saddr = irb->esw.esw0.saddr;
}
/* FIXME: DCTI for format 2? */
/* Copy authorization bit. */
cdev_irb->esw.esw0.erw.auth = irb->esw.esw0.erw.auth;
/* Copy path verification required flag. */
cdev_irb->esw.esw0.erw.pvrf = irb->esw.esw0.erw.pvrf;
if (irb->esw.esw0.erw.pvrf && cdev->private->options.pgroup)
cdev->private->flags.doverify = 1;
/* Copy concurrent sense bit. */
cdev_irb->esw.esw0.erw.cons = irb->esw.esw0.erw.cons;
if (irb->esw.esw0.erw.cons)
cdev_irb->esw.esw0.erw.scnt = irb->esw.esw0.erw.scnt;
}
/*
* Accumulate status from irb to devstat.
*/
void
ccw_device_accumulate_irb(struct ccw_device *cdev, struct irb *irb)
{
struct irb *cdev_irb;
/*
* Check if the status pending bit is set in stctl.
* If not, the remaining bit have no meaning and we must ignore them.
* The esw is not meaningful as well...
*/
if (!(irb->scsw.stctl & SCSW_STCTL_STATUS_PEND))
return;
/* Check for channel checks and interface control checks. */
ccw_device_msg_control_check(cdev, irb);
/* Check for path not operational. */
if (irb->scsw.pno && irb->scsw.fctl != 0 &&
(!(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS) ||
(irb->scsw.actl & SCSW_ACTL_SUSPENDED)))
ccw_device_path_notoper(cdev);
/*
* Don't accumulate unsolicited interrupts.
*/
if ((irb->scsw.stctl ==
(SCSW_STCTL_STATUS_PEND | SCSW_STCTL_ALERT_STATUS)) &&
(!irb->scsw.cc))
return;
cdev_irb = &cdev->private->irb;
/* Copy bits which are valid only for the start function. */
if (irb->scsw.fctl & SCSW_FCTL_START_FUNC) {
/* Copy key. */
cdev_irb->scsw.key = irb->scsw.key;
/* Copy suspend control bit. */
cdev_irb->scsw.sctl = irb->scsw.sctl;
/* Accumulate deferred condition code. */
cdev_irb->scsw.cc |= irb->scsw.cc;
/* Copy ccw format bit. */
cdev_irb->scsw.fmt = irb->scsw.fmt;
/* Copy prefetch bit. */
cdev_irb->scsw.pfch = irb->scsw.pfch;
/* Copy initial-status-interruption-control. */
cdev_irb->scsw.isic = irb->scsw.isic;
/* Copy address limit checking control. */
cdev_irb->scsw.alcc = irb->scsw.alcc;
/* Copy suppress suspend bit. */
cdev_irb->scsw.ssi = irb->scsw.ssi;
}
/* Take care of the extended control bit and extended control word. */
ccw_device_accumulate_ecw(cdev, irb);
/* Accumulate function control. */
cdev_irb->scsw.fctl |= irb->scsw.fctl;
/* Copy activity control. */
cdev_irb->scsw.actl= irb->scsw.actl;
/* Accumulate status control. */
cdev_irb->scsw.stctl |= irb->scsw.stctl;
/*
* Copy ccw address if it is valid. This is a bit simplified
* but should be close enough for all practical purposes.
*/
if ((irb->scsw.stctl & SCSW_STCTL_PRIM_STATUS) ||
((irb->scsw.stctl ==
(SCSW_STCTL_INTER_STATUS|SCSW_STCTL_STATUS_PEND)) &&
(irb->scsw.actl & SCSW_ACTL_DEVACT) &&
(irb->scsw.actl & SCSW_ACTL_SCHACT)) ||
(irb->scsw.actl & SCSW_ACTL_SUSPENDED))
cdev_irb->scsw.cpa = irb->scsw.cpa;
/* Accumulate device status, but not the device busy flag. */
cdev_irb->scsw.dstat &= ~DEV_STAT_BUSY;
cdev_irb->scsw.dstat |= irb->scsw.dstat;
/* Accumulate subchannel status. */
cdev_irb->scsw.cstat |= irb->scsw.cstat;
/* Copy residual count if it is valid. */
if ((irb->scsw.stctl & SCSW_STCTL_PRIM_STATUS) &&
(irb->scsw.cstat & ~(SCHN_STAT_PCI | SCHN_STAT_INCORR_LEN)) == 0)
cdev_irb->scsw.count = irb->scsw.count;
/* Take care of bits in the extended status word. */
ccw_device_accumulate_esw(cdev, irb);
/*
* Check whether we must issue a SENSE CCW ourselves if there is no
* concurrent sense facility installed for the subchannel.
* No sense is required if no delayed sense is pending
* and we did not get a unit check without sense information.
*
* Note: We should check for ioinfo[irq]->flags.consns but VM
* violates the ESA/390 architecture and doesn't present an
* operand exception for virtual devices without concurrent
* sense facility available/supported when enabling the
* concurrent sense facility.
*/
if ((cdev_irb->scsw.dstat & DEV_STAT_UNIT_CHECK) &&
!(cdev_irb->esw.esw0.erw.cons))
cdev->private->flags.dosense = 1;
}
/*
* Do a basic sense.
*/
int
ccw_device_do_sense(struct ccw_device *cdev, struct irb *irb)
{
struct subchannel *sch;
sch = to_subchannel(cdev->dev.parent);
/* A sense is required, can we do it now ? */
if ((irb->scsw.actl & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)) != 0)
/*
* we received an Unit Check but we have no final
* status yet, therefore we must delay the SENSE
* processing. We must not report this intermediate
* status to the device interrupt handler.
*/
return -EBUSY;
/*
* We have ending status but no sense information. Do a basic sense.
*/
sch = to_subchannel(cdev->dev.parent);
sch->sense_ccw.cmd_code = CCW_CMD_BASIC_SENSE;
sch->sense_ccw.cda = (__u32) __pa(cdev->private->irb.ecw);
sch->sense_ccw.count = SENSE_MAX_COUNT;
sch->sense_ccw.flags = CCW_FLAG_SLI;
return cio_start (sch, &sch->sense_ccw, 0xff);
}
/*
* Add information from basic sense to devstat.
*/
void
ccw_device_accumulate_basic_sense(struct ccw_device *cdev, struct irb *irb)
{
/*
* Check if the status pending bit is set in stctl.
* If not, the remaining bit have no meaning and we must ignore them.
* The esw is not meaningful as well...
*/
if (!(irb->scsw.stctl & SCSW_STCTL_STATUS_PEND))
return;
/* Check for channel checks and interface control checks. */
ccw_device_msg_control_check(cdev, irb);
/* Check for path not operational. */
if (irb->scsw.pno && irb->scsw.fctl != 0 &&
(!(irb->scsw.stctl & SCSW_STCTL_INTER_STATUS) ||
(irb->scsw.actl & SCSW_ACTL_SUSPENDED)))
ccw_device_path_notoper(cdev);
if (!(irb->scsw.dstat & DEV_STAT_UNIT_CHECK) &&
(irb->scsw.dstat & DEV_STAT_CHN_END)) {
cdev->private->irb.esw.esw0.erw.cons = 1;
cdev->private->flags.dosense = 0;
}
/* Check if path verification is required. */
if (ccw_device_accumulate_esw_valid(irb) &&
irb->esw.esw0.erw.pvrf && cdev->private->options.pgroup)
cdev->private->flags.doverify = 1;
}
/*
* This function accumulates the status into the private devstat and
* starts a basic sense if one is needed.
*/
int
ccw_device_accumulate_and_sense(struct ccw_device *cdev, struct irb *irb)
{
ccw_device_accumulate_irb(cdev, irb);
if ((irb->scsw.actl & (SCSW_ACTL_DEVACT | SCSW_ACTL_SCHACT)) != 0)
return -EBUSY;
/* Check for basic sense. */
if (cdev->private->flags.dosense &&
!(irb->scsw.dstat & DEV_STAT_UNIT_CHECK)) {
cdev->private->irb.esw.esw0.erw.cons = 1;
cdev->private->flags.dosense = 0;
return 0;
}
if (cdev->private->flags.dosense) {
ccw_device_do_sense(cdev, irb);
return -EBUSY;
}
return 0;
}

228
drivers/s390/cio/ioasm.h Normal file
View file

@ -0,0 +1,228 @@
#ifndef S390_CIO_IOASM_H
#define S390_CIO_IOASM_H
/*
* TPI info structure
*/
struct tpi_info {
__u32 reserved1 : 16; /* reserved 0x00000001 */
__u32 irq : 16; /* aka. subchannel number */
__u32 intparm; /* interruption parameter */
__u32 adapter_IO : 1;
__u32 reserved2 : 1;
__u32 isc : 3;
__u32 reserved3 : 12;
__u32 int_type : 3;
__u32 reserved4 : 12;
} __attribute__ ((packed));
/*
* Some S390 specific IO instructions as inline
*/
extern __inline__ int stsch(int irq, volatile struct schib *addr)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" stsch 0(%2)\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (irq | 0x10000), "a" (addr)
: "cc", "1" );
return ccode;
}
extern __inline__ int msch(int irq, volatile struct schib *addr)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" msch 0(%2)\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (irq | 0x10000L), "a" (addr)
: "cc", "1" );
return ccode;
}
extern __inline__ int msch_err(int irq, volatile struct schib *addr)
{
int ccode;
__asm__ __volatile__(
" lhi %0,%3\n"
" lr 1,%1\n"
" msch 0(%2)\n"
"0: ipm %0\n"
" srl %0,28\n"
"1:\n"
#ifdef CONFIG_ARCH_S390X
".section __ex_table,\"a\"\n"
" .align 8\n"
" .quad 0b,1b\n"
".previous"
#else
".section __ex_table,\"a\"\n"
" .align 4\n"
" .long 0b,1b\n"
".previous"
#endif
: "=&d" (ccode)
: "d" (irq | 0x10000L), "a" (addr), "K" (-EIO)
: "cc", "1" );
return ccode;
}
extern __inline__ int tsch(int irq, volatile struct irb *addr)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" tsch 0(%2)\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (irq | 0x10000L), "a" (addr)
: "cc", "1" );
return ccode;
}
extern __inline__ int tpi( volatile struct tpi_info *addr)
{
int ccode;
__asm__ __volatile__(
" tpi 0(%1)\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "a" (addr)
: "cc", "1" );
return ccode;
}
extern __inline__ int ssch(int irq, volatile struct orb *addr)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" ssch 0(%2)\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (irq | 0x10000L), "a" (addr)
: "cc", "1" );
return ccode;
}
extern __inline__ int rsch(int irq)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" rsch\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (irq | 0x10000L)
: "cc", "1" );
return ccode;
}
extern __inline__ int csch(int irq)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" csch\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (irq | 0x10000L)
: "cc", "1" );
return ccode;
}
extern __inline__ int hsch(int irq)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" hsch\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (irq | 0x10000L)
: "cc", "1" );
return ccode;
}
extern __inline__ int xsch(int irq)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" .insn rre,0xb2760000,%1,0\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (irq | 0x10000L)
: "cc", "1" );
return ccode;
}
extern __inline__ int chsc(void *chsc_area)
{
int cc;
__asm__ __volatile__ (
".insn rre,0xb25f0000,%1,0 \n\t"
"ipm %0 \n\t"
"srl %0,28 \n\t"
: "=d" (cc)
: "d" (chsc_area)
: "cc" );
return cc;
}
extern __inline__ int iac( void)
{
int ccode;
__asm__ __volatile__(
" iac 1\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode) : : "cc", "1" );
return ccode;
}
extern __inline__ int rchp(int chpid)
{
int ccode;
__asm__ __volatile__(
" lr 1,%1\n"
" rchp\n"
" ipm %0\n"
" srl %0,28"
: "=d" (ccode)
: "d" (chpid)
: "cc", "1" );
return ccode;
}
#endif

3468
drivers/s390/cio/qdio.c Normal file

File diff suppressed because it is too large Load diff

648
drivers/s390/cio/qdio.h Normal file
View file

@ -0,0 +1,648 @@
#ifndef _CIO_QDIO_H
#define _CIO_QDIO_H
#define VERSION_CIO_QDIO_H "$Revision: 1.26 $"
#ifdef CONFIG_QDIO_DEBUG
#define QDIO_VERBOSE_LEVEL 9
#else /* CONFIG_QDIO_DEBUG */
#define QDIO_VERBOSE_LEVEL 5
#endif /* CONFIG_QDIO_DEBUG */
#define QDIO_USE_PROCESSING_STATE
#ifdef CONFIG_QDIO_PERF_STATS
#define QDIO_PERFORMANCE_STATS
#endif /* CONFIG_QDIO_PERF_STATS */
#define QDIO_MINIMAL_BH_RELIEF_TIME 16
#define QDIO_TIMER_POLL_VALUE 1
#define IQDIO_TIMER_POLL_VALUE 1
/*
* unfortunately this can't be (QDIO_MAX_BUFFERS_PER_Q*4/3) or so -- as
* we never know, whether we'll get initiative again, e.g. to give the
* transmit skb's back to the stack, however the stack may be waiting for
* them... therefore we define 4 as threshold to start polling (which
* will stop as soon as the asynchronous queue catches up)
* btw, this only applies to the asynchronous HiperSockets queue
*/
#define IQDIO_FILL_LEVEL_TO_POLL 4
#define TIQDIO_THININT_ISC 3
#define TIQDIO_DELAY_TARGET 0
#define QDIO_BUSY_BIT_PATIENCE 100 /* in microsecs */
#define QDIO_BUSY_BIT_GIVE_UP 10000000 /* 10 seconds */
#define IQDIO_GLOBAL_LAPS 2 /* GLOBAL_LAPS are not used as we */
#define IQDIO_GLOBAL_LAPS_INT 1 /* don't global summary */
#define IQDIO_LOCAL_LAPS 4
#define IQDIO_LOCAL_LAPS_INT 1
#define IQDIO_GLOBAL_SUMMARY_CC_MASK 2
/*#define IQDIO_IQDC_INT_PARM 0x1234*/
#define QDIO_Q_LAPS 5
#define QDIO_STORAGE_KEY 0
#define L2_CACHELINE_SIZE 256
#define INDICATORS_PER_CACHELINE (L2_CACHELINE_SIZE/sizeof(__u32))
#define QDIO_PERF "qdio_perf"
/* must be a power of 2 */
/*#define QDIO_STATS_NUMBER 4
#define QDIO_STATS_CLASSES 2
#define QDIO_STATS_COUNT_NEEDED 2*/
#define QDIO_NO_USE_COUNT_TIMEOUT (1*HZ) /* wait for 1 sec on each q before
exiting without having use_count
of the queue to 0 */
#define QDIO_ESTABLISH_TIMEOUT (1*HZ)
#define QDIO_ACTIVATE_TIMEOUT ((5*HZ)>>10)
#define QDIO_CLEANUP_CLEAR_TIMEOUT (20*HZ)
#define QDIO_CLEANUP_HALT_TIMEOUT (10*HZ)
enum qdio_irq_states {
QDIO_IRQ_STATE_INACTIVE,
QDIO_IRQ_STATE_ESTABLISHED,
QDIO_IRQ_STATE_ACTIVE,
QDIO_IRQ_STATE_STOPPED,
QDIO_IRQ_STATE_CLEANUP,
QDIO_IRQ_STATE_ERR,
NR_QDIO_IRQ_STATES,
};
/* used as intparm in do_IO: */
#define QDIO_DOING_SENSEID 0
#define QDIO_DOING_ESTABLISH 1
#define QDIO_DOING_ACTIVATE 2
#define QDIO_DOING_CLEANUP 3
/************************* DEBUG FACILITY STUFF *********************/
#define QDIO_DBF_HEX(ex,name,level,addr,len) \
do { \
if (ex) \
debug_exception(qdio_dbf_##name,level,(void*)(addr),len); \
else \
debug_event(qdio_dbf_##name,level,(void*)(addr),len); \
} while (0)
#define QDIO_DBF_TEXT(ex,name,level,text) \
do { \
if (ex) \
debug_text_exception(qdio_dbf_##name,level,text); \
else \
debug_text_event(qdio_dbf_##name,level,text); \
} while (0)
#define QDIO_DBF_HEX0(ex,name,addr,len) QDIO_DBF_HEX(ex,name,0,addr,len)
#define QDIO_DBF_HEX1(ex,name,addr,len) QDIO_DBF_HEX(ex,name,1,addr,len)
#define QDIO_DBF_HEX2(ex,name,addr,len) QDIO_DBF_HEX(ex,name,2,addr,len)
#ifdef CONFIG_QDIO_DEBUG
#define QDIO_DBF_HEX3(ex,name,addr,len) QDIO_DBF_HEX(ex,name,3,addr,len)
#define QDIO_DBF_HEX4(ex,name,addr,len) QDIO_DBF_HEX(ex,name,4,addr,len)
#define QDIO_DBF_HEX5(ex,name,addr,len) QDIO_DBF_HEX(ex,name,5,addr,len)
#define QDIO_DBF_HEX6(ex,name,addr,len) QDIO_DBF_HEX(ex,name,6,addr,len)
#else /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_HEX3(ex,name,addr,len) do {} while (0)
#define QDIO_DBF_HEX4(ex,name,addr,len) do {} while (0)
#define QDIO_DBF_HEX5(ex,name,addr,len) do {} while (0)
#define QDIO_DBF_HEX6(ex,name,addr,len) do {} while (0)
#endif /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_TEXT0(ex,name,text) QDIO_DBF_TEXT(ex,name,0,text)
#define QDIO_DBF_TEXT1(ex,name,text) QDIO_DBF_TEXT(ex,name,1,text)
#define QDIO_DBF_TEXT2(ex,name,text) QDIO_DBF_TEXT(ex,name,2,text)
#ifdef CONFIG_QDIO_DEBUG
#define QDIO_DBF_TEXT3(ex,name,text) QDIO_DBF_TEXT(ex,name,3,text)
#define QDIO_DBF_TEXT4(ex,name,text) QDIO_DBF_TEXT(ex,name,4,text)
#define QDIO_DBF_TEXT5(ex,name,text) QDIO_DBF_TEXT(ex,name,5,text)
#define QDIO_DBF_TEXT6(ex,name,text) QDIO_DBF_TEXT(ex,name,6,text)
#else /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_TEXT3(ex,name,text) do {} while (0)
#define QDIO_DBF_TEXT4(ex,name,text) do {} while (0)
#define QDIO_DBF_TEXT5(ex,name,text) do {} while (0)
#define QDIO_DBF_TEXT6(ex,name,text) do {} while (0)
#endif /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_SETUP_NAME "qdio_setup"
#define QDIO_DBF_SETUP_LEN 8
#define QDIO_DBF_SETUP_INDEX 2
#define QDIO_DBF_SETUP_NR_AREAS 1
#ifdef CONFIG_QDIO_DEBUG
#define QDIO_DBF_SETUP_LEVEL 6
#else /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_SETUP_LEVEL 2
#endif /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_SBAL_NAME "qdio_labs" /* sbal */
#define QDIO_DBF_SBAL_LEN 256
#define QDIO_DBF_SBAL_INDEX 2
#define QDIO_DBF_SBAL_NR_AREAS 2
#ifdef CONFIG_QDIO_DEBUG
#define QDIO_DBF_SBAL_LEVEL 6
#else /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_SBAL_LEVEL 2
#endif /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_TRACE_NAME "qdio_trace"
#define QDIO_DBF_TRACE_LEN 8
#define QDIO_DBF_TRACE_NR_AREAS 2
#ifdef CONFIG_QDIO_DEBUG
#define QDIO_DBF_TRACE_INDEX 4
#define QDIO_DBF_TRACE_LEVEL 4 /* -------- could be even more verbose here */
#else /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_TRACE_INDEX 2
#define QDIO_DBF_TRACE_LEVEL 2
#endif /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_SENSE_NAME "qdio_sense"
#define QDIO_DBF_SENSE_LEN 64
#define QDIO_DBF_SENSE_INDEX 1
#define QDIO_DBF_SENSE_NR_AREAS 1
#ifdef CONFIG_QDIO_DEBUG
#define QDIO_DBF_SENSE_LEVEL 6
#else /* CONFIG_QDIO_DEBUG */
#define QDIO_DBF_SENSE_LEVEL 2
#endif /* CONFIG_QDIO_DEBUG */
#ifdef CONFIG_QDIO_DEBUG
#define QDIO_TRACE_QTYPE QDIO_ZFCP_QFMT
#define QDIO_DBF_SLSB_OUT_NAME "qdio_slsb_out"
#define QDIO_DBF_SLSB_OUT_LEN QDIO_MAX_BUFFERS_PER_Q
#define QDIO_DBF_SLSB_OUT_INDEX 8
#define QDIO_DBF_SLSB_OUT_NR_AREAS 1
#define QDIO_DBF_SLSB_OUT_LEVEL 6
#define QDIO_DBF_SLSB_IN_NAME "qdio_slsb_in"
#define QDIO_DBF_SLSB_IN_LEN QDIO_MAX_BUFFERS_PER_Q
#define QDIO_DBF_SLSB_IN_INDEX 8
#define QDIO_DBF_SLSB_IN_NR_AREAS 1
#define QDIO_DBF_SLSB_IN_LEVEL 6
#endif /* CONFIG_QDIO_DEBUG */
#define QDIO_PRINTK_HEADER QDIO_NAME ": "
#if QDIO_VERBOSE_LEVEL>8
#define QDIO_PRINT_STUPID(x...) printk( KERN_DEBUG QDIO_PRINTK_HEADER x)
#else
#define QDIO_PRINT_STUPID(x...)
#endif
#if QDIO_VERBOSE_LEVEL>7
#define QDIO_PRINT_ALL(x...) printk( QDIO_PRINTK_HEADER x)
#else
#define QDIO_PRINT_ALL(x...)
#endif
#if QDIO_VERBOSE_LEVEL>6
#define QDIO_PRINT_INFO(x...) printk( QDIO_PRINTK_HEADER x)
#else
#define QDIO_PRINT_INFO(x...)
#endif
#if QDIO_VERBOSE_LEVEL>5
#define QDIO_PRINT_WARN(x...) printk( QDIO_PRINTK_HEADER x)
#else
#define QDIO_PRINT_WARN(x...)
#endif
#if QDIO_VERBOSE_LEVEL>4
#define QDIO_PRINT_ERR(x...) printk( QDIO_PRINTK_HEADER x)
#else
#define QDIO_PRINT_ERR(x...)
#endif
#if QDIO_VERBOSE_LEVEL>3
#define QDIO_PRINT_CRIT(x...) printk( QDIO_PRINTK_HEADER x)
#else
#define QDIO_PRINT_CRIT(x...)
#endif
#if QDIO_VERBOSE_LEVEL>2
#define QDIO_PRINT_ALERT(x...) printk( QDIO_PRINTK_HEADER x)
#else
#define QDIO_PRINT_ALERT(x...)
#endif
#if QDIO_VERBOSE_LEVEL>1
#define QDIO_PRINT_EMERG(x...) printk( QDIO_PRINTK_HEADER x)
#else
#define QDIO_PRINT_EMERG(x...)
#endif
#define HEXDUMP16(importance,header,ptr) \
QDIO_PRINT_##importance(header "%02x %02x %02x %02x " \
"%02x %02x %02x %02x %02x %02x %02x %02x " \
"%02x %02x %02x %02x\n",*(((char*)ptr)), \
*(((char*)ptr)+1),*(((char*)ptr)+2), \
*(((char*)ptr)+3),*(((char*)ptr)+4), \
*(((char*)ptr)+5),*(((char*)ptr)+6), \
*(((char*)ptr)+7),*(((char*)ptr)+8), \
*(((char*)ptr)+9),*(((char*)ptr)+10), \
*(((char*)ptr)+11),*(((char*)ptr)+12), \
*(((char*)ptr)+13),*(((char*)ptr)+14), \
*(((char*)ptr)+15)); \
QDIO_PRINT_##importance(header "%02x %02x %02x %02x %02x %02x %02x %02x " \
"%02x %02x %02x %02x %02x %02x %02x %02x\n", \
*(((char*)ptr)+16),*(((char*)ptr)+17), \
*(((char*)ptr)+18),*(((char*)ptr)+19), \
*(((char*)ptr)+20),*(((char*)ptr)+21), \
*(((char*)ptr)+22),*(((char*)ptr)+23), \
*(((char*)ptr)+24),*(((char*)ptr)+25), \
*(((char*)ptr)+26),*(((char*)ptr)+27), \
*(((char*)ptr)+28),*(((char*)ptr)+29), \
*(((char*)ptr)+30),*(((char*)ptr)+31));
/****************** END OF DEBUG FACILITY STUFF *********************/
/*
* Some instructions as assembly
*/
extern __inline__ int
do_siga_sync(unsigned int irq, unsigned int mask1, unsigned int mask2)
{
int cc;
#ifndef CONFIG_ARCH_S390X
asm volatile (
"lhi 0,2 \n\t"
"lr 1,%1 \n\t"
"lr 2,%2 \n\t"
"lr 3,%3 \n\t"
"siga 0 \n\t"
"ipm %0 \n\t"
"srl %0,28 \n\t"
: "=d" (cc)
: "d" (0x10000|irq), "d" (mask1), "d" (mask2)
: "cc", "0", "1", "2", "3"
);
#else /* CONFIG_ARCH_S390X */
asm volatile (
"lghi 0,2 \n\t"
"llgfr 1,%1 \n\t"
"llgfr 2,%2 \n\t"
"llgfr 3,%3 \n\t"
"siga 0 \n\t"
"ipm %0 \n\t"
"srl %0,28 \n\t"
: "=d" (cc)
: "d" (0x10000|irq), "d" (mask1), "d" (mask2)
: "cc", "0", "1", "2", "3"
);
#endif /* CONFIG_ARCH_S390X */
return cc;
}
extern __inline__ int
do_siga_input(unsigned int irq, unsigned int mask)
{
int cc;
#ifndef CONFIG_ARCH_S390X
asm volatile (
"lhi 0,1 \n\t"
"lr 1,%1 \n\t"
"lr 2,%2 \n\t"
"siga 0 \n\t"
"ipm %0 \n\t"
"srl %0,28 \n\t"
: "=d" (cc)
: "d" (0x10000|irq), "d" (mask)
: "cc", "0", "1", "2", "memory"
);
#else /* CONFIG_ARCH_S390X */
asm volatile (
"lghi 0,1 \n\t"
"llgfr 1,%1 \n\t"
"llgfr 2,%2 \n\t"
"siga 0 \n\t"
"ipm %0 \n\t"
"srl %0,28 \n\t"
: "=d" (cc)
: "d" (0x10000|irq), "d" (mask)
: "cc", "0", "1", "2", "memory"
);
#endif /* CONFIG_ARCH_S390X */
return cc;
}
extern __inline__ int
do_siga_output(unsigned long irq, unsigned long mask, __u32 *bb)
{
int cc;
__u32 busy_bit;
#ifndef CONFIG_ARCH_S390X
asm volatile (
"lhi 0,0 \n\t"
"lr 1,%2 \n\t"
"lr 2,%3 \n\t"
"siga 0 \n\t"
"0:"
"ipm %0 \n\t"
"srl %0,28 \n\t"
"srl 0,31 \n\t"
"lr %1,0 \n\t"
"1: \n\t"
".section .fixup,\"ax\"\n\t"
"2: \n\t"
"lhi %0,%4 \n\t"
"bras 1,3f \n\t"
".long 1b \n\t"
"3: \n\t"
"l 1,0(1) \n\t"
"br 1 \n\t"
".previous \n\t"
".section __ex_table,\"a\"\n\t"
".align 4 \n\t"
".long 0b,2b \n\t"
".previous \n\t"
: "=d" (cc), "=d" (busy_bit)
: "d" (0x10000|irq), "d" (mask),
"i" (QDIO_SIGA_ERROR_ACCESS_EXCEPTION)
: "cc", "0", "1", "2", "memory"
);
#else /* CONFIG_ARCH_S390X */
asm volatile (
"lghi 0,0 \n\t"
"llgfr 1,%2 \n\t"
"llgfr 2,%3 \n\t"
"siga 0 \n\t"
"0:"
"ipm %0 \n\t"
"srl %0,28 \n\t"
"srl 0,31 \n\t"
"llgfr %1,0 \n\t"
"1: \n\t"
".section .fixup,\"ax\"\n\t"
"lghi %0,%4 \n\t"
"jg 1b \n\t"
".previous\n\t"
".section __ex_table,\"a\"\n\t"
".align 8 \n\t"
".quad 0b,1b \n\t"
".previous \n\t"
: "=d" (cc), "=d" (busy_bit)
: "d" (0x10000|irq), "d" (mask),
"i" (QDIO_SIGA_ERROR_ACCESS_EXCEPTION)
: "cc", "0", "1", "2", "memory"
);
#endif /* CONFIG_ARCH_S390X */
(*bb) = busy_bit;
return cc;
}
extern __inline__ unsigned long
do_clear_global_summary(void)
{
unsigned long time;
#ifndef CONFIG_ARCH_S390X
asm volatile (
"lhi 1,3 \n\t"
".insn rre,0xb2650000,2,0 \n\t"
"lr %0,3 \n\t"
: "=d" (time) : : "cc", "1", "2", "3"
);
#else /* CONFIG_ARCH_S390X */
asm volatile (
"lghi 1,3 \n\t"
".insn rre,0xb2650000,2,0 \n\t"
"lgr %0,3 \n\t"
: "=d" (time) : : "cc", "1", "2", "3"
);
#endif /* CONFIG_ARCH_S390X */
return time;
}
/*
* QDIO device commands returned by extended Sense-ID
*/
#define DEFAULT_ESTABLISH_QS_CMD 0x1b
#define DEFAULT_ESTABLISH_QS_COUNT 0x1000
#define DEFAULT_ACTIVATE_QS_CMD 0x1f
#define DEFAULT_ACTIVATE_QS_COUNT 0
/*
* additional CIWs returned by extended Sense-ID
*/
#define CIW_TYPE_EQUEUE 0x3 /* establish QDIO queues */
#define CIW_TYPE_AQUEUE 0x4 /* activate QDIO queues */
#define QDIO_CHSC_RESPONSE_CODE_OK 1
/* flags for st qdio sch data */
#define CHSC_FLAG_QDIO_CAPABILITY 0x80
#define CHSC_FLAG_VALIDITY 0x40
#define CHSC_FLAG_SIGA_INPUT_NECESSARY 0x40
#define CHSC_FLAG_SIGA_OUTPUT_NECESSARY 0x20
#define CHSC_FLAG_SIGA_SYNC_NECESSARY 0x10
#define CHSC_FLAG_SIGA_SYNC_DONE_ON_THININTS 0x08
#define CHSC_FLAG_SIGA_SYNC_DONE_ON_OUTB_PCIS 0x04
#ifdef QDIO_PERFORMANCE_STATS
struct qdio_perf_stats {
unsigned int tl_runs;
unsigned int siga_outs;
unsigned int siga_ins;
unsigned int siga_syncs;
unsigned int pcis;
unsigned int thinints;
unsigned int fast_reqs;
__u64 start_time_outbound;
unsigned int outbound_cnt;
unsigned int outbound_time;
__u64 start_time_inbound;
unsigned int inbound_cnt;
unsigned int inbound_time;
};
#endif /* QDIO_PERFORMANCE_STATS */
#define atomic_swap(a,b) xchg((int*)a.counter,b)
/* unlikely as the later the better */
#define SYNC_MEMORY if (unlikely(q->siga_sync)) qdio_siga_sync_q(q)
#define SYNC_MEMORY_ALL if (unlikely(q->siga_sync)) \
qdio_siga_sync(q,~0U,~0U)
#define SYNC_MEMORY_ALL_OUTB if (unlikely(q->siga_sync)) \
qdio_siga_sync(q,~0U,0)
#define NOW qdio_get_micros()
#define SAVE_TIMESTAMP(q) q->timing.last_transfer_time=NOW
#define GET_SAVED_TIMESTAMP(q) (q->timing.last_transfer_time)
#define SAVE_FRONTIER(q,val) q->last_move_ftc=val
#define GET_SAVED_FRONTIER(q) (q->last_move_ftc)
#define MY_MODULE_STRING(x) #x
#ifdef CONFIG_ARCH_S390X
#define QDIO_GET_ADDR(x) ((__u32)(unsigned long)x)
#else /* CONFIG_ARCH_S390X */
#define QDIO_GET_ADDR(x) ((__u32)(long)x)
#endif /* CONFIG_ARCH_S390X */
#ifdef CONFIG_QDIO_DEBUG
#define set_slsb(x,y) \
if(q->queue_type==QDIO_TRACE_QTYPE) { \
if(q->is_input_q) { \
QDIO_DBF_HEX2(0,slsb_in,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \
} else { \
QDIO_DBF_HEX2(0,slsb_out,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \
} \
} \
qdio_set_slsb(x,y); \
if(q->queue_type==QDIO_TRACE_QTYPE) { \
if(q->is_input_q) { \
QDIO_DBF_HEX2(0,slsb_in,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \
} else { \
QDIO_DBF_HEX2(0,slsb_out,&q->slsb,QDIO_MAX_BUFFERS_PER_Q); \
} \
}
#else /* CONFIG_QDIO_DEBUG */
#define set_slsb(x,y) qdio_set_slsb(x,y)
#endif /* CONFIG_QDIO_DEBUG */
struct qdio_q {
volatile struct slsb slsb;
char unused[QDIO_MAX_BUFFERS_PER_Q];
__u32 * volatile dev_st_chg_ind;
int is_input_q;
int irq;
struct ccw_device *cdev;
unsigned int is_iqdio_q;
unsigned int is_thinint_q;
/* bit 0 means queue 0, bit 1 means queue 1, ... */
unsigned int mask;
unsigned int q_no;
qdio_handler_t (*handler);
/* points to the next buffer to be checked for having
* been processed by the card (outbound)
* or to the next buffer the program should check for (inbound) */
volatile int first_to_check;
/* and the last time it was: */
volatile int last_move_ftc;
atomic_t number_of_buffers_used;
atomic_t polling;
unsigned int siga_in;
unsigned int siga_out;
unsigned int siga_sync;
unsigned int siga_sync_done_on_thinints;
unsigned int siga_sync_done_on_outb_tis;
unsigned int hydra_gives_outbound_pcis;
/* used to save beginning position when calling dd_handlers */
int first_element_to_kick;
atomic_t use_count;
atomic_t is_in_shutdown;
void *irq_ptr;
#ifdef QDIO_USE_TIMERS_FOR_POLLING
struct timer_list timer;
atomic_t timer_already_set;
spinlock_t timer_lock;
#else /* QDIO_USE_TIMERS_FOR_POLLING */
struct tasklet_struct tasklet;
#endif /* QDIO_USE_TIMERS_FOR_POLLING */
enum qdio_irq_states state;
/* used to store the error condition during a data transfer */
unsigned int qdio_error;
unsigned int siga_error;
unsigned int error_status_flags;
/* list of interesting queues */
volatile struct qdio_q *list_next;
volatile struct qdio_q *list_prev;
struct sl *sl;
volatile struct sbal *sbal[QDIO_MAX_BUFFERS_PER_Q];
struct qdio_buffer *qdio_buffers[QDIO_MAX_BUFFERS_PER_Q];
unsigned long int_parm;
/*struct {
int in_bh_check_limit;
int threshold;
} threshold_classes[QDIO_STATS_CLASSES];*/
struct {
/* inbound: the time to stop polling
outbound: the time to kick peer */
int threshold; /* the real value */
/* outbound: last time of do_QDIO
inbound: last time of noticing incoming data */
/*__u64 last_transfer_times[QDIO_STATS_NUMBER];
int last_transfer_index; */
__u64 last_transfer_time;
__u64 busy_start;
} timing;
atomic_t busy_siga_counter;
unsigned int queue_type;
/* leave this member at the end. won't be cleared in qdio_fill_qs */
struct slib *slib; /* a page is allocated under this pointer,
sl points into this page, offset PAGE_SIZE/2
(after slib) */
} __attribute__ ((aligned(256)));
struct qdio_irq {
__u32 * volatile dev_st_chg_ind;
unsigned long int_parm;
int irq;
unsigned int is_iqdio_irq;
unsigned int is_thinint_irq;
unsigned int hydra_gives_outbound_pcis;
unsigned int sync_done_on_outb_pcis;
enum qdio_irq_states state;
unsigned int no_input_qs;
unsigned int no_output_qs;
unsigned char qdioac;
struct ccw1 ccw;
struct ciw equeue;
struct ciw aqueue;
struct qib qib;
void (*original_int_handler) (struct ccw_device *,
unsigned long, struct irb *);
/* leave these four members together at the end. won't be cleared in qdio_fill_irq */
struct qdr *qdr;
struct qdio_q *input_qs[QDIO_MAX_QUEUES_PER_IRQ];
struct qdio_q *output_qs[QDIO_MAX_QUEUES_PER_IRQ];
struct semaphore setting_up_sema;
};
#endif

View file

@ -0,0 +1,6 @@
#
# S/390 crypto devices
#
z90crypt-objs := z90main.o z90hardware.o
obj-$(CONFIG_Z90CRYPT) += z90crypt.o

View file

@ -0,0 +1,168 @@
/*
* linux/drivers/s390/crypto/z90common.h
*
* z90crypt 1.3.2
*
* Copyright (C) 2001, 2004 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 VERSION_Z90COMMON_H "$Revision: 1.16 $"
#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
};
#define Z90C_NO_DEVICES 1
#define Z90C_AMBIGUOUS_DOMAIN 2
#define Z90C_INCORRECT_DOMAIN 3
#define ENOTINIT 4
#define SEN_BUSY 7
#define SEN_USER_ERROR 8
#define SEN_QUEUE_FULL 11
#define SEN_NOT_AVAIL 16
#define SEN_PAD_ERROR 17
#define SEN_RETRY 18
#define SEN_RELEASED 24
#define REC_EMPTY 4
#define REC_BUSY 6
#define REC_OPERAND_INV 8
#define REC_OPERAND_SIZE 9
#define REC_EVEN_MOD 10
#define REC_NO_WORK 11
#define REC_HARDWAR_ERR 12
#define REC_NO_RESPONSE 13
#define REC_RETRY_DEV 14
#define REC_USER_GONE 15
#define REC_BAD_MESSAGE 16
#define REC_INVALID_PAD 17
#define REC_USE_PCICA 18
#define WRONG_DEVICE_TYPE 20
#define REC_FATAL_ERROR 32
#define SEN_FATAL_ERROR 33
#define TSQ_FATAL_ERROR 34
#define RSQ_FATAL_ERROR 35
#define Z90CRYPT_NUM_TYPES 5
#define PCICA 0
#define PCICC 1
#define PCIXCC_MCL2 2
#define PCIXCC_MCL3 3
#define CEX2C 4
#define NILDEV -1
#define ANYDEV -1
#define PCIXCC_UNK -2
enum hdevice_type {
PCICC_HW = 3,
PCICA_HW = 4,
PCIXCC_HW = 5,
OTHER_HW = 6,
CEX2C_HW = 7
};
struct CPRBX {
unsigned short cprb_len;
unsigned char cprb_ver_id;
unsigned char pad_000[3];
unsigned char func_id[2];
unsigned char cprb_flags[4];
unsigned int req_parml;
unsigned int req_datal;
unsigned int rpl_msgbl;
unsigned int rpld_parml;
unsigned int rpl_datal;
unsigned int rpld_datal;
unsigned int req_extbl;
unsigned char pad_001[4];
unsigned int rpld_extbl;
unsigned char req_parmb[16];
unsigned char req_datab[16];
unsigned char rpl_parmb[16];
unsigned char rpl_datab[16];
unsigned char req_extb[16];
unsigned char rpl_extb[16];
unsigned short ccp_rtcode;
unsigned short ccp_rscode;
unsigned int mac_data_len;
unsigned char logon_id[8];
unsigned char mac_value[8];
unsigned char mac_content_flgs;
unsigned char pad_002;
unsigned short domain;
unsigned char pad_003[12];
unsigned char pad_004[36];
};
#ifndef DEV_NAME
#define DEV_NAME "z90crypt"
#endif
#define PRINTK(fmt, args...) \
printk(KERN_DEBUG DEV_NAME ": %s -> " fmt, __FUNCTION__ , ## args)
#define PRINTKN(fmt, args...) \
printk(KERN_DEBUG DEV_NAME ": " fmt, ## args)
#define PRINTKW(fmt, args...) \
printk(KERN_WARNING DEV_NAME ": %s -> " fmt, __FUNCTION__ , ## args)
#define PRINTKC(fmt, args...) \
printk(KERN_CRIT DEV_NAME ": %s -> " fmt, __FUNCTION__ , ## args)
#ifdef Z90CRYPT_DEBUG
#define PDEBUG(fmt, args...) \
printk(KERN_DEBUG DEV_NAME ": %s -> " fmt, __FUNCTION__ , ## args)
#else
#define PDEBUG(fmt, args...) do {} while (0)
#endif
#define UMIN(a,b) ((a) < (b) ? (a) : (b))
#define IS_EVEN(x) ((x) == (2 * ((x) / 2)))
#endif

View file

@ -0,0 +1,258 @@
/*
* linux/drivers/s390/crypto/z90crypt.h
*
* z90crypt 1.3.2
*
* Copyright (C) 2001, 2004 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 _Z90CRYPT_H_
#define _Z90CRYPT_H_
#include <linux/ioctl.h>
#define VERSION_Z90CRYPT_H "$Revision: 1.11 $"
#define z90crypt_VERSION 1
#define z90crypt_RELEASE 3 // 2 = PCIXCC, 3 = rewrite for coding standards
#define z90crypt_VARIANT 2 // 2 = added PCIXCC MCL3 and CEX2C support
/**
* If we are not using the sparse checker, __user has no use.
*/
#ifdef __CHECKER__
# define __user __attribute__((noderef, address_space(1)))
#else
# define __user
#endif
/**
* struct ica_rsa_modexpo
*
* Requirements:
* - outputdatalength is at least as large as inputdatalength.
* - All key parts are right justified in their fields, padded on
* the left with zeroes.
* - length(b_key) = inputdatalength
* - length(n_modulus) = inputdatalength
*/
struct ica_rsa_modexpo {
char __user * inputdata;
unsigned int inputdatalength;
char __user * outputdata;
unsigned int outputdatalength;
char __user * b_key;
char __user * n_modulus;
};
/**
* struct ica_rsa_modexpo_crt
*
* Requirements:
* - inputdatalength is even.
* - outputdatalength is at least as large as inputdatalength.
* - All key parts are right justified in their fields, padded on
* the left with zeroes.
* - length(bp_key) = inputdatalength/2 + 8
* - length(bq_key) = inputdatalength/2
* - length(np_key) = inputdatalength/2 + 8
* - length(nq_key) = inputdatalength/2
* - length(u_mult_inv) = inputdatalength/2 + 8
*/
struct ica_rsa_modexpo_crt {
char __user * inputdata;
unsigned int inputdatalength;
char __user * outputdata;
unsigned int outputdatalength;
char __user * bp_key;
char __user * bq_key;
char __user * np_prime;
char __user * nq_prime;
char __user * u_mult_inv;
};
#define Z90_IOCTL_MAGIC 'z' // NOTE: Need to allocate from linux folks
/**
* Interface notes:
*
* The ioctl()s which are implemented (along with relevant details)
* are:
*
* ICARSAMODEXPO
* Perform an RSA operation using a Modulus-Exponent pair
* This takes an ica_rsa_modexpo struct as its arg.
*
* NOTE: please refer to the comments preceding this structure
* for the implementation details for the contents of the
* block
*
* ICARSACRT
* Perform an RSA operation using a Chinese-Remainder Theorem key
* This takes an ica_rsa_modexpo_crt struct as its arg.
*
* NOTE: please refer to the comments preceding this structure
* for the implementation details for the contents of the
* block
*
* Z90STAT_TOTALCOUNT
* Return an integer count of all device types together.
*
* Z90STAT_PCICACOUNT
* Return an integer count of all PCICAs.
*
* Z90STAT_PCICCCOUNT
* Return an integer count of all PCICCs.
*
* Z90STAT_PCIXCCMCL2COUNT
* Return an integer count of all MCL2 PCIXCCs.
*
* Z90STAT_PCIXCCMCL3COUNT
* Return an integer count of all MCL3 PCIXCCs.
*
* Z90STAT_CEX2CCOUNT
* Return an integer count of all CEX2Cs.
*
* Z90STAT_REQUESTQ_COUNT
* Return an integer count of the number of entries waiting to be
* sent to a device.
*
* Z90STAT_PENDINGQ_COUNT
* Return an integer count of the number of entries sent to a
* device awaiting the reply.
*
* Z90STAT_TOTALOPEN_COUNT
* Return an integer count of the number of open file handles.
*
* Z90STAT_DOMAIN_INDEX
* Return the integer value of the Cryptographic Domain.
*
* Z90STAT_STATUS_MASK
* Return an 64 element array of unsigned chars for the status of
* all devices.
* 0x01: PCICA
* 0x02: PCICC
* 0x03: PCIXCC_MCL2
* 0x04: PCIXCC_MCL3
* 0x05: CEX2C
* 0x0d: device is disabled via the proc filesystem
*
* Z90STAT_QDEPTH_MASK
* Return an 64 element array of unsigned chars for the queue
* depth of all devices.
*
* Z90STAT_PERDEV_REQCNT
* Return an 64 element array of unsigned integers for the number
* of successfully completed requests per device since the device
* was detected and made available.
*
* ICAZ90STATUS (deprecated)
* Return some device driver status in a ica_z90_status struct
* This takes an ica_z90_status struct as its arg.
*
* NOTE: this ioctl() is deprecated, and has been replaced with
* single ioctl()s for each type of status being requested
*
* Z90STAT_PCIXCCCOUNT (deprecated)
* Return an integer count of all PCIXCCs (MCL2 + MCL3).
* This is DEPRECATED now that MCL3 PCIXCCs are treated differently from
* MCL2 PCIXCCs.
*
* Z90QUIESCE (not recommended)
* Quiesce the driver. This is intended to stop all new
* requests from being processed. Its use is NOT recommended,
* except in circumstances where there is no other way to stop
* callers from accessing the driver. Its original use was to
* allow the driver to be "drained" of work in preparation for
* a system shutdown.
*
* NOTE: once issued, this ban on new work cannot be undone
* except by unloading and reloading the driver.
*/
/**
* Supported ioctl calls
*/
#define ICARSAMODEXPO _IOC(_IOC_READ|_IOC_WRITE, Z90_IOCTL_MAGIC, 0x05, 0)
#define ICARSACRT _IOC(_IOC_READ|_IOC_WRITE, Z90_IOCTL_MAGIC, 0x06, 0)
/* DEPRECATED status calls (bound for removal at some point) */
#define ICAZ90STATUS _IOR(Z90_IOCTL_MAGIC, 0x10, struct ica_z90_status)
#define Z90STAT_PCIXCCCOUNT _IOR(Z90_IOCTL_MAGIC, 0x43, int)
/* unrelated to ICA callers */
#define Z90QUIESCE _IO(Z90_IOCTL_MAGIC, 0x11)
/* New status calls */
#define Z90STAT_TOTALCOUNT _IOR(Z90_IOCTL_MAGIC, 0x40, int)
#define Z90STAT_PCICACOUNT _IOR(Z90_IOCTL_MAGIC, 0x41, int)
#define Z90STAT_PCICCCOUNT _IOR(Z90_IOCTL_MAGIC, 0x42, int)
#define Z90STAT_PCIXCCMCL2COUNT _IOR(Z90_IOCTL_MAGIC, 0x4b, int)
#define Z90STAT_PCIXCCMCL3COUNT _IOR(Z90_IOCTL_MAGIC, 0x4c, int)
#define Z90STAT_CEX2CCOUNT _IOR(Z90_IOCTL_MAGIC, 0x4d, int)
#define Z90STAT_REQUESTQ_COUNT _IOR(Z90_IOCTL_MAGIC, 0x44, int)
#define Z90STAT_PENDINGQ_COUNT _IOR(Z90_IOCTL_MAGIC, 0x45, int)
#define Z90STAT_TOTALOPEN_COUNT _IOR(Z90_IOCTL_MAGIC, 0x46, int)
#define Z90STAT_DOMAIN_INDEX _IOR(Z90_IOCTL_MAGIC, 0x47, int)
#define Z90STAT_STATUS_MASK _IOR(Z90_IOCTL_MAGIC, 0x48, char[64])
#define Z90STAT_QDEPTH_MASK _IOR(Z90_IOCTL_MAGIC, 0x49, char[64])
#define Z90STAT_PERDEV_REQCNT _IOR(Z90_IOCTL_MAGIC, 0x4a, int[64])
/**
* local errno definitions
*/
#define ENOBUFF 129 // filp->private_data->...>work_elem_p->buffer is NULL
#define EWORKPEND 130 // user issues ioctl while another pending
#define ERELEASED 131 // user released while ioctl pending
#define EQUIESCE 132 // z90crypt quiescing (no more work allowed)
#define ETIMEOUT 133 // request timed out
#define EUNKNOWN 134 // some unrecognized error occured (retry may succeed)
#define EGETBUFF 135 // Error getting buffer or hardware lacks capability
// (retry in software)
/**
* DEPRECATED STRUCTURES
*/
/**
* This structure is DEPRECATED and the corresponding ioctl() has been
* replaced with individual ioctl()s for each piece of data!
* This structure will NOT survive past version 1.3.1, so switch to the
* new ioctl()s.
*/
#define MASK_LENGTH 64 // mask length
struct ica_z90_status {
int totalcount;
int leedslitecount; // PCICA
int leeds2count; // PCICC
// int PCIXCCCount; is not in struct for backward compatibility
int requestqWaitCount;
int pendingqWaitCount;
int totalOpenCount;
int cryptoDomain;
// status: 0=not there, 1=PCICA, 2=PCICC, 3=PCIXCC_MCL2, 4=PCIXCC_MCL3,
// 5=CEX2C
unsigned char status[MASK_LENGTH];
// qdepth: # work elements waiting for each device
unsigned char qdepth[MASK_LENGTH];
};
#endif /* _Z90CRYPT_H_ */

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

246
drivers/s390/ebcdic.c Normal file
View file

@ -0,0 +1,246 @@
/*
* arch/s390/kernel/ebcdic.c
* ECBDIC -> ASCII, ASCII -> ECBDIC conversion tables.
*
* S390 version
* Copyright (C) 1998 IBM Corporation
* Author(s): Martin Schwidefsky <schwidefsky@de.ibm.com>
*/
#include <asm/types.h>
/*
* ASCII -> EBCDIC
*/
__u8 _ascebc[256] =
{
/*00 NL SH SX EX ET NQ AK BL */
0x00, 0x01, 0x02, 0x03, 0x37, 0x2D, 0x2E, 0x2F,
/*08 BS HT LF VT FF CR SO SI */
0x16, 0x05, 0x15, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
/*10 DL D1 D2 D3 D4 NK SN EB */
0x10, 0x11, 0x12, 0x13, 0x3C, 0x15, 0x32, 0x26,
/*18 CN EM SB EC FS GS RS US */
0x18, 0x19, 0x3F, 0x27, 0x1C, 0x1D, 0x1E, 0x1F,
/*20 SP ! " # $ % & ' */
0x40, 0x5A, 0x7F, 0x7B, 0x5B, 0x6C, 0x50, 0x7D,
/*28 ( ) * + , - . / */
0x4D, 0x5D, 0x5C, 0x4E, 0x6B, 0x60, 0x4B, 0x61,
/*30 0 1 2 3 4 5 6 7 */
0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7,
/*38 8 9 : ; < = > ? */
0xF8, 0xF9, 0x7A, 0x5E, 0x4C, 0x7E, 0x6E, 0x6F,
/*40 @ A B C D E F G */
0x7C, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7,
/*48 H I J K L M N O */
0xC8, 0xC9, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6,
/*50 P Q R S T U V W */
0xD7, 0xD8, 0xD9, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6,
/*58 X Y Z [ \ ] ^ _ */
0xE7, 0xE8, 0xE9, 0xAD, 0xE0, 0xBD, 0x5F, 0x6D,
/*60 ` a b c d e f g */
0x79, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87,
/*68 h i j k l m n o */
0x88, 0x89, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96,
/*70 p q r s t u v w */
0x97, 0x98, 0x99, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6,
/*78 x y z { | } ~ DL */
0xA7, 0xA8, 0xA9, 0xC0, 0x4F, 0xD0, 0xA1, 0x07,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F,
0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0x3F, 0xFF
};
/*
* EBCDIC -> ASCII
*/
__u8 _ebcasc[256] =
{
/* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */
0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F,
/* 0x08 -GE -SPS -RPT VT FF CR SO SI */
0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
/* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC
-ENP ->LF */
0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07,
/* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB
-IUS */
0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
/* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC
-INP */
0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B,
/* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL
-SW */
0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07,
/* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */
0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04,
/* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */
0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A,
/* 0x40 SP RSP ∽ ---- */
0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86,
/* 0x48 . < ( + | */
0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C,
/* 0x50 & ---- */
0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07,
/* 0x58 ≡ ! $ * ) ; */
0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA,
/* 0x60 - / ---- <20> ---- ---- ---- */
0x2D, 0x2F, 0x07, 0x8E, 0x07, 0x07, 0x07, 0x8F,
/* 0x68 ---- , % _ > ? */
0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F,
/* 0x70 ---- ---- ---- ---- ---- ---- ---- */
0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
/* 0x78 * ` : # @ ' = " */
0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22,
/* 0x80 * a b c d e f g */
0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
/* 0x88 h i ---- ---- ---- */
0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1,
/* 0x90 <20> j k l m n o p */
0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
/* 0x98 q r ---- ---- */
0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07,
/* 0xA0 ~ s t u v w x */
0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
/* 0xA8 y z ---- ---- ---- ---- */
0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07,
/* 0xB0 ^ ---- 〒 ---- */
0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC,
/* 0xB8 ---- [ ] ---- ---- ---- ---- */
0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07,
/* 0xC0 { A B C D E F G */
0x7B, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47,
/* 0xC8 H I ---- ‡ ---- */
0x48, 0x49, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07,
/* 0xD0 } J K L M N O P */
0x7D, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, 0x50,
/* 0xD8 Q R ---- ◯ */
0x51, 0x52, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98,
/* 0xE0 \ S T U V W X */
0x5C, 0xF6, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58,
/* 0xE8 Y Z ---- <20> ---- ---- ---- */
0x59, 0x5A, 0xFD, 0x07, 0x99, 0x07, 0x07, 0x07,
/* 0xF0 0 1 2 3 4 5 6 7 */
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
/* 0xF8 8 9 ---- ---- ⌒ ---- ---- ---- */
0x38, 0x39, 0x07, 0x07, 0x9A, 0x07, 0x07, 0x07
};
/*
* EBCDIC (capitals) -> ASCII (small case)
*/
__u8 _ebcasc_reduce_case[256] =
{
/* 0x00 NUL SOH STX ETX *SEL HT *RNL DEL */
0x00, 0x01, 0x02, 0x03, 0x07, 0x09, 0x07, 0x7F,
/* 0x08 -GE -SPS -RPT VT FF CR SO SI */
0x07, 0x07, 0x07, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F,
/* 0x10 DLE DC1 DC2 DC3 -RES -NL BS -POC
-ENP ->LF */
0x10, 0x11, 0x12, 0x13, 0x07, 0x0A, 0x08, 0x07,
/* 0x18 CAN EM -UBS -CU1 -IFS -IGS -IRS -ITB
-IUS */
0x18, 0x19, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
/* 0x20 -DS -SOS FS -WUS -BYP LF ETB ESC
-INP */
0x07, 0x07, 0x1C, 0x07, 0x07, 0x0A, 0x17, 0x1B,
/* 0x28 -SA -SFE -SM -CSP -MFA ENQ ACK BEL
-SW */
0x07, 0x07, 0x07, 0x07, 0x07, 0x05, 0x06, 0x07,
/* 0x30 ---- ---- SYN -IR -PP -TRN -NBS EOT */
0x07, 0x07, 0x16, 0x07, 0x07, 0x07, 0x07, 0x04,
/* 0x38 -SBS -IT -RFF -CU3 DC4 NAK ---- SUB */
0x07, 0x07, 0x07, 0x07, 0x14, 0x15, 0x07, 0x1A,
/* 0x40 SP RSP ∽ ---- */
0x20, 0xFF, 0x83, 0x84, 0x85, 0xA0, 0x07, 0x86,
/* 0x48 . < ( + | */
0x87, 0xA4, 0x9B, 0x2E, 0x3C, 0x28, 0x2B, 0x7C,
/* 0x50 & ---- */
0x26, 0x82, 0x88, 0x89, 0x8A, 0xA1, 0x8C, 0x07,
/* 0x58 ≡ ! $ * ) ; */
0x8D, 0xE1, 0x21, 0x24, 0x2A, 0x29, 0x3B, 0xAA,
/* 0x60 - / ---- <20> ---- ---- ---- */
0x2D, 0x2F, 0x07, 0x84, 0x07, 0x07, 0x07, 0x8F,
/* 0x68 ---- , % _ > ? */
0x80, 0xA5, 0x07, 0x2C, 0x25, 0x5F, 0x3E, 0x3F,
/* 0x70 ---- ---- ---- ---- ---- ---- ---- */
0x07, 0x90, 0x07, 0x07, 0x07, 0x07, 0x07, 0x07,
/* 0x78 * ` : # @ ' = " */
0x70, 0x60, 0x3A, 0x23, 0x40, 0x27, 0x3D, 0x22,
/* 0x80 * a b c d e f g */
0x07, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
/* 0x88 h i ---- ---- ---- */
0x68, 0x69, 0xAE, 0xAF, 0x07, 0x07, 0x07, 0xF1,
/* 0x90 <20> j k l m n o p */
0xF8, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
/* 0x98 q r ---- ---- */
0x71, 0x72, 0xA6, 0xA7, 0x91, 0x07, 0x92, 0x07,
/* 0xA0 ~ s t u v w x */
0xE6, 0x7E, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
/* 0xA8 y z ---- ---- ---- ---- */
0x79, 0x7A, 0xAD, 0xAB, 0x07, 0x07, 0x07, 0x07,
/* 0xB0 ^ ---- 〒 ---- */
0x5E, 0x9C, 0x9D, 0xFA, 0x07, 0x07, 0x07, 0xAC,
/* 0xB8 ---- [ ] ---- ---- ---- ---- */
0xAB, 0x07, 0x5B, 0x5D, 0x07, 0x07, 0x07, 0x07,
/* 0xC0 { A B C D E F G */
0x7B, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67,
/* 0xC8 H I ---- ‡ ---- */
0x68, 0x69, 0x07, 0x93, 0x94, 0x95, 0xA2, 0x07,
/* 0xD0 } J K L M N O P */
0x7D, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 0x70,
/* 0xD8 Q R ---- ◯ */
0x71, 0x72, 0x07, 0x96, 0x81, 0x97, 0xA3, 0x98,
/* 0xE0 \ S T U V W X */
0x5C, 0xF6, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78,
/* 0xE8 Y Z ---- <20> ---- ---- ---- */
0x79, 0x7A, 0xFD, 0x07, 0x94, 0x07, 0x07, 0x07,
/* 0xF0 0 1 2 3 4 5 6 7 */
0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
/* 0xF8 8 9 ---- ---- ⌒ ---- ---- ---- */
0x38, 0x39, 0x07, 0x07, 0x81, 0x07, 0x07, 0x07
};

108
drivers/s390/net/Kconfig Normal file
View file

@ -0,0 +1,108 @@
menu "S/390 network device drivers"
depends on NETDEVICES && ARCH_S390
config LCS
tristate "Lan Channel Station Interface"
depends on NETDEVICES && (NET_ETHERNET || TR || FDDI)
help
Select this option if you want to use LCS networking on IBM S/390
or zSeries. This device driver supports Token Ring (IEEE 802.5),
FDDI (IEEE 802.7) and Ethernet.
This option is also available as a module which will be
called lcs.ko. If you do not know what it is, it's safe to say "Y".
config CTC
tristate "CTC device support"
depends on NETDEVICES
help
Select this option if you want to use channel-to-channel networking
on IBM S/390 or zSeries. This device driver supports real CTC
coupling using ESCON. It also supports virtual CTCs when running
under VM. It will use the channel device configuration if this is
available. This option is also available as a module which will be
called ctc.ko. If you do not know what it is, it's safe to say "Y".
config IUCV
tristate "IUCV support (VM only)"
help
Select this option if you want to use inter-user communication
under VM or VIF. If unsure, say "Y" to enable a fast communication
link between VM guests. At boot time the user ID of the guest needs
to be passed to the kernel. Note that both kernels need to be
compiled with this option and both need to be booted with the user ID
of the other VM guest.
config NETIUCV
tristate "IUCV network device support (VM only)"
depends on IUCV && NETDEVICES
help
Select this option if you want to use inter-user communication
vehicle networking under VM or VIF. It enables a fast communication
link between VM guests. Using ifconfig a point-to-point connection
can be established to the Linux for zSeries and S7390 system
running on the other VM guest. This option is also available
as a module which will be called netiucv.ko. If unsure, say "Y".
config SMSGIUCV
tristate "IUCV special message support (VM only)"
depends on IUCV
help
Select this option if you want to be able to receive SMSG messages
from other VM guest systems.
config CLAW
tristate "CLAW device support"
depends on NETDEVICES
help
This driver supports channel attached CLAW devices.
CLAW is Common Link Access for Workstation. Common devices
that use CLAW are RS/6000s, Cisco Routers (CIP) and 3172 devices.
To compile as a module choose M here: The module will be called
claw.ko to compile into the kernel choose Y
config QETH
tristate "Gigabit Ethernet device support"
depends on NETDEVICES && IP_MULTICAST && QDIO
help
This driver supports the IBM S/390 and zSeries OSA Express adapters
in QDIO mode (all media types), HiperSockets interfaces and VM GuestLAN
interfaces in QDIO and HIPER mode.
For details please refer to the documentation provided by IBM at
<http://www10.software.ibm.com/developerworks/opensource/linux390>
To compile this driver as a module, choose M here: the
module will be called qeth.ko.
comment "Gigabit Ethernet default settings"
depends on QETH
config QETH_IPV6
bool "IPv6 support for gigabit ethernet"
depends on (QETH = IPV6) || (QETH && IPV6 = 'y')
help
If CONFIG_QETH is switched on, this option will include IPv6
support in the qeth device driver.
config QETH_VLAN
bool "VLAN support for gigabit ethernet"
depends on (QETH = VLAN_8021Q) || (QETH && VLAN_8021Q = 'y')
help
If CONFIG_QETH is switched on, this option will include IEEE
802.1q VLAN support in the qeth device driver.
config QETH_PERF_STATS
bool "Performance statistics in /proc"
depends on QETH
help
When switched on, this option will add a file in the proc-fs
(/proc/qeth_perf_stats) containing performance statistics. It
may slightly impact performance, so this is only recommended for
internal tuning of the device driver.
config CCWGROUP
tristate
default (LCS || CTC || QETH)
endmenu

14
drivers/s390/net/Makefile Normal file
View file

@ -0,0 +1,14 @@
#
# S/390 network devices
#
ctc-objs := ctcmain.o ctctty.o ctcdbug.o
obj-$(CONFIG_IUCV) += iucv.o
obj-$(CONFIG_NETIUCV) += netiucv.o fsm.o
obj-$(CONFIG_SMSGIUCV) += smsgiucv.o
obj-$(CONFIG_CTC) += ctc.o fsm.o cu3088.o
obj-$(CONFIG_LCS) += lcs.o cu3088.o
qeth-y := qeth_main.o qeth_mpc.o qeth_sys.o qeth_eddp.o qeth_tso.o
qeth-$(CONFIG_PROC_FS) += qeth_proc.o
obj-$(CONFIG_QETH) += qeth.o

4447
drivers/s390/net/claw.c Normal file

File diff suppressed because it is too large Load diff

335
drivers/s390/net/claw.h Normal file
View file

@ -0,0 +1,335 @@
/*******************************************************
* Define constants *
* *
********************************************************/
#define VERSION_CLAW_H "$Revision: 1.6 $"
/*-----------------------------------------------------*
* CCW command codes for CLAW protocol *
*------------------------------------------------------*/
#define CCW_CLAW_CMD_WRITE 0x01 /* write - not including link */
#define CCW_CLAW_CMD_READ 0x02 /* read */
#define CCW_CLAW_CMD_NOP 0x03 /* NOP */
#define CCW_CLAW_CMD_SENSE 0x04 /* Sense */
#define CCW_CLAW_CMD_SIGNAL_SMOD 0x05 /* Signal Status Modifier */
#define CCW_CLAW_CMD_TIC 0x08 /* TIC */
#define CCW_CLAW_CMD_READHEADER 0x12 /* read header data */
#define CCW_CLAW_CMD_READFF 0x22 /* read an FF */
#define CCW_CLAW_CMD_SENSEID 0xe4 /* Sense ID */
/*-----------------------------------------------------*
* CLAW Unique constants *
*------------------------------------------------------*/
#define MORE_to_COME_FLAG 0x04 /* OR with write CCW in case of m-t-c */
#define CLAW_IDLE 0x00 /* flag to indicate CLAW is idle */
#define CLAW_BUSY 0xff /* flag to indicate CLAW is busy */
#define CLAW_PENDING 0x00 /* flag to indicate i/o is pending */
#define CLAW_COMPLETE 0xff /* flag to indicate i/o completed */
/*-----------------------------------------------------*
* CLAW control comand code *
*------------------------------------------------------*/
#define SYSTEM_VALIDATE_REQUEST 0x01 /* System Validate request */
#define SYSTEM_VALIDATE_RESPONSE 0x02 /* System Validate response */
#define CONNECTION_REQUEST 0x21 /* Connection request */
#define CONNECTION_RESPONSE 0x22 /* Connection response */
#define CONNECTION_CONFIRM 0x23 /* Connection confirm */
#define DISCONNECT 0x24 /* Disconnect */
#define CLAW_ERROR 0x41 /* CLAW error message */
#define CLAW_VERSION_ID 2 /* CLAW version ID */
/*-----------------------------------------------------*
* CLAW adater sense bytes *
*------------------------------------------------------*/
#define CLAW_ADAPTER_SENSE_BYTE 0x41 /* Stop command issued to adapter */
/*-----------------------------------------------------*
* CLAW control command return codes *
*------------------------------------------------------*/
#define CLAW_RC_NAME_MISMATCH 166 /* names do not match */
#define CLAW_RC_WRONG_VERSION 167 /* wrong CLAW version number */
#define CLAW_RC_HOST_RCV_TOO_SMALL 180 /* Host maximum receive is */
/* less than Linux on zSeries*/
/* transmit size */
/*-----------------------------------------------------*
* CLAW Constants application name *
*------------------------------------------------------*/
#define HOST_APPL_NAME "TCPIP "
#define WS_APPL_NAME_IP_LINK "TCPIP "
#define WS_APPL_NAME_IP_NAME "IP "
#define WS_APPL_NAME_API_LINK "API "
#define WS_APPL_NAME_PACKED "PACKED "
#define WS_NAME_NOT_DEF "NOT_DEF "
#define PACKING_ASK 1
#define PACK_SEND 2
#define DO_PACKED 3
#define MAX_ENVELOPE_SIZE 65536
#define CLAW_DEFAULT_MTU_SIZE 4096
#define DEF_PACK_BUFSIZE 32768
#define READ 0
#define WRITE 1
#define TB_TX 0 /* sk buffer handling in process */
#define TB_STOP 1 /* network device stop in process */
#define TB_RETRY 2 /* retry in process */
#define TB_NOBUFFER 3 /* no buffer on free queue */
#define CLAW_MAX_LINK_ID 1
#define CLAW_MAX_DEV 256 /* max claw devices */
#define MAX_NAME_LEN 8 /* host name, adapter name length */
#define CLAW_FRAME_SIZE 4096
#define CLAW_ID_SIZE BUS_ID_SIZE+3
/* state machine codes used in claw_irq_handler */
#define CLAW_STOP 0
#define CLAW_START_HALT_IO 1
#define CLAW_START_SENSEID 2
#define CLAW_START_READ 3
#define CLAW_START_WRITE 4
/*-----------------------------------------------------*
* Lock flag *
*------------------------------------------------------*/
#define LOCK_YES 0
#define LOCK_NO 1
/*-----------------------------------------------------*
* DBF Debug macros *
*------------------------------------------------------*/
#define CLAW_DBF_TEXT(level, name, text) \
do { \
debug_text_event(claw_dbf_##name, level, text); \
} while (0)
#define CLAW_DBF_HEX(level,name,addr,len) \
do { \
debug_event(claw_dbf_##name,level,(void*)(addr),len); \
} while (0)
#define CLAW_DBF_TEXT_(level,name,text...) \
do { \
sprintf(debug_buffer, text); \
debug_text_event(claw_dbf_##name,level, debug_buffer);\
} while (0)
/*******************************************************
* Define Control Blocks *
* *
********************************************************/
/*------------------------------------------------------*/
/* CLAW header */
/*------------------------------------------------------*/
struct clawh {
__u16 length; /* length of data read by preceding read CCW */
__u8 opcode; /* equivalent read CCW */
__u8 flag; /* flag of FF to indicate read was completed */
};
/*------------------------------------------------------*/
/* CLAW Packing header 4 bytes */
/*------------------------------------------------------*/
struct clawph {
__u16 len; /* Length of Packed Data Area */
__u8 flag; /* Reserved not used */
__u8 link_num; /* Link ID */
};
/*------------------------------------------------------*/
/* CLAW Ending struct ccwbk */
/*------------------------------------------------------*/
struct endccw {
__u32 real; /* real address of this block */
__u8 write1; /* write 1 is active */
__u8 read1; /* read 1 is active */
__u16 reserved; /* reserved for future use */
struct ccw1 write1_nop1;
struct ccw1 write1_nop2;
struct ccw1 write2_nop1;
struct ccw1 write2_nop2;
struct ccw1 read1_nop1;
struct ccw1 read1_nop2;
struct ccw1 read2_nop1;
struct ccw1 read2_nop2;
};
/*------------------------------------------------------*/
/* CLAW struct ccwbk */
/*------------------------------------------------------*/
struct ccwbk {
void *next; /* pointer to next ccw block */
__u32 real; /* real address of this ccw */
void *p_buffer; /* virtual address of data */
struct clawh header; /* claw header */
struct ccw1 write; /* write CCW */
struct ccw1 w_read_FF; /* read FF */
struct ccw1 w_TIC_1; /* TIC */
struct ccw1 read; /* read CCW */
struct ccw1 read_h; /* read header */
struct ccw1 signal; /* signal SMOD */
struct ccw1 r_TIC_1; /* TIC1 */
struct ccw1 r_read_FF; /* read FF */
struct ccw1 r_TIC_2; /* TIC2 */
};
/*------------------------------------------------------*/
/* CLAW control block */
/*------------------------------------------------------*/
struct clawctl {
__u8 command; /* control command */
__u8 version; /* CLAW protocol version */
__u8 linkid; /* link ID */
__u8 correlator; /* correlator */
__u8 rc; /* return code */
__u8 reserved1; /* reserved */
__u8 reserved2; /* reserved */
__u8 reserved3; /* reserved */
__u8 data[24]; /* command specific fields */
};
/*------------------------------------------------------*/
/* Data for SYSTEMVALIDATE command */
/*------------------------------------------------------*/
struct sysval {
char WS_name[8]; /* Workstation System name */
char host_name[8]; /* Host system name */
__u16 read_frame_size; /* read frame size */
__u16 write_frame_size; /* write frame size */
__u8 reserved[4]; /* reserved */
};
/*------------------------------------------------------*/
/* Data for Connect command */
/*------------------------------------------------------*/
struct conncmd {
char WS_name[8]; /* Workstation application name */
char host_name[8]; /* Host application name */
__u16 reserved1[2]; /* read frame size */
__u8 reserved2[4]; /* reserved */
};
/*------------------------------------------------------*/
/* Data for CLAW error */
/*------------------------------------------------------*/
struct clawwerror {
char reserved1[8]; /* reserved */
char reserved2[8]; /* reserved */
char reserved3[8]; /* reserved */
};
/*------------------------------------------------------*/
/* Data buffer for CLAW */
/*------------------------------------------------------*/
struct clawbuf {
char buffer[MAX_ENVELOPE_SIZE]; /* data buffer */
};
/*------------------------------------------------------*/
/* Channel control block for read and write channel */
/*------------------------------------------------------*/
struct chbk {
unsigned int devno;
int irq;
char id[CLAW_ID_SIZE];
__u32 IO_active;
__u8 claw_state;
struct irb *irb;
struct ccw_device *cdev; /* pointer to the channel device */
struct net_device *ndev;
wait_queue_head_t wait;
struct tasklet_struct tasklet;
struct timer_list timer;
unsigned long flag_a; /* atomic flags */
#define CLAW_BH_ACTIVE 0
unsigned long flag_b; /* atomic flags */
#define CLAW_WRITE_ACTIVE 0
__u8 last_dstat;
__u8 flag;
struct sk_buff_head collect_queue;
spinlock_t collect_lock;
#define CLAW_WRITE 0x02 /* - Set if this is a write channel */
#define CLAW_READ 0x01 /* - Set if this is a read channel */
#define CLAW_TIMER 0x80 /* - Set if timer made the wake_up */
};
/*--------------------------------------------------------------*
* CLAW environment block *
*---------------------------------------------------------------*/
struct claw_env {
unsigned int devno[2]; /* device number */
char host_name[9]; /* Host name */
char adapter_name [9]; /* adapter name */
char api_type[9]; /* TCPIP, API or PACKED */
void *p_priv; /* privptr */
__u16 read_buffers; /* read buffer number */
__u16 write_buffers; /* write buffer number */
__u16 read_size; /* read buffer size */
__u16 write_size; /* write buffer size */
__u16 dev_id; /* device ident */
__u8 packing; /* are we packing? */
volatile __u8 queme_switch; /* gate for imed packing */
volatile unsigned long pk_delay; /* Delay for adaptive packing */
__u8 in_use; /* device active flag */
struct net_device *ndev; /* backward ptr to the net dev*/
};
/*--------------------------------------------------------------*
* CLAW main control block *
*---------------------------------------------------------------*/
struct claw_privbk {
void *p_buff_ccw;
__u32 p_buff_ccw_num;
void *p_buff_read;
__u32 p_buff_read_num;
__u32 p_buff_pages_perread;
void *p_buff_write;
__u32 p_buff_write_num;
__u32 p_buff_pages_perwrite;
long active_link_ID; /* Active logical link ID */
struct ccwbk *p_write_free_chain; /* pointer to free ccw chain */
struct ccwbk *p_write_active_first; /* ptr to the first write ccw */
struct ccwbk *p_write_active_last; /* ptr to the last write ccw */
struct ccwbk *p_read_active_first; /* ptr to the first read ccw */
struct ccwbk *p_read_active_last; /* ptr to the last read ccw */
struct endccw *p_end_ccw; /*ptr to ending ccw */
struct ccwbk *p_claw_signal_blk; /* ptr to signal block */
__u32 write_free_count; /* number of free bufs for write */
struct net_device_stats stats; /* device status */
struct chbk channel[2]; /* Channel control blocks */
__u8 mtc_skipping;
int mtc_offset;
int mtc_logical_link;
void *p_mtc_envelope;
struct sk_buff *pk_skb; /* packing buffer */
int pk_cnt;
struct clawctl ctl_bk;
struct claw_env *p_env;
__u8 system_validate_comp;
__u8 release_pend;
__u8 checksum_received_ip_pkts;
__u8 buffs_alloc;
struct endccw end_ccw;
unsigned long tbusy;
};
/************************************************************/
/* define global constants */
/************************************************************/
#define CCWBK_SIZE sizeof(struct ccwbk)

View file

@ -0,0 +1,83 @@
/*
*
* linux/drivers/s390/net/ctcdbug.c ($Revision: 1.4 $)
*
* CTC / ESCON network driver - s390 dbf exploit.
*
* Copyright 2000,2003 IBM Corporation
*
* Author(s): Original Code written by
* Peter Tiedemann (ptiedem@de.ibm.com)
*
* $Revision: 1.4 $ $Date: 2004/08/04 10:11:59 $
*
* 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 "ctcdbug.h"
/**
* Debug Facility Stuff
*/
debug_info_t *ctc_dbf_setup = NULL;
debug_info_t *ctc_dbf_data = NULL;
debug_info_t *ctc_dbf_trace = NULL;
DEFINE_PER_CPU(char[256], ctc_dbf_txt_buf);
void
ctc_unregister_dbf_views(void)
{
if (ctc_dbf_setup)
debug_unregister(ctc_dbf_setup);
if (ctc_dbf_data)
debug_unregister(ctc_dbf_data);
if (ctc_dbf_trace)
debug_unregister(ctc_dbf_trace);
}
int
ctc_register_dbf_views(void)
{
ctc_dbf_setup = debug_register(CTC_DBF_SETUP_NAME,
CTC_DBF_SETUP_INDEX,
CTC_DBF_SETUP_NR_AREAS,
CTC_DBF_SETUP_LEN);
ctc_dbf_data = debug_register(CTC_DBF_DATA_NAME,
CTC_DBF_DATA_INDEX,
CTC_DBF_DATA_NR_AREAS,
CTC_DBF_DATA_LEN);
ctc_dbf_trace = debug_register(CTC_DBF_TRACE_NAME,
CTC_DBF_TRACE_INDEX,
CTC_DBF_TRACE_NR_AREAS,
CTC_DBF_TRACE_LEN);
if ((ctc_dbf_setup == NULL) || (ctc_dbf_data == NULL) ||
(ctc_dbf_trace == NULL)) {
ctc_unregister_dbf_views();
return -ENOMEM;
}
debug_register_view(ctc_dbf_setup, &debug_hex_ascii_view);
debug_set_level(ctc_dbf_setup, CTC_DBF_SETUP_LEVEL);
debug_register_view(ctc_dbf_data, &debug_hex_ascii_view);
debug_set_level(ctc_dbf_data, CTC_DBF_DATA_LEVEL);
debug_register_view(ctc_dbf_trace, &debug_hex_ascii_view);
debug_set_level(ctc_dbf_trace, CTC_DBF_TRACE_LEVEL);
return 0;
}

123
drivers/s390/net/ctcdbug.h Normal file
View file

@ -0,0 +1,123 @@
/*
*
* linux/drivers/s390/net/ctcdbug.h ($Revision: 1.4 $)
*
* CTC / ESCON network driver - s390 dbf exploit.
*
* Copyright 2000,2003 IBM Corporation
*
* Author(s): Original Code written by
* Peter Tiedemann (ptiedem@de.ibm.com)
*
* $Revision: 1.4 $ $Date: 2004/10/15 09:26:58 $
*
* 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 <asm/debug.h>
/**
* Debug Facility stuff
*/
#define CTC_DBF_SETUP_NAME "ctc_setup"
#define CTC_DBF_SETUP_LEN 16
#define CTC_DBF_SETUP_INDEX 3
#define CTC_DBF_SETUP_NR_AREAS 1
#define CTC_DBF_SETUP_LEVEL 3
#define CTC_DBF_DATA_NAME "ctc_data"
#define CTC_DBF_DATA_LEN 128
#define CTC_DBF_DATA_INDEX 3
#define CTC_DBF_DATA_NR_AREAS 1
#define CTC_DBF_DATA_LEVEL 2
#define CTC_DBF_TRACE_NAME "ctc_trace"
#define CTC_DBF_TRACE_LEN 16
#define CTC_DBF_TRACE_INDEX 2
#define CTC_DBF_TRACE_NR_AREAS 2
#define CTC_DBF_TRACE_LEVEL 3
#define DBF_TEXT(name,level,text) \
do { \
debug_text_event(ctc_dbf_##name,level,text); \
} while (0)
#define DBF_HEX(name,level,addr,len) \
do { \
debug_event(ctc_dbf_##name,level,(void*)(addr),len); \
} while (0)
DECLARE_PER_CPU(char[256], ctc_dbf_txt_buf);
extern debug_info_t *ctc_dbf_setup;
extern debug_info_t *ctc_dbf_data;
extern debug_info_t *ctc_dbf_trace;
#define DBF_TEXT_(name,level,text...) \
do { \
char* ctc_dbf_txt_buf = get_cpu_var(ctc_dbf_txt_buf); \
sprintf(ctc_dbf_txt_buf, text); \
debug_text_event(ctc_dbf_##name,level,ctc_dbf_txt_buf); \
put_cpu_var(ctc_dbf_txt_buf); \
} while (0)
#define DBF_SPRINTF(name,level,text...) \
do { \
debug_sprintf_event(ctc_dbf_trace, level, ##text ); \
debug_sprintf_event(ctc_dbf_trace, level, text ); \
} while (0)
int ctc_register_dbf_views(void);
void ctc_unregister_dbf_views(void);
/**
* some more debug stuff
*/
#define HEXDUMP16(importance,header,ptr) \
PRINT_##importance(header "%02x %02x %02x %02x %02x %02x %02x %02x " \
"%02x %02x %02x %02x %02x %02x %02x %02x\n", \
*(((char*)ptr)),*(((char*)ptr)+1),*(((char*)ptr)+2), \
*(((char*)ptr)+3),*(((char*)ptr)+4),*(((char*)ptr)+5), \
*(((char*)ptr)+6),*(((char*)ptr)+7),*(((char*)ptr)+8), \
*(((char*)ptr)+9),*(((char*)ptr)+10),*(((char*)ptr)+11), \
*(((char*)ptr)+12),*(((char*)ptr)+13), \
*(((char*)ptr)+14),*(((char*)ptr)+15)); \
PRINT_##importance(header "%02x %02x %02x %02x %02x %02x %02x %02x " \
"%02x %02x %02x %02x %02x %02x %02x %02x\n", \
*(((char*)ptr)+16),*(((char*)ptr)+17), \
*(((char*)ptr)+18),*(((char*)ptr)+19), \
*(((char*)ptr)+20),*(((char*)ptr)+21), \
*(((char*)ptr)+22),*(((char*)ptr)+23), \
*(((char*)ptr)+24),*(((char*)ptr)+25), \
*(((char*)ptr)+26),*(((char*)ptr)+27), \
*(((char*)ptr)+28),*(((char*)ptr)+29), \
*(((char*)ptr)+30),*(((char*)ptr)+31));
static inline void
hex_dump(unsigned char *buf, size_t len)
{
size_t i;
for (i = 0; i < len; i++) {
if (i && !(i % 16))
printk("\n");
printk("%02x ", *(buf + i));
}
printk("\n");
}

3304
drivers/s390/net/ctcmain.c Normal file

File diff suppressed because it is too large Load diff

1276
drivers/s390/net/ctctty.c Normal file

File diff suppressed because it is too large Load diff

37
drivers/s390/net/ctctty.h Normal file
View file

@ -0,0 +1,37 @@
/*
* $Id: ctctty.h,v 1.4 2003/09/18 08:01:10 mschwide Exp $
*
* CTC / ESCON network driver, tty interface.
*
* Copyright (C) 2001 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Fritz Elfert (elfert@de.ibm.com, felfert@millenux.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 _CTCTTY_H_
#define _CTCTTY_H_
#include <linux/skbuff.h>
#include <linux/netdevice.h>
extern int ctc_tty_register_netdev(struct net_device *);
extern void ctc_tty_unregister_netdev(struct net_device *);
extern void ctc_tty_netif_rx(struct sk_buff *);
extern int ctc_tty_init(void);
extern void ctc_tty_cleanup(void);
extern void ctc_tty_setcarrier(struct net_device *, int);
#endif

166
drivers/s390/net/cu3088.c Normal file
View file

@ -0,0 +1,166 @@
/*
* $Id: cu3088.c,v 1.34 2004/06/15 13:16:27 pavlic Exp $
*
* CTC / LCS ccw_device driver
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH, IBM Corporation
* Author(s): Arnd Bergmann <arndb@de.ibm.com>
* Cornelia Huck <cohuck@de.ibm.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/err.h>
#include <asm/ccwdev.h>
#include <asm/ccwgroup.h>
#include "cu3088.h"
const char *cu3088_type[] = {
"not a channel",
"CTC/A",
"ESCON channel",
"FICON channel",
"P390 LCS card",
"OSA LCS card",
"unknown channel type",
"unsupported channel type",
};
/* static definitions */
static struct ccw_device_id cu3088_ids[] = {
{ CCW_DEVICE(0x3088, 0x08), .driver_info = channel_type_parallel },
{ CCW_DEVICE(0x3088, 0x1f), .driver_info = channel_type_escon },
{ CCW_DEVICE(0x3088, 0x1e), .driver_info = channel_type_ficon },
{ CCW_DEVICE(0x3088, 0x01), .driver_info = channel_type_p390 },
{ CCW_DEVICE(0x3088, 0x60), .driver_info = channel_type_osa2 },
{ /* end of list */ }
};
static struct ccw_driver cu3088_driver;
struct device *cu3088_root_dev;
static ssize_t
group_write(struct device_driver *drv, const char *buf, size_t count)
{
const char *start, *end;
char bus_ids[2][BUS_ID_SIZE], *argv[2];
int i;
int ret;
struct ccwgroup_driver *cdrv;
cdrv = to_ccwgroupdrv(drv);
if (!cdrv)
return -EINVAL;
start = buf;
for (i=0; i<2; i++) {
static const char delim[] = {',', '\n'};
int len;
if (!(end = strchr(start, delim[i])))
return count;
len = min_t(ptrdiff_t, BUS_ID_SIZE, end - start + 1);
strlcpy (bus_ids[i], start, len);
argv[i] = bus_ids[i];
start = end + 1;
}
ret = ccwgroup_create(cu3088_root_dev, cdrv->driver_id,
&cu3088_driver, 2, argv);
return (ret == 0) ? count : ret;
}
static DRIVER_ATTR(group, 0200, NULL, group_write);
/* Register-unregister for ctc&lcs */
int
register_cu3088_discipline(struct ccwgroup_driver *dcp)
{
int rc;
if (!dcp)
return -EINVAL;
/* Register discipline.*/
rc = ccwgroup_driver_register(dcp);
if (rc)
return rc;
rc = driver_create_file(&dcp->driver, &driver_attr_group);
if (rc)
ccwgroup_driver_unregister(dcp);
return rc;
}
void
unregister_cu3088_discipline(struct ccwgroup_driver *dcp)
{
if (!dcp)
return;
driver_remove_file(&dcp->driver, &driver_attr_group);
ccwgroup_driver_unregister(dcp);
}
static struct ccw_driver cu3088_driver = {
.owner = THIS_MODULE,
.ids = cu3088_ids,
.name = "cu3088",
.probe = ccwgroup_probe_ccwdev,
.remove = ccwgroup_remove_ccwdev,
};
/* module setup */
static int __init
cu3088_init (void)
{
int rc;
cu3088_root_dev = s390_root_dev_register("cu3088");
if (IS_ERR(cu3088_root_dev))
return PTR_ERR(cu3088_root_dev);
rc = ccw_driver_register(&cu3088_driver);
if (rc)
s390_root_dev_unregister(cu3088_root_dev);
return rc;
}
static void __exit
cu3088_exit (void)
{
ccw_driver_unregister(&cu3088_driver);
s390_root_dev_unregister(cu3088_root_dev);
}
MODULE_DEVICE_TABLE(ccw,cu3088_ids);
MODULE_AUTHOR("Arnd Bergmann <[email protected]>");
MODULE_LICENSE("GPL");
module_init(cu3088_init);
module_exit(cu3088_exit);
EXPORT_SYMBOL_GPL(cu3088_type);
EXPORT_SYMBOL_GPL(register_cu3088_discipline);
EXPORT_SYMBOL_GPL(unregister_cu3088_discipline);

Some files were not shown because too many files have changed in this diff Show more