Commit c820de39 authored by Cornelia Huck's avatar Cornelia Huck Committed by Heiko Carstens
Browse files

[S390] cio: Rework css driver.



Rework the css driver methods to provide sane callbacks for
subchannels of all types.

As a bonus, this cleans up and simplyfies the machine check
handling for I/O subchannels a lot.
Signed-off-by: default avatarCornelia Huck <cornelia.huck@de.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
Signed-off-by: default avatarHeiko Carstens <heiko.carstens@de.ibm.com>
parent 7e9db9ea
......@@ -496,6 +496,26 @@ void chp_process_crw(int id, int status)
chsc_chp_offline(chpid);
}
int chp_ssd_get_mask(struct chsc_ssd_info *ssd, struct res_acc_data *data)
{
int i;
int mask;
for (i = 0; i < 8; i++) {
mask = 0x80 >> i;
if (!(ssd->path_mask & mask))
continue;
if (!chp_id_is_equal(&ssd->chpid[i], &data->chpid))
continue;
if ((ssd->fla_valid_mask & mask) &&
((ssd->fla[i] & data->fla_mask) != data->fla))
continue;
return mask;
}
return 0;
}
EXPORT_SYMBOL_GPL(chp_ssd_get_mask);
static inline int info_bit_num(struct chp_id id)
{
return id.id + id.cssid * (__MAX_CHPID + 1);
......
......@@ -19,6 +19,17 @@
#define CHP_STATUS_RESERVED 2
#define CHP_STATUS_NOT_RECOGNIZED 3
#define CHP_ONLINE 0
#define CHP_OFFLINE 1
#define CHP_VARY_ON 2
#define CHP_VARY_OFF 3
struct res_acc_data {
struct chp_id chpid;
u32 fla_mask;
u16 fla;
};
static inline int chp_test_bit(u8 *bitmap, int num)
{
int byte = num >> 3;
......@@ -50,5 +61,5 @@ int chp_new(struct chp_id chpid);
void chp_cfg_schedule(struct chp_id chpid, int configure);
void chp_cfg_cancel_deconfigure(struct chp_id chpid);
int chp_info_get_status(struct chp_id chpid);
int chp_ssd_get_mask(struct chsc_ssd_info *, struct res_acc_data *);
#endif /* S390_CHP_H */
......@@ -2,8 +2,7 @@
* drivers/s390/cio/chsc.c
* S/390 common I/O routines -- channel subsystem call
*
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Copyright IBM Corp. 1999,2008
* Author(s): Ingo Adlung (adlung@de.ibm.com)
* Cornelia Huck (cornelia.huck@de.ibm.com)
* Arnd Bergmann (arndb@de.ibm.com)
......@@ -127,77 +126,12 @@ out_free:
return ret;
}
static int check_for_io_on_path(struct subchannel *sch, int mask)
{
int cc;
cc = stsch(sch->schid, &sch->schib);
if (cc)
return 0;
if (sch->schib.scsw.actl && sch->schib.pmcw.lpum == mask)
return 1;
return 0;
}
static void terminate_internal_io(struct subchannel *sch)
{
if (cio_clear(sch)) {
/* Recheck device in case clear failed. */
sch->lpm = 0;
if (device_trigger_verify(sch) != 0)
css_schedule_eval(sch->schid);
return;
}
/* Request retry of internal operation. */
device_set_intretry(sch);
/* Call handler. */
if (sch->driver && sch->driver->termination)
sch->driver->termination(sch);
}
static int s390_subchannel_remove_chpid(struct subchannel *sch, void *data)
{
int j;
int mask;
struct chp_id *chpid = data;
struct schib schib;
for (j = 0; j < 8; j++) {
mask = 0x80 >> j;
if ((sch->schib.pmcw.pim & mask) &&
(sch->schib.pmcw.chpid[j] == chpid->id))
break;
}
if (j >= 8)
return 0;
spin_lock_irq(sch->lock);
stsch(sch->schid, &schib);
if (!css_sch_is_valid(&schib))
goto out_unreg;
memcpy(&sch->schib, &schib, sizeof(struct schib));
/* Check for single path devices. */
if (sch->schib.pmcw.pim == 0x80)
goto out_unreg;
if (check_for_io_on_path(sch, mask)) {
if (device_is_online(sch))
device_kill_io(sch);
else {
terminate_internal_io(sch);
/* Re-start path verification. */
if (sch->driver && sch->driver->verify)
sch->driver->verify(sch);
}
} else {
/* trigger path verification. */
if (sch->driver && sch->driver->verify)
sch->driver->verify(sch);
else if (sch->lpm == mask)
if (sch->driver && sch->driver->chp_event)
if (sch->driver->chp_event(sch, data, CHP_OFFLINE) != 0)
goto out_unreg;
}
spin_unlock_irq(sch->lock);
return 0;
......@@ -242,53 +176,11 @@ static int s390_process_res_acc_new_sch(struct subchannel_id schid, void *data)
return 0;
}
struct res_acc_data {
struct chp_id chpid;
u32 fla_mask;
u16 fla;
};
static int get_res_chpid_mask(struct chsc_ssd_info *ssd,
struct res_acc_data *data)
{
int i;
int mask;
for (i = 0; i < 8; i++) {
mask = 0x80 >> i;
if (!(ssd->path_mask & mask))
continue;
if (!chp_id_is_equal(&ssd->chpid[i], &data->chpid))
continue;
if ((ssd->fla_valid_mask & mask) &&
((ssd->fla[i] & data->fla_mask) != data->fla))
continue;
return mask;
}
return 0;
}
static int __s390_process_res_acc(struct subchannel *sch, void *data)
{
int chp_mask, old_lpm;
struct res_acc_data *res_data = data;
spin_lock_irq(sch->lock);
chp_mask = get_res_chpid_mask(&sch->ssd_info, res_data);
if (chp_mask == 0)
goto out;
if (stsch(sch->schid, &sch->schib))
goto out;
old_lpm = sch->lpm;
sch->lpm = ((sch->schib.pmcw.pim &
sch->schib.pmcw.pam &
sch->schib.pmcw.pom)
| chp_mask) & sch->opm;
if (!old_lpm && sch->lpm)
device_trigger_reprobe(sch);
else if (sch->driver && sch->driver->verify)
sch->driver->verify(sch);
out:
if (sch->driver && sch->driver->chp_event)
sch->driver->chp_event(sch, data, CHP_ONLINE);
spin_unlock_irq(sch->lock);
return 0;
......@@ -509,114 +401,36 @@ void chsc_process_crw(void)
} while (sei_area->flags & 0x80);
}
static int __chp_add_new_sch(struct subchannel_id schid, void *data)
{
struct schib schib;
if (stsch_err(schid, &schib))
/* We're through */
return -ENXIO;
/* Put it on the slow path. */
css_schedule_eval(schid);
return 0;
}
static int __chp_add(struct subchannel *sch, void *data)
{
int i, mask;
struct chp_id *chpid = data;
spin_lock_irq(sch->lock);
for (i=0; i<8; i++) {
mask = 0x80 >> i;
if ((sch->schib.pmcw.pim & mask) &&
(sch->schib.pmcw.chpid[i] == chpid->id))
break;
}
if (i==8) {
spin_unlock_irq(sch->lock);
return 0;
}
if (stsch(sch->schid, &sch->schib)) {
spin_unlock_irq(sch->lock);
css_schedule_eval(sch->schid);
return 0;
}
sch->lpm = ((sch->schib.pmcw.pim &
sch->schib.pmcw.pam &
sch->schib.pmcw.pom)
| mask) & sch->opm;
if (sch->driver && sch->driver->verify)
sch->driver->verify(sch);
spin_unlock_irq(sch->lock);
return 0;
}
void chsc_chp_online(struct chp_id chpid)
{
char dbf_txt[15];
struct res_acc_data res_data;
sprintf(dbf_txt, "cadd%x.%02x", chpid.cssid, chpid.id);
CIO_TRACE_EVENT(2, dbf_txt);
if (chp_get_status(chpid) != 0) {
memset(&res_data, 0, sizeof(struct res_acc_data));
res_data.chpid = chpid;
/* Wait until previous actions have settled. */
css_wait_for_slow_path();
for_each_subchannel_staged(__chp_add, __chp_add_new_sch,
&chpid);
for_each_subchannel_staged(__s390_process_res_acc, NULL,
&res_data);
}
}
static void __s390_subchannel_vary_chpid(struct subchannel *sch,
struct chp_id chpid, int on)
{
int chp, old_lpm;
int mask;
unsigned long flags;
struct res_acc_data res_data;
memset(&res_data, 0, sizeof(struct res_acc_data));
res_data.chpid = chpid;
spin_lock_irqsave(sch->lock, flags);
old_lpm = sch->lpm;
for (chp = 0; chp < 8; chp++) {
mask = 0x80 >> chp;
if (!(sch->ssd_info.path_mask & mask))
continue;
if (!chp_id_is_equal(&sch->ssd_info.chpid[chp], &chpid))
continue;
if (on) {
sch->opm |= mask;
sch->lpm |= mask;
if (!old_lpm)
device_trigger_reprobe(sch);
else if (sch->driver && sch->driver->verify)
sch->driver->verify(sch);
break;
}
sch->opm &= ~mask;
sch->lpm &= ~mask;
if (check_for_io_on_path(sch, mask)) {
if (device_is_online(sch))
/* Path verification is done after killing. */
device_kill_io(sch);
else {
/* Kill and retry internal I/O. */
terminate_internal_io(sch);
/* Re-start path verification. */
if (sch->driver && sch->driver->verify)
sch->driver->verify(sch);
}
} else if (!sch->lpm) {
if (device_trigger_verify(sch) != 0)
css_schedule_eval(sch->schid);
} else if (sch->driver && sch->driver->verify)
sch->driver->verify(sch);
break;
}
if (sch->driver && sch->driver->chp_event)
sch->driver->chp_event(sch, &res_data,
on ? CHP_VARY_ON : CHP_VARY_OFF);
spin_unlock_irqrestore(sch->lock, flags);
}
......
......@@ -564,6 +564,7 @@ int cio_validate_subchannel(struct subchannel *sch, struct subchannel_id schid)
}
/* Copy subchannel type from path management control word. */
sch->st = sch->schib.pmcw.st;
switch (sch->st) {
case SUBCHANNEL_TYPE_IO:
err = cio_validate_io_subchannel(sch);
......
......@@ -283,7 +283,7 @@ static int css_register_subchannel(struct subchannel *sch)
return ret;
}
static int css_probe_device(struct subchannel_id schid)
int css_probe_device(struct subchannel_id schid)
{
int ret;
struct subchannel *sch;
......@@ -330,112 +330,6 @@ int css_sch_is_valid(struct schib *schib)
}
EXPORT_SYMBOL_GPL(css_sch_is_valid);
static int css_get_subchannel_status(struct subchannel *sch)
{
struct schib schib;
if (stsch(sch->schid, &schib))
return CIO_GONE;
if (!css_sch_is_valid(&schib))
return CIO_GONE;
if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev))
return CIO_REVALIDATE;
if (!sch->lpm)
return CIO_NO_PATH;
return CIO_OPER;
}
static int css_evaluate_known_subchannel(struct subchannel *sch, int slow)
{
int event, ret, disc;
unsigned long flags;
enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE } action;
spin_lock_irqsave(sch->lock, flags);
disc = device_is_disconnected(sch);
if (disc && slow) {
/* Disconnected devices are evaluated directly only.*/
spin_unlock_irqrestore(sch->lock, flags);
return 0;
}
/* No interrupt after machine check - kill pending timers. */
device_kill_pending_timer(sch);
if (!disc && !slow) {
/* Non-disconnected devices are evaluated on the slow path. */
spin_unlock_irqrestore(sch->lock, flags);
return -EAGAIN;
}
event = css_get_subchannel_status(sch);
CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n",
sch->schid.ssid, sch->schid.sch_no, event,
disc ? "disconnected" : "normal",
slow ? "slow" : "fast");
/* Analyze subchannel status. */
action = NONE;
switch (event) {
case CIO_NO_PATH:
if (disc) {
/* Check if paths have become available. */
action = REPROBE;
break;
}
/* fall through */
case CIO_GONE:
/* Prevent unwanted effects when opening lock. */
cio_disable_subchannel(sch);
device_set_disconnected(sch);
/* Ask driver what to do with device. */
action = UNREGISTER;
if (sch->driver && sch->driver->notify) {
spin_unlock_irqrestore(sch->lock, flags);
ret = sch->driver->notify(sch, event);
spin_lock_irqsave(sch->lock, flags);
if (ret)
action = NONE;
}
break;
case CIO_REVALIDATE:
/* Device will be removed, so no notify necessary. */
if (disc)
/* Reprobe because immediate unregister might block. */
action = REPROBE;
else
action = UNREGISTER_PROBE;
break;
case CIO_OPER:
if (disc)
/* Get device operational again. */
action = REPROBE;
break;
}
/* Perform action. */
ret = 0;
switch (action) {
case UNREGISTER:
case UNREGISTER_PROBE:
/* Unregister device (will use subchannel lock). */
spin_unlock_irqrestore(sch->lock, flags);
css_sch_device_unregister(sch);
spin_lock_irqsave(sch->lock, flags);
/* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0;
cio_modify(sch);
break;
case REPROBE:
device_trigger_reprobe(sch);
break;
default:
break;
}
spin_unlock_irqrestore(sch->lock, flags);
/* Probe if necessary. */
if (action == UNREGISTER_PROBE)
ret = css_probe_device(sch->schid);
return ret;
}
static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
{
struct schib schib;
......@@ -454,6 +348,21 @@ static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
return css_probe_device(schid);
}
static int css_evaluate_known_subchannel(struct subchannel *sch, int slow)
{
int ret = 0;
if (sch->driver) {
if (sch->driver->sch_event)
ret = sch->driver->sch_event(sch, slow);
else
dev_dbg(&sch->dev,
"Got subchannel machine check but "
"no sch_event handler provided.\n");
}
return ret;
}
static void css_evaluate_subchannel(struct subchannel_id schid, int slow)
{
struct subchannel *sch;
......
......@@ -58,18 +58,27 @@ struct pgid {
__u32 tod_high; /* high word TOD clock */
} __attribute__ ((packed));
/*
* A css driver handles all subchannels of one type.
*/
struct subchannel;
/**
* struct css_driver - device driver for subchannels
* @owner: owning module
* @subchannel_type: subchannel type supported by this driver
* @drv: embedded device driver structure
* @irq: called on interrupts
* @chp_event: called for events affecting a channel path
* @sch_event: called for events affecting the subchannel
* @probe: function called on probe
* @remove: function called on remove
* @shutdown: called at device shutdown
* @name: name of the device driver
*/
struct css_driver {
struct module *owner;
unsigned int subchannel_type;
struct device_driver drv;
void (*irq)(struct subchannel *);
int (*notify)(struct subchannel *, int);
void (*verify)(struct subchannel *);
void (*termination)(struct subchannel *);
int (*chp_event)(struct subchannel *, void *, int);
int (*sch_event)(struct subchannel *, int);
int (*probe)(struct subchannel *);
int (*remove)(struct subchannel *);
void (*shutdown)(struct subchannel *);
......@@ -87,7 +96,8 @@ extern int css_driver_register(struct css_driver *);
extern void css_driver_unregister(struct css_driver *);
extern void css_sch_device_unregister(struct subchannel *);
extern struct subchannel * get_subchannel_by_schid(struct subchannel_id);
extern int css_probe_device(struct subchannel_id);
extern struct subchannel *get_subchannel_by_schid(struct subchannel_id);
extern int css_init_done;
int for_each_subchannel_staged(int (*fn_known)(struct subchannel *, void *),
int (*fn_unknown)(struct subchannel_id,
......@@ -119,20 +129,6 @@ struct channel_subsystem {
extern struct bus_type css_bus_type;
extern struct channel_subsystem *channel_subsystems[];
/* 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_kill_io(struct subchannel *);
void device_set_intretry(struct subchannel *sch);
int device_trigger_verify(struct subchannel *sch);
/* Machine check helper function. */
void device_kill_pending_timer(struct subchannel *);
/* Helper functions to build lists for the slow path. */
void css_schedule_eval(struct subchannel_id schid);
void css_schedule_eval_all(void);
......
......@@ -2,8 +2,7 @@
* drivers/s390/cio/device.c
* bus driver for ccw devices
*
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
* IBM Corporation
* Copyright IBM Corp. 2002,2008
* Author(s): Arnd Bergmann (arndb@de.ibm.com)
* Cornelia Huck (cornelia.huck@de.ibm.com)
* Martin Schwidefsky (schwidefsky@de.ibm.com)
......@@ -126,19 +125,17 @@ struct bus_type ccw_bus_type;
static void io_subchannel_irq(struct subchannel *);
static int io_subchannel_probe(struct subchannel *);
static int io_subchannel_remove(struct subchannel *);
static int io_subchannel_notify(struct subchannel *, int);
static void io_subchannel_verify(struct subchannel *);
static void io_subchannel_ioterm(struct subchannel *);
static void io_subchannel_shutdown(struct subchannel *);
static int io_subchannel_sch_event(struct subchannel *, int);
static int io_subchannel_chp_event(struct subchannel *, void *, int);
static struct css_driver io_subchannel_driver = {
.owner = THIS_MODULE,
.subchannel_type = SUBCHANNEL_TYPE_IO,
.name = "io_subchannel",
.irq = io_subchannel_irq,
.notify = io_subchannel_notify,
.verify = io_subchannel_verify,
.termination = io_subchannel_ioterm,
.sch_event = io_subchannel_sch_event,
.chp_event = io_subchannel_chp_event,
.probe = io_subchannel_probe,
.remove = io_subchannel_remove,
.shutdown = io_subchannel_shutdown,
......@@ -786,7 +783,7 @@ static void sch_attach_device(struct subchannel *sch,
sch_set_cdev(sch, cdev);
cdev->private->schid = sch->schid;
cdev->ccwlock = sch->lock;
device_trigger_reprobe(sch);
ccw_device_trigger_reprobe(cdev);
spin_unlock_irq(sch->lock);
}
......@@ -1265,11 +1262,7 @@ static int io_subchannel_notify(struct subchannel *sch, int event)
cdev = sch_get_cdev(sch);
if (!cdev)
return 0;
if (!cdev->drv)
return 0;
if (!cdev->online)
return 0;
return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0;
return ccw_device_notify(cdev, event);
}
static void io_subchannel_verify(struct subchannel *sch)
......@@ -1281,22 +1274,98 @@ static void io_subchannel_verify(struct subchannel *sch)
dev_fsm_event(cdev, DEV_EVENT_VERIFY);
}
static void io_subchannel_ioterm(struct subchannel *sch)
static int check_for_io_on_path(struct subchannel *sch, int mask)
{
struct ccw_device *cdev;
int cc;
cdev = sch_get_cdev(sch);
if (!cdev)
return;
/* Internal I/O will be retried by the interrupt handler. */
if (cdev->private->flags.intretry)
cc = stsch(sch->schid, &sch->schib);
if (cc)
return 0;
if (sch->schib.scsw.actl && sch->schib.pmcw.lpum == mask)
return 1;