Commit fb6958a5 authored by Cornelia Huck's avatar Cornelia Huck Committed by Linus Torvalds

[PATCH] s390: multiple subchannel sets support

Add support for multiple subchannel sets.  Works with arbitrary devices in
subchannel set 1 and is transparent to device drivers.  Although currently
only two subchannel sets are available, this will work with the architectured
maximum number of subchannel sets as well.
Signed-off-by: default avatarCornelia Huck <cohuck@de.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 678a395b
/*
* drivers/s390/cio/blacklist.c
* S/390 common I/O routines -- blacklisting of specific devices
* $Revision: 1.35 $
* $Revision: 1.39 $
*
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
......@@ -35,10 +35,10 @@
* These can be single devices or ranges of devices
*/
/* 65536 bits to indicate if a devno is blacklisted or not */
/* 65536 bits for each set to indicate if a devno is blacklisted or not */
#define __BL_DEV_WORDS ((__MAX_SUBCHANNEL + (8*sizeof(long) - 1)) / \
(8*sizeof(long)))
static unsigned long bl_dev[__BL_DEV_WORDS];
static unsigned long bl_dev[__MAX_SSID + 1][__BL_DEV_WORDS];
typedef enum {add, free} range_action;
/*
......@@ -46,21 +46,23 @@ typedef enum {add, free} range_action;
* (Un-)blacklist the devices from-to
*/
static inline void
blacklist_range (range_action action, unsigned int from, unsigned int to)
blacklist_range (range_action action, unsigned int from, unsigned int to,
unsigned int ssid)
{
if (!to)
to = from;
if (from > to || to > __MAX_SUBCHANNEL) {
if (from > to || to > __MAX_SUBCHANNEL || ssid > __MAX_SSID) {
printk (KERN_WARNING "Invalid blacklist range "
"0x%04x to 0x%04x, skipping\n", from, to);
"0.%x.%04x to 0.%x.%04x, skipping\n",
ssid, from, ssid, to);
return;
}
for (; from <= to; from++) {
if (action == add)
set_bit (from, bl_dev);
set_bit (from, bl_dev[ssid]);
else
clear_bit (from, bl_dev);
clear_bit (from, bl_dev[ssid]);
}
}
......@@ -70,7 +72,7 @@ blacklist_range (range_action action, unsigned int from, unsigned int to)
* Shamelessly grabbed from dasd_devmap.c.
*/
static inline int
blacklist_busid(char **str, int *id0, int *id1, int *devno)
blacklist_busid(char **str, int *id0, int *ssid, int *devno)
{
int val, old_style;
char *sav;
......@@ -87,7 +89,7 @@ blacklist_busid(char **str, int *id0, int *id1, int *devno)
goto confused;
val = simple_strtoul(*str, str, 16);
if (old_style || (*str)[0] != '.') {
*id0 = *id1 = 0;
*id0 = *ssid = 0;
if (val < 0 || val > 0xffff)
goto confused;
*devno = val;
......@@ -106,7 +108,7 @@ blacklist_busid(char **str, int *id0, int *id1, int *devno)
val = simple_strtoul(*str, str, 16);
if (val < 0 || val > 0xff || (*str)++[0] != '.')
goto confused;
*id1 = val;
*ssid = val;
if (!isxdigit((*str)[0])) /* We require at least one hex digit */
goto confused;
val = simple_strtoul(*str, str, 16);
......@@ -126,7 +128,7 @@ confused:
static inline int
blacklist_parse_parameters (char *str, range_action action)
{
unsigned int from, to, from_id0, to_id0, from_id1, to_id1;
unsigned int from, to, from_id0, to_id0, from_ssid, to_ssid;
while (*str != 0 && *str != '\n') {
range_action ra = action;
......@@ -143,23 +145,25 @@ blacklist_parse_parameters (char *str, range_action action)
*/
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_SUBCHANNEL;
int j;
str += 3;
for (j=0; j <= __MAX_SSID; j++)
blacklist_range(ra, 0, __MAX_SUBCHANNEL, j);
} else {
int rc;
rc = blacklist_busid(&str, &from_id0,
&from_id1, &from);
&from_ssid, &from);
if (rc)
continue;
to = from;
to_id0 = from_id0;
to_id1 = from_id1;
to_ssid = from_ssid;
if (*str == '-') {
str++;
rc = blacklist_busid(&str, &to_id0,
&to_id1, &to);
&to_ssid, &to);
if (rc)
continue;
}
......@@ -169,18 +173,19 @@ blacklist_parse_parameters (char *str, range_action action)
strsep(&str, ",\n"));
continue;
}
if ((from_id0 != to_id0) || (from_id1 != to_id1)) {
if ((from_id0 != to_id0) ||
(from_ssid != to_ssid)) {
printk(KERN_WARNING "invalid cio_ignore range "
"%x.%x.%04x-%x.%x.%04x\n",
from_id0, from_id1, from,
to_id0, to_id1, to);
from_id0, from_ssid, from,
to_id0, to_ssid, to);
continue;
}
pr_debug("blacklist_setup: adding range "
"from %x.%x.%04x to %x.%x.%04x\n",
from_id0, from_ssid, from, to_id0, to_ssid, to);
blacklist_range (ra, from, to, to_ssid);
}
/* 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;
}
......@@ -214,9 +219,9 @@ __setup ("cio_ignore=", blacklist_setup);
* Used by validate_subchannel()
*/
int
is_blacklisted (int devno)
is_blacklisted (int ssid, int devno)
{
return test_bit (devno, bl_dev);
return test_bit (devno, bl_dev[ssid]);
}
#ifdef CONFIG_PROC_FS
......@@ -283,6 +288,7 @@ blacklist_parse_proc_parameters (char *buf)
/* Iterator struct for all devices. */
struct ccwdev_iter {
int devno;
int ssid;
int in_range;
};
......@@ -291,13 +297,14 @@ cio_ignore_proc_seq_start(struct seq_file *s, loff_t *offset)
{
struct ccwdev_iter *iter;
if (*offset > __MAX_SUBCHANNEL)
if (*offset >= (__MAX_SUBCHANNEL + 1) * (__MAX_SSID + 1))
return NULL;
iter = kmalloc(sizeof(struct ccwdev_iter), GFP_KERNEL);
if (!iter)
return ERR_PTR(-ENOMEM);
memset(iter, 0, sizeof(struct ccwdev_iter));
iter->devno = *offset;
iter->ssid = *offset / (__MAX_SUBCHANNEL + 1);
iter->devno = *offset % (__MAX_SUBCHANNEL + 1);
return iter;
}
......@@ -313,10 +320,16 @@ cio_ignore_proc_seq_next(struct seq_file *s, void *it, loff_t *offset)
{
struct ccwdev_iter *iter;
if (*offset > __MAX_SUBCHANNEL)
if (*offset >= (__MAX_SUBCHANNEL + 1) * (__MAX_SSID + 1))
return NULL;
iter = (struct ccwdev_iter *)it;
iter->devno++;
if (iter->devno == __MAX_SUBCHANNEL) {
iter->devno = 0;
iter->ssid++;
if (iter->ssid > __MAX_SSID)
return NULL;
} else
iter->devno++;
(*offset)++;
return iter;
}
......@@ -327,23 +340,24 @@ cio_ignore_proc_seq_show(struct seq_file *s, void *it)
struct ccwdev_iter *iter;
iter = (struct ccwdev_iter *)it;
if (!is_blacklisted(iter->devno))
if (!is_blacklisted(iter->ssid, iter->devno))
/* Not blacklisted, nothing to output. */
return 0;
if (!iter->in_range) {
/* First device in range. */
if ((iter->devno == __MAX_SUBCHANNEL) ||
!is_blacklisted(iter->devno + 1))
!is_blacklisted(iter->ssid, iter->devno + 1))
/* Singular device. */
return seq_printf(s, "0.0.%04x\n", iter->devno);
return seq_printf(s, "0.%x.%04x\n",
iter->ssid, iter->devno);
iter->in_range = 1;
return seq_printf(s, "0.0.%04x-", iter->devno);
return seq_printf(s, "0.%x.%04x-", iter->ssid, iter->devno);
}
if ((iter->devno == __MAX_SUBCHANNEL) ||
!is_blacklisted(iter->devno + 1)) {
!is_blacklisted(iter->ssid, iter->devno + 1)) {
/* Last device in range. */
iter->in_range = 0;
return seq_printf(s, "0.0.%04x\n", iter->devno);
return seq_printf(s, "0.%x.%04x\n", iter->ssid, iter->devno);
}
return 0;
}
......
#ifndef S390_BLACKLIST_H
#define S390_BLACKLIST_H
extern int is_blacklisted (int devno);
extern int is_blacklisted (int ssid, int devno);
#endif
/*
* drivers/s390/cio/chsc.c
* S/390 common I/O routines -- channel subsystem call
* $Revision: 1.120 $
* $Revision: 1.126 $
*
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
......@@ -75,7 +75,9 @@ chsc_get_sch_desc_irq(struct subchannel *sch, void *page)
struct {
struct chsc_header request;
u16 reserved1;
u16 reserved1a:10;
u16 ssid:2;
u16 reserved1b:4;
u16 f_sch; /* first subchannel */
u16 reserved2;
u16 l_sch; /* last subchannel */
......@@ -102,6 +104,7 @@ chsc_get_sch_desc_irq(struct subchannel *sch, void *page)
.code = 0x0004,
};
ssd_area->ssid = sch->schid.ssid;
ssd_area->f_sch = sch->schid.sch_no;
ssd_area->l_sch = sch->schid.sch_no;
......@@ -145,8 +148,8 @@ chsc_get_sch_desc_irq(struct subchannel *sch, void *page)
*/
if (ssd_area->st > 3) { /* uhm, that looks strange... */
CIO_CRW_EVENT(0, "Strange subchannel type %d"
" for sch %04x\n", ssd_area->st,
sch->schid.sch_no);
" for sch 0.%x.%04x\n", ssd_area->st,
sch->schid.ssid, sch->schid.sch_no);
/*
* There may have been a new subchannel type defined in the
* time since this code was written; since we don't know which
......@@ -155,8 +158,9 @@ chsc_get_sch_desc_irq(struct subchannel *sch, void *page)
return 0;
} else {
const char *type[4] = {"I/O", "chsc", "message", "ADM"};
CIO_CRW_EVENT(6, "ssd: sch %04x is %s subchannel\n",
sch->schid.sch_no, type[ssd_area->st]);
CIO_CRW_EVENT(6, "ssd: sch 0.%x.%04x is %s subchannel\n",
sch->schid.ssid, sch->schid.sch_no,
type[ssd_area->st]);
sch->ssd_info.valid = 1;
sch->ssd_info.type = ssd_area->st;
......@@ -364,7 +368,7 @@ s390_process_res_acc_new_sch(struct subchannel_id schid)
* that beast may be on we'll have to do a stsch
* on all devices, grr...
*/
if (stsch(schid, &schib))
if (stsch_err(schid, &schib))
/* We're through */
return need_rescan ? -EAGAIN : -ENXIO;
......@@ -818,7 +822,7 @@ __s390_vary_chpid_on(struct subchannel_id schid, void *data)
put_device(&sch->dev);
return 0;
}
if (stsch(schid, &schib))
if (stsch_err(schid, &schib))
/* We're through */
return -ENXIO;
/* Put it on the slow path. */
......@@ -1078,6 +1082,54 @@ chsc_alloc_sei_area(void)
return (sei_page ? 0 : -ENOMEM);
}
int __init
chsc_enable_facility(int operation_code)
{
int ret;
struct {
struct chsc_header request;
u8 reserved1:4;
u8 format:4;
u8 reserved2;
u16 operation_code;
u32 reserved3;
u32 reserved4;
u32 operation_data_area[252];
struct chsc_header response;
u32 reserved5:4;
u32 format2:4;
u32 reserved6:24;
} *sda_area;
sda_area = (void *)get_zeroed_page(GFP_KERNEL|GFP_DMA);
if (!sda_area)
return -ENOMEM;
sda_area->request = (struct chsc_header) {
.length = 0x0400,
.code = 0x0031,
};
sda_area->operation_code = operation_code;
ret = chsc(sda_area);
if (ret > 0) {
ret = (ret == 3) ? -ENODEV : -EBUSY;
goto out;
}
switch (sda_area->response.code) {
case 0x0003: /* invalid request block */
case 0x0007:
ret = -EINVAL;
break;
case 0x0004: /* command not provided */
case 0x0101: /* facility not provided */
ret = -EOPNOTSUPP;
break;
}
out:
free_page((unsigned long)sda_area);
return ret;
}
subsys_initcall(chsc_alloc_sei_area);
struct css_general_char css_general_characteristics;
......
......@@ -5,6 +5,8 @@
#define CHSC_SEI_ACC_LINKADDR 2
#define CHSC_SEI_ACC_FULLLINKADDR 3
#define CHSC_SDA_OC_MSS 0x2
struct chsc_header {
u16 length;
u16 code;
......@@ -64,6 +66,8 @@ extern int css_characteristics_avail;
extern void *chsc_get_chp_desc(struct subchannel*, int);
extern int chsc_enable_facility(int);
#define to_channelpath(dev) container_of(dev, struct channel_path, dev)
#endif
/*
* drivers/s390/cio/cio.c
* S/390 common I/O routines -- low level i/o calls
* $Revision: 1.135 $
* $Revision: 1.138 $
*
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
......@@ -166,7 +166,8 @@ cio_start_handle_notoper(struct subchannel *sch, __u8 lpm)
stsch (sch->schid, &sch->schib);
CIO_MSG_EVENT(0, "cio_start: 'not oper' status for "
"subchannel %04x!\n", sch->schid.sch_no);
"subchannel 0.%x.%04x!\n", sch->schid.ssid,
sch->schid.sch_no);
sprintf(dbf_text, "no%s", sch->dev.bus_id);
CIO_TRACE_EVENT(0, dbf_text);
CIO_HEX_EVENT(0, &sch->schib, sizeof (struct schib));
......@@ -522,15 +523,18 @@ cio_validate_subchannel (struct subchannel *sch, struct subchannel_id schid)
spin_lock_init(&sch->lock);
/* Set a name for the subchannel */
snprintf (sch->dev.bus_id, BUS_ID_SIZE, "0.0.%04x", schid.sch_no);
snprintf (sch->dev.bus_id, BUS_ID_SIZE, "0.%x.%04x", schid.ssid,
schid.sch_no);
/*
* The first subchannel that is not-operational (ccode==3)
* indicates that there aren't any more devices available.
* If stsch gets an exception, it means the current subchannel set
* is not valid.
*/
ccode = stsch (schid, &sch->schib);
ccode = stsch_err (schid, &sch->schib);
if (ccode)
return -ENXIO;
return (ccode == 3) ? -ENXIO : ccode;
sch->schid = schid;
/* Copy subchannel type from path management control word. */
......@@ -541,9 +545,9 @@ cio_validate_subchannel (struct subchannel *sch, struct subchannel_id schid)
*/
if (sch->st != 0) {
CIO_DEBUG(KERN_INFO, 0,
"Subchannel %04X reports "
"Subchannel 0.%x.%04x reports "
"non-I/O subchannel type %04X\n",
sch->schid.sch_no, sch->st);
sch->schid.ssid, sch->schid.sch_no, sch->st);
/* We stop here for non-io subchannels. */
return sch->st;
}
......@@ -554,26 +558,29 @@ cio_validate_subchannel (struct subchannel *sch, struct subchannel_id schid)
return -ENODEV;
/* Devno is valid. */
if (is_blacklisted (sch->schib.pmcw.dev)) {
if (is_blacklisted (sch->schid.ssid, 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);
"at devno %04X, subchannel set %x\n",
sch->schib.pmcw.dev, sch->schid.ssid);
return -ENODEV;
}
sch->opm = 0xff;
chsc_validate_chpids(sch);
if (!cio_is_console(sch->schid))
chsc_validate_chpids(sch);
sch->lpm = sch->schib.pmcw.pim &
sch->schib.pmcw.pam &
sch->schib.pmcw.pom &
sch->opm;
CIO_DEBUG(KERN_INFO, 0,
"Detected device %04X on subchannel %04X"
"Detected device %04x on subchannel 0.%x.%04X"
" - PIM = %02X, PAM = %02X, POM = %02X\n",
sch->schib.pmcw.dev, sch->schid.sch_no, sch->schib.pmcw.pim,
sch->schib.pmcw.dev, sch->schid.ssid,
sch->schid.sch_no, sch->schib.pmcw.pim,
sch->schib.pmcw.pam, sch->schib.pmcw.pom);
/*
......@@ -693,7 +700,7 @@ wait_cons_dev (void)
static int
cio_test_for_console(struct subchannel_id schid, void *data)
{
if (stsch(schid, &console_subchannel.schib) != 0)
if (stsch_err(schid, &console_subchannel.schib) != 0)
return -ENXIO;
if (console_subchannel.schib.pmcw.dnv &&
console_subchannel.schib.pmcw.dev ==
......@@ -841,7 +848,7 @@ __shutdown_subchannel_easy(struct subchannel_id schid, void *data)
{
struct schib schib;
if (stsch(schid, &schib))
if (stsch_err(schid, &schib))
return -ENXIO;
if (!schib.pmcw.ena)
return 0;
......
/*
* drivers/s390/cio/css.c
* driver for channel subsystem
* $Revision: 1.85 $
* $Revision: 1.93 $
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
......@@ -23,6 +23,7 @@
int need_rescan = 0;
int css_init_done = 0;
static int max_ssid = 0;
struct channel_subsystem *css[__MAX_CSSID + 1];
......@@ -37,10 +38,13 @@ for_each_subchannel(int(*fn)(struct subchannel_id, void *), void *data)
init_subchannel_id(&schid);
ret = -ENODEV;
do {
ret = fn(schid, data);
if (ret)
break;
} while (schid.sch_no++ < __MAX_SUBCHANNEL);
do {
ret = fn(schid, data);
if (ret)
break;
} while (schid.sch_no++ < __MAX_SUBCHANNEL);
schid.sch_no = 0;
} while (schid.ssid++ < max_ssid);
return ret;
}
......@@ -205,8 +209,8 @@ css_evaluate_subchannel(struct subchannel_id schid, int slow)
return -EAGAIN; /* Will be done on the slow path. */
}
event = css_get_subchannel_status(sch, schid);
CIO_MSG_EVENT(4, "Evaluating schid %04x, event %d, %s, %s path.\n",
schid.sch_no, event,
CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n",
schid.ssid, schid.sch_no, event,
sch?(disc?"disconnected":"normal"):"unknown",
slow?"slow":"fast");
switch (event) {
......@@ -352,19 +356,23 @@ css_reiterate_subchannels(void)
* Called from the machine check handler for subchannel report words.
*/
int
css_process_crw(int irq)
css_process_crw(int rsid1, int rsid2)
{
int ret;
struct subchannel_id mchk_schid;
CIO_CRW_EVENT(2, "source is subchannel %04X\n", irq);
CIO_CRW_EVENT(2, "source is subchannel %04X, subsystem id %x\n",
rsid1, rsid2);
if (need_rescan)
/* We need to iterate all subchannels anyway. */
return -EAGAIN;
init_subchannel_id(&mchk_schid);
mchk_schid.sch_no = irq;
mchk_schid.sch_no = rsid1;
if (rsid2 != 0)
mchk_schid.ssid = (rsid2 >> 8) & 3;
/*
* 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
......@@ -465,12 +473,23 @@ init_channel_subsystem (void)
if ((ret = bus_register(&css_bus_type)))
goto out;
/* Try to enable MSS. */
ret = chsc_enable_facility(CHSC_SDA_OC_MSS);
switch (ret) {
case 0: /* Success. */
max_ssid = __MAX_SSID;
break;
case -ENOMEM:
goto out_bus;
default:
max_ssid = 0;
}
/* Setup css structure. */
for (i = 0; i <= __MAX_CSSID; i++) {
css[i] = kmalloc(sizeof(struct channel_subsystem), GFP_KERNEL);
if (!css[i]) {
ret = -ENOMEM;
goto out_bus;
goto out_unregister;
}
setup_css(i);
ret = device_register(&css[i]->device);
......@@ -485,11 +504,12 @@ init_channel_subsystem (void)
return 0;
out_free:
kfree(css[i]);
out_bus:
out_unregister:
while (i > 0) {
i--;
device_unregister(&css[i]->device);
}
out_bus:
bus_unregister(&css_bus_type);
out:
return ret;
......
......@@ -77,6 +77,7 @@ struct ccw_device_private {
unsigned long registered;
__u16 devno; /* device number */
__u16 sch_no; /* subchannel number */
__u8 ssid; /* subchannel set id */
__u8 imask; /* lpm mask for SNID/SID/SPGID */
int iretry; /* retry counter SNID/SID/SPGID */
struct {
......@@ -135,6 +136,7 @@ extern int css_init_done;
extern int for_each_subchannel(int(*fn)(struct subchannel_id, void *), void *);
#define __MAX_SUBCHANNEL 65535
#define __MAX_SSID 3
#define __MAX_CHPID 255
#define __MAX_CSSID 0
......
......@@ -535,7 +535,8 @@ ccw_device_register(struct ccw_device *cdev)
}
struct match_data {
unsigned int devno;
unsigned int devno;
unsigned int ssid;
struct ccw_device * sibling;
};
......@@ -548,6 +549,7 @@ match_devno(struct device * dev, void * data)
cdev = to_ccwdev(dev);
if ((cdev->private->state == DEV_STATE_DISCONNECTED) &&
(cdev->private->devno == d->devno) &&
(cdev->private->ssid == d->ssid) &&
(cdev != d->sibling)) {
cdev->private->state = DEV_STATE_NOT_OPER;
return 1;
......@@ -556,11 +558,13 @@ match_devno(struct device * dev, void * data)
}
static struct ccw_device *
get_disc_ccwdev_by_devno(unsigned int devno, struct ccw_device *sibling)
get_disc_ccwdev_by_devno(unsigned int devno, unsigned int ssid,
struct ccw_device *sibling)
{
struct device *dev;
struct match_data data = {
.devno = devno,
.devno = devno,
.ssid = ssid,
.sibling = sibling,
};
......@@ -616,7 +620,7 @@ ccw_device_do_unreg_rereg(void *data)
need_rename = 1;
other_cdev = get_disc_ccwdev_by_devno(sch->schib.pmcw.dev,
cdev);
sch->schid.ssid, cdev);
if (other_cdev) {
struct subchannel *other_sch;
......@@ -639,8 +643,8 @@ ccw_device_do_unreg_rereg(void *data)
if (test_and_clear_bit(1, &cdev->private->registered))
device_del(&cdev->dev);
if (need_rename)
snprintf (cdev->dev.bus_id, BUS_ID_SIZE, "0.0.%04x",
sch->schib.pmcw.dev);