mirror of
https://github.com/torvalds/linux.git
synced 2024-11-14 08:02:07 +00:00
[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: Cornelia Huck <cornelia.huck@de.ibm.com> Signed-off-by: Martin Schwidefsky <schwidefsky@de.ibm.com> Signed-off-by: Heiko Carstens <heiko.carstens@de.ibm.com>
This commit is contained in:
parent
7e9db9eaef
commit
c820de39bd
@ -496,6 +496,26 @@ void chp_process_crw(int id, int status)
|
|||||||
chsc_chp_offline(chpid);
|
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)
|
static inline int info_bit_num(struct chp_id id)
|
||||||
{
|
{
|
||||||
return id.id + id.cssid * (__MAX_CHPID + 1);
|
return id.id + id.cssid * (__MAX_CHPID + 1);
|
||||||
|
@ -19,6 +19,17 @@
|
|||||||
#define CHP_STATUS_RESERVED 2
|
#define CHP_STATUS_RESERVED 2
|
||||||
#define CHP_STATUS_NOT_RECOGNIZED 3
|
#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)
|
static inline int chp_test_bit(u8 *bitmap, int num)
|
||||||
{
|
{
|
||||||
int byte = num >> 3;
|
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_schedule(struct chp_id chpid, int configure);
|
||||||
void chp_cfg_cancel_deconfigure(struct chp_id chpid);
|
void chp_cfg_cancel_deconfigure(struct chp_id chpid);
|
||||||
int chp_info_get_status(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 */
|
#endif /* S390_CHP_H */
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
* drivers/s390/cio/chsc.c
|
* drivers/s390/cio/chsc.c
|
||||||
* S/390 common I/O routines -- channel subsystem call
|
* S/390 common I/O routines -- channel subsystem call
|
||||||
*
|
*
|
||||||
* Copyright (C) 1999-2002 IBM Deutschland Entwicklung GmbH,
|
* Copyright IBM Corp. 1999,2008
|
||||||
* IBM Corporation
|
|
||||||
* Author(s): Ingo Adlung (adlung@de.ibm.com)
|
* Author(s): Ingo Adlung (adlung@de.ibm.com)
|
||||||
* Cornelia Huck (cornelia.huck@de.ibm.com)
|
* Cornelia Huck (cornelia.huck@de.ibm.com)
|
||||||
* Arnd Bergmann (arndb@de.ibm.com)
|
* Arnd Bergmann (arndb@de.ibm.com)
|
||||||
@ -127,77 +126,12 @@ out_free:
|
|||||||
return ret;
|
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)
|
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);
|
spin_lock_irq(sch->lock);
|
||||||
|
if (sch->driver && sch->driver->chp_event)
|
||||||
stsch(sch->schid, &schib);
|
if (sch->driver->chp_event(sch, data, CHP_OFFLINE) != 0)
|
||||||
if (!css_sch_is_valid(&schib))
|
|
||||||
goto out_unreg;
|
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)
|
|
||||||
goto out_unreg;
|
|
||||||
}
|
|
||||||
|
|
||||||
spin_unlock_irq(sch->lock);
|
spin_unlock_irq(sch->lock);
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
@ -242,53 +176,11 @@ static int s390_process_res_acc_new_sch(struct subchannel_id schid, void *data)
|
|||||||
return 0;
|
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)
|
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);
|
spin_lock_irq(sch->lock);
|
||||||
chp_mask = get_res_chpid_mask(&sch->ssd_info, res_data);
|
if (sch->driver && sch->driver->chp_event)
|
||||||
if (chp_mask == 0)
|
sch->driver->chp_event(sch, data, CHP_ONLINE);
|
||||||
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:
|
|
||||||
spin_unlock_irq(sch->lock);
|
spin_unlock_irq(sch->lock);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
@ -509,114 +401,36 @@ void chsc_process_crw(void)
|
|||||||
} while (sei_area->flags & 0x80);
|
} 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)
|
void chsc_chp_online(struct chp_id chpid)
|
||||||
{
|
{
|
||||||
char dbf_txt[15];
|
char dbf_txt[15];
|
||||||
|
struct res_acc_data res_data;
|
||||||
|
|
||||||
sprintf(dbf_txt, "cadd%x.%02x", chpid.cssid, chpid.id);
|
sprintf(dbf_txt, "cadd%x.%02x", chpid.cssid, chpid.id);
|
||||||
CIO_TRACE_EVENT(2, dbf_txt);
|
CIO_TRACE_EVENT(2, dbf_txt);
|
||||||
|
|
||||||
if (chp_get_status(chpid) != 0) {
|
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. */
|
/* Wait until previous actions have settled. */
|
||||||
css_wait_for_slow_path();
|
css_wait_for_slow_path();
|
||||||
for_each_subchannel_staged(__chp_add, __chp_add_new_sch,
|
for_each_subchannel_staged(__s390_process_res_acc, NULL,
|
||||||
&chpid);
|
&res_data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static void __s390_subchannel_vary_chpid(struct subchannel *sch,
|
static void __s390_subchannel_vary_chpid(struct subchannel *sch,
|
||||||
struct chp_id chpid, int on)
|
struct chp_id chpid, int on)
|
||||||
{
|
{
|
||||||
int chp, old_lpm;
|
|
||||||
int mask;
|
|
||||||
unsigned long flags;
|
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);
|
spin_lock_irqsave(sch->lock, flags);
|
||||||
old_lpm = sch->lpm;
|
if (sch->driver && sch->driver->chp_event)
|
||||||
for (chp = 0; chp < 8; chp++) {
|
sch->driver->chp_event(sch, &res_data,
|
||||||
mask = 0x80 >> chp;
|
on ? CHP_VARY_ON : CHP_VARY_OFF);
|
||||||
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;
|
|
||||||
}
|
|
||||||
spin_unlock_irqrestore(sch->lock, flags);
|
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. */
|
/* Copy subchannel type from path management control word. */
|
||||||
sch->st = sch->schib.pmcw.st;
|
sch->st = sch->schib.pmcw.st;
|
||||||
|
|
||||||
switch (sch->st) {
|
switch (sch->st) {
|
||||||
case SUBCHANNEL_TYPE_IO:
|
case SUBCHANNEL_TYPE_IO:
|
||||||
err = cio_validate_io_subchannel(sch);
|
err = cio_validate_io_subchannel(sch);
|
||||||
|
@ -283,7 +283,7 @@ static int css_register_subchannel(struct subchannel *sch)
|
|||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int css_probe_device(struct subchannel_id schid)
|
int css_probe_device(struct subchannel_id schid)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
struct subchannel *sch;
|
struct subchannel *sch;
|
||||||
@ -330,112 +330,6 @@ int css_sch_is_valid(struct schib *schib)
|
|||||||
}
|
}
|
||||||
EXPORT_SYMBOL_GPL(css_sch_is_valid);
|
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)
|
static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
|
||||||
{
|
{
|
||||||
struct schib schib;
|
struct schib schib;
|
||||||
@ -454,6 +348,21 @@ static int css_evaluate_new_subchannel(struct subchannel_id schid, int slow)
|
|||||||
return css_probe_device(schid);
|
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)
|
static void css_evaluate_subchannel(struct subchannel_id schid, int slow)
|
||||||
{
|
{
|
||||||
struct subchannel *sch;
|
struct subchannel *sch;
|
||||||
|
@ -58,18 +58,27 @@ struct pgid {
|
|||||||
__u32 tod_high; /* high word TOD clock */
|
__u32 tod_high; /* high word TOD clock */
|
||||||
} __attribute__ ((packed));
|
} __attribute__ ((packed));
|
||||||
|
|
||||||
/*
|
|
||||||
* A css driver handles all subchannels of one type.
|
|
||||||
*/
|
|
||||||
struct subchannel;
|
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 css_driver {
|
||||||
struct module *owner;
|
struct module *owner;
|
||||||
unsigned int subchannel_type;
|
unsigned int subchannel_type;
|
||||||
struct device_driver drv;
|
struct device_driver drv;
|
||||||
void (*irq)(struct subchannel *);
|
void (*irq)(struct subchannel *);
|
||||||
int (*notify)(struct subchannel *, int);
|
int (*chp_event)(struct subchannel *, void *, int);
|
||||||
void (*verify)(struct subchannel *);
|
int (*sch_event)(struct subchannel *, int);
|
||||||
void (*termination)(struct subchannel *);
|
|
||||||
int (*probe)(struct subchannel *);
|
int (*probe)(struct subchannel *);
|
||||||
int (*remove)(struct subchannel *);
|
int (*remove)(struct subchannel *);
|
||||||
void (*shutdown)(struct subchannel *);
|
void (*shutdown)(struct subchannel *);
|
||||||
@ -87,6 +96,7 @@ extern int css_driver_register(struct css_driver *);
|
|||||||
extern void css_driver_unregister(struct css_driver *);
|
extern void css_driver_unregister(struct css_driver *);
|
||||||
|
|
||||||
extern void css_sch_device_unregister(struct subchannel *);
|
extern void css_sch_device_unregister(struct subchannel *);
|
||||||
|
extern int css_probe_device(struct subchannel_id);
|
||||||
extern struct subchannel *get_subchannel_by_schid(struct subchannel_id);
|
extern struct subchannel *get_subchannel_by_schid(struct subchannel_id);
|
||||||
extern int css_init_done;
|
extern int css_init_done;
|
||||||
int for_each_subchannel_staged(int (*fn_known)(struct subchannel *, void *),
|
int for_each_subchannel_staged(int (*fn_known)(struct subchannel *, void *),
|
||||||
@ -119,20 +129,6 @@ struct channel_subsystem {
|
|||||||
extern struct bus_type css_bus_type;
|
extern struct bus_type css_bus_type;
|
||||||
extern struct channel_subsystem *channel_subsystems[];
|
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. */
|
/* Helper functions to build lists for the slow path. */
|
||||||
void css_schedule_eval(struct subchannel_id schid);
|
void css_schedule_eval(struct subchannel_id schid);
|
||||||
void css_schedule_eval_all(void);
|
void css_schedule_eval_all(void);
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
* drivers/s390/cio/device.c
|
* drivers/s390/cio/device.c
|
||||||
* bus driver for ccw devices
|
* bus driver for ccw devices
|
||||||
*
|
*
|
||||||
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
|
* Copyright IBM Corp. 2002,2008
|
||||||
* IBM Corporation
|
|
||||||
* Author(s): Arnd Bergmann (arndb@de.ibm.com)
|
* Author(s): Arnd Bergmann (arndb@de.ibm.com)
|
||||||
* Cornelia Huck (cornelia.huck@de.ibm.com)
|
* Cornelia Huck (cornelia.huck@de.ibm.com)
|
||||||
* Martin Schwidefsky (schwidefsky@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 void io_subchannel_irq(struct subchannel *);
|
||||||
static int io_subchannel_probe(struct subchannel *);
|
static int io_subchannel_probe(struct subchannel *);
|
||||||
static int io_subchannel_remove(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 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 = {
|
static struct css_driver io_subchannel_driver = {
|
||||||
.owner = THIS_MODULE,
|
.owner = THIS_MODULE,
|
||||||
.subchannel_type = SUBCHANNEL_TYPE_IO,
|
.subchannel_type = SUBCHANNEL_TYPE_IO,
|
||||||
.name = "io_subchannel",
|
.name = "io_subchannel",
|
||||||
.irq = io_subchannel_irq,
|
.irq = io_subchannel_irq,
|
||||||
.notify = io_subchannel_notify,
|
.sch_event = io_subchannel_sch_event,
|
||||||
.verify = io_subchannel_verify,
|
.chp_event = io_subchannel_chp_event,
|
||||||
.termination = io_subchannel_ioterm,
|
|
||||||
.probe = io_subchannel_probe,
|
.probe = io_subchannel_probe,
|
||||||
.remove = io_subchannel_remove,
|
.remove = io_subchannel_remove,
|
||||||
.shutdown = io_subchannel_shutdown,
|
.shutdown = io_subchannel_shutdown,
|
||||||
@ -786,7 +783,7 @@ static void sch_attach_device(struct subchannel *sch,
|
|||||||
sch_set_cdev(sch, cdev);
|
sch_set_cdev(sch, cdev);
|
||||||
cdev->private->schid = sch->schid;
|
cdev->private->schid = sch->schid;
|
||||||
cdev->ccwlock = sch->lock;
|
cdev->ccwlock = sch->lock;
|
||||||
device_trigger_reprobe(sch);
|
ccw_device_trigger_reprobe(cdev);
|
||||||
spin_unlock_irq(sch->lock);
|
spin_unlock_irq(sch->lock);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1265,11 +1262,7 @@ static int io_subchannel_notify(struct subchannel *sch, int event)
|
|||||||
cdev = sch_get_cdev(sch);
|
cdev = sch_get_cdev(sch);
|
||||||
if (!cdev)
|
if (!cdev)
|
||||||
return 0;
|
return 0;
|
||||||
if (!cdev->drv)
|
return ccw_device_notify(cdev, event);
|
||||||
return 0;
|
|
||||||
if (!cdev->online)
|
|
||||||
return 0;
|
|
||||||
return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void io_subchannel_verify(struct subchannel *sch)
|
static void io_subchannel_verify(struct subchannel *sch)
|
||||||
@ -1281,20 +1274,96 @@ static void io_subchannel_verify(struct subchannel *sch)
|
|||||||
dev_fsm_event(cdev, DEV_EVENT_VERIFY);
|
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)
|
||||||
|
{
|
||||||
|
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,
|
||||||
|
struct ccw_device *cdev)
|
||||||
|
{
|
||||||
|
if (cio_clear(sch)) {
|
||||||
|
/* Recheck device in case clear failed. */
|
||||||
|
sch->lpm = 0;
|
||||||
|
if (cdev->online)
|
||||||
|
dev_fsm_event(cdev, DEV_EVENT_VERIFY);
|
||||||
|
else
|
||||||
|
css_schedule_eval(sch->schid);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
cdev->private->state = DEV_STATE_CLEAR_VERIFY;
|
||||||
|
/* Request retry of internal operation. */
|
||||||
|
cdev->private->flags.intretry = 1;
|
||||||
|
/* Call handler. */
|
||||||
|
if (cdev->handler)
|
||||||
|
cdev->handler(cdev, cdev->private->intparm,
|
||||||
|
ERR_PTR(-EIO));
|
||||||
|
}
|
||||||
|
|
||||||
|
static void io_subchannel_terminate_path(struct subchannel *sch, u8 mask)
|
||||||
{
|
{
|
||||||
struct ccw_device *cdev;
|
struct ccw_device *cdev;
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
cdev = sch_get_cdev(sch);
|
||||||
if (!cdev)
|
if (!cdev)
|
||||||
return;
|
return;
|
||||||
/* Internal I/O will be retried by the interrupt handler. */
|
if (check_for_io_on_path(sch, mask)) {
|
||||||
if (cdev->private->flags.intretry)
|
if (cdev->private->state == DEV_STATE_ONLINE)
|
||||||
return;
|
ccw_device_kill_io(cdev);
|
||||||
cdev->private->state = DEV_STATE_CLEAR_VERIFY;
|
else {
|
||||||
if (cdev->handler)
|
terminate_internal_io(sch, cdev);
|
||||||
cdev->handler(cdev, cdev->private->intparm,
|
/* Re-start path verification. */
|
||||||
ERR_PTR(-EIO));
|
dev_fsm_event(cdev, DEV_EVENT_VERIFY);
|
||||||
|
}
|
||||||
|
} else
|
||||||
|
/* trigger path verification. */
|
||||||
|
dev_fsm_event(cdev, DEV_EVENT_VERIFY);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
static int io_subchannel_chp_event(struct subchannel *sch, void *data,
|
||||||
|
int event)
|
||||||
|
{
|
||||||
|
int mask;
|
||||||
|
struct res_acc_data *res_data;
|
||||||
|
|
||||||
|
res_data = data;
|
||||||
|
mask = chp_ssd_get_mask(&sch->ssd_info, res_data);
|
||||||
|
if (!mask)
|
||||||
|
return 0;
|
||||||
|
switch (event) {
|
||||||
|
case CHP_VARY_OFF:
|
||||||
|
sch->opm &= ~mask;
|
||||||
|
sch->lpm &= ~mask;
|
||||||
|
io_subchannel_terminate_path(sch, mask);
|
||||||
|
break;
|
||||||
|
case CHP_VARY_ON:
|
||||||
|
sch->opm |= mask;
|
||||||
|
sch->lpm |= mask;
|
||||||
|
io_subchannel_verify(sch);
|
||||||
|
break;
|
||||||
|
case CHP_OFFLINE:
|
||||||
|
if (stsch(sch->schid, &sch->schib))
|
||||||
|
return -ENXIO;
|
||||||
|
if (!css_sch_is_valid(&sch->schib))
|
||||||
|
return -ENODEV;
|
||||||
|
io_subchannel_terminate_path(sch, mask);
|
||||||
|
break;
|
||||||
|
case CHP_ONLINE:
|
||||||
|
if (stsch(sch->schid, &sch->schib))
|
||||||
|
return -ENXIO;
|
||||||
|
sch->lpm |= mask & sch->opm;
|
||||||
|
io_subchannel_verify(sch);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
@ -1326,6 +1395,195 @@ io_subchannel_shutdown(struct subchannel *sch)
|
|||||||
cio_disable_subchannel(sch);
|
cio_disable_subchannel(sch);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static int io_subchannel_get_status(struct subchannel *sch)
|
||||||
|
{
|
||||||
|
struct schib schib;
|
||||||
|
|
||||||
|
if (stsch(sch->schid, &schib) || !schib.pmcw.dnv)
|
||||||
|
return CIO_GONE;
|
||||||
|
if (sch->schib.pmcw.dnv && (schib.pmcw.dev != sch->schib.pmcw.dev))
|
||||||
|
return CIO_REVALIDATE;
|
||||||
|
if (!sch->lpm)
|
||||||
|
return CIO_NO_PATH;
|
||||||
|
return CIO_OPER;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int device_is_disconnected(struct ccw_device *cdev)
|
||||||
|
{
|
||||||
|
if (!cdev)
|
||||||
|
return 0;
|
||||||
|
return (cdev->private->state == DEV_STATE_DISCONNECTED ||
|
||||||
|
cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int recovery_check(struct device *dev, void *data)
|
||||||
|
{
|
||||||
|
struct ccw_device *cdev = to_ccwdev(dev);
|
||||||
|
int *redo = data;
|
||||||
|
|
||||||
|
spin_lock_irq(cdev->ccwlock);
|
||||||
|
switch (cdev->private->state) {
|
||||||
|
case DEV_STATE_DISCONNECTED:
|
||||||
|
CIO_MSG_EVENT(3, "recovery: trigger 0.%x.%04x\n",
|
||||||
|
cdev->private->dev_id.ssid,
|
||||||
|
cdev->private->dev_id.devno);
|
||||||
|
dev_fsm_event(cdev, DEV_EVENT_VERIFY);
|
||||||
|
*redo = 1;
|
||||||
|
break;
|
||||||
|
case DEV_STATE_DISCONNECTED_SENSE_ID:
|
||||||
|
*redo = 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
spin_unlock_irq(cdev->ccwlock);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void recovery_work_func(struct work_struct *unused)
|
||||||
|
{
|
||||||
|
int redo = 0;
|
||||||
|
|
||||||
|
bus_for_each_dev(&ccw_bus_type, NULL, &redo, recovery_check);
|
||||||
|
if (redo) {
|
||||||
|
spin_lock_irq(&recovery_lock);
|
||||||
|
if (!timer_pending(&recovery_timer)) {
|
||||||
|
if (recovery_phase < ARRAY_SIZE(recovery_delay) - 1)
|
||||||
|
recovery_phase++;
|
||||||
|
mod_timer(&recovery_timer, jiffies +
|
||||||
|
recovery_delay[recovery_phase] * HZ);
|
||||||
|
}
|
||||||
|
spin_unlock_irq(&recovery_lock);
|
||||||
|
} else
|
||||||
|
CIO_MSG_EVENT(4, "recovery: end\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
static DECLARE_WORK(recovery_work, recovery_work_func);
|
||||||
|
|
||||||
|
static void recovery_func(unsigned long data)
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
* We can't do our recovery in softirq context and it's not
|
||||||
|
* performance critical, so we schedule it.
|
||||||
|
*/
|
||||||
|
schedule_work(&recovery_work);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ccw_device_schedule_recovery(void)
|
||||||
|
{
|
||||||
|
unsigned long flags;
|
||||||
|
|
||||||
|
CIO_MSG_EVENT(4, "recovery: schedule\n");
|
||||||
|
spin_lock_irqsave(&recovery_lock, flags);
|
||||||
|
if (!timer_pending(&recovery_timer) || (recovery_phase != 0)) {
|
||||||
|
recovery_phase = 0;
|
||||||
|
mod_timer(&recovery_timer, jiffies + recovery_delay[0] * HZ);
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(&recovery_lock, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
static void device_set_disconnected(struct ccw_device *cdev)
|
||||||
|
{
|
||||||
|
if (!cdev)
|
||||||
|
return;
|
||||||
|
ccw_device_set_timeout(cdev, 0);
|
||||||
|
cdev->private->flags.fake_irb = 0;
|
||||||
|
cdev->private->state = DEV_STATE_DISCONNECTED;
|
||||||
|
if (cdev->online)
|
||||||
|
ccw_device_schedule_recovery();
|
||||||
|
}
|
||||||
|
|
||||||
|
static int io_subchannel_sch_event(struct subchannel *sch, int slow)
|
||||||
|
{
|
||||||
|
int event, ret, disc;
|
||||||
|
unsigned long flags;
|
||||||
|
enum { NONE, UNREGISTER, UNREGISTER_PROBE, REPROBE } action;
|
||||||
|
struct ccw_device *cdev;
|
||||||
|
|
||||||
|
spin_lock_irqsave(sch->lock, flags);
|
||||||
|
cdev = sch_get_cdev(sch);
|
||||||
|
disc = device_is_disconnected(cdev);
|
||||||
|
if (disc && slow) {
|
||||||
|
/* Disconnected devices are evaluated directly only.*/
|
||||||
|
spin_unlock_irqrestore(sch->lock, flags);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
/* No interrupt after machine check - kill pending timers. */
|
||||||
|
if (cdev)
|
||||||
|
ccw_device_set_timeout(cdev, 0);
|
||||||
|
if (!disc && !slow) {
|
||||||
|
/* Non-disconnected devices are evaluated on the slow path. */
|
||||||
|
spin_unlock_irqrestore(sch->lock, flags);
|
||||||
|
return -EAGAIN;
|
||||||
|
}
|
||||||
|
event = io_subchannel_get_status(sch);
|
||||||
|
CIO_MSG_EVENT(4, "Evaluating schid 0.%x.%04x, event %d, %s, %s path.\n",
|
||||||
|
sch->schid.ssid, sch->schid.sch_no, event,
|
||||||
|
disc ? "disconnected" : "normal",
|
||||||
|
slow ? "slow" : "fast");
|
||||||
|
/* Analyze subchannel status. */
|
||||||
|
action = NONE;
|
||||||
|
switch (event) {
|
||||||
|
case CIO_NO_PATH:
|
||||||
|
if (disc) {
|
||||||
|
/* Check if paths have become available. */
|
||||||
|
action = REPROBE;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
/* fall through */
|
||||||
|
case CIO_GONE:
|
||||||
|
/* Prevent unwanted effects when opening lock. */
|
||||||
|
cio_disable_subchannel(sch);
|
||||||
|
device_set_disconnected(cdev);
|
||||||
|
/* Ask driver what to do with device. */
|
||||||
|
action = UNREGISTER;
|
||||||
|
spin_unlock_irqrestore(sch->lock, flags);
|
||||||
|
ret = io_subchannel_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:
|
||||||
|
ccw_device_trigger_reprobe(cdev);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
spin_unlock_irqrestore(sch->lock, flags);
|
||||||
|
/* Probe if necessary. */
|
||||||
|
if (action == UNREGISTER_PROBE)
|
||||||
|
ret = css_probe_device(sch->schid);
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
#ifdef CONFIG_CCW_CONSOLE
|
#ifdef CONFIG_CCW_CONSOLE
|
||||||
static struct ccw_device console_cdev;
|
static struct ccw_device console_cdev;
|
||||||
static struct ccw_device_private console_private;
|
static struct ccw_device_private console_private;
|
||||||
@ -1558,71 +1816,6 @@ ccw_device_get_subchannel_id(struct ccw_device *cdev)
|
|||||||
return sch->schid;
|
return sch->schid;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int recovery_check(struct device *dev, void *data)
|
|
||||||
{
|
|
||||||
struct ccw_device *cdev = to_ccwdev(dev);
|
|
||||||
int *redo = data;
|
|
||||||
|
|
||||||
spin_lock_irq(cdev->ccwlock);
|
|
||||||
switch (cdev->private->state) {
|
|
||||||
case DEV_STATE_DISCONNECTED:
|
|
||||||
CIO_MSG_EVENT(4, "recovery: trigger 0.%x.%04x\n",
|
|
||||||
cdev->private->dev_id.ssid,
|
|
||||||
cdev->private->dev_id.devno);
|
|
||||||
dev_fsm_event(cdev, DEV_EVENT_VERIFY);
|
|
||||||
*redo = 1;
|
|
||||||
break;
|
|
||||||
case DEV_STATE_DISCONNECTED_SENSE_ID:
|
|
||||||
*redo = 1;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
spin_unlock_irq(cdev->ccwlock);
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static void recovery_work_func(struct work_struct *unused)
|
|
||||||
{
|
|
||||||
int redo = 0;
|
|
||||||
|
|
||||||
bus_for_each_dev(&ccw_bus_type, NULL, &redo, recovery_check);
|
|
||||||
if (redo) {
|
|
||||||
spin_lock_irq(&recovery_lock);
|
|
||||||
if (!timer_pending(&recovery_timer)) {
|
|
||||||
if (recovery_phase < ARRAY_SIZE(recovery_delay) - 1)
|
|
||||||
recovery_phase++;
|
|
||||||
mod_timer(&recovery_timer, jiffies +
|
|
||||||
recovery_delay[recovery_phase] * HZ);
|
|
||||||
}
|
|
||||||
spin_unlock_irq(&recovery_lock);
|
|
||||||
} else
|
|
||||||
CIO_MSG_EVENT(4, "recovery: end\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
static DECLARE_WORK(recovery_work, recovery_work_func);
|
|
||||||
|
|
||||||
static void recovery_func(unsigned long data)
|
|
||||||
{
|
|
||||||
/*
|
|
||||||
* We can't do our recovery in softirq context and it's not
|
|
||||||
* performance critical, so we schedule it.
|
|
||||||
*/
|
|
||||||
schedule_work(&recovery_work);
|
|
||||||
}
|
|
||||||
|
|
||||||
void ccw_device_schedule_recovery(void)
|
|
||||||
{
|
|
||||||
unsigned long flags;
|
|
||||||
|
|
||||||
CIO_MSG_EVENT(4, "recovery: schedule\n");
|
|
||||||
spin_lock_irqsave(&recovery_lock, flags);
|
|
||||||
if (!timer_pending(&recovery_timer) || (recovery_phase != 0)) {
|
|
||||||
recovery_phase = 0;
|
|
||||||
mod_timer(&recovery_timer, jiffies + recovery_delay[0] * HZ);
|
|
||||||
}
|
|
||||||
spin_unlock_irqrestore(&recovery_lock, flags);
|
|
||||||
}
|
|
||||||
|
|
||||||
MODULE_LICENSE("GPL");
|
MODULE_LICENSE("GPL");
|
||||||
EXPORT_SYMBOL(ccw_device_set_online);
|
EXPORT_SYMBOL(ccw_device_set_online);
|
||||||
EXPORT_SYMBOL(ccw_device_set_offline);
|
EXPORT_SYMBOL(ccw_device_set_offline);
|
||||||
|
@ -88,8 +88,6 @@ int ccw_device_recognition(struct ccw_device *);
|
|||||||
int ccw_device_online(struct ccw_device *);
|
int ccw_device_online(struct ccw_device *);
|
||||||
int ccw_device_offline(struct ccw_device *);
|
int ccw_device_offline(struct ccw_device *);
|
||||||
|
|
||||||
void ccw_device_schedule_recovery(void);
|
|
||||||
|
|
||||||
/* Function prototypes for device status and basic sense stuff. */
|
/* Function prototypes for device status and basic sense stuff. */
|
||||||
void ccw_device_accumulate_irb(struct ccw_device *, struct irb *);
|
void ccw_device_accumulate_irb(struct ccw_device *, struct irb *);
|
||||||
void ccw_device_accumulate_basic_sense(struct ccw_device *, struct irb *);
|
void ccw_device_accumulate_basic_sense(struct ccw_device *, struct irb *);
|
||||||
@ -118,6 +116,11 @@ int ccw_device_call_handler(struct ccw_device *);
|
|||||||
|
|
||||||
int ccw_device_stlck(struct ccw_device *);
|
int ccw_device_stlck(struct ccw_device *);
|
||||||
|
|
||||||
|
/* Helper function for machine check handling. */
|
||||||
|
void ccw_device_trigger_reprobe(struct ccw_device *);
|
||||||
|
void ccw_device_kill_io(struct ccw_device *);
|
||||||
|
int ccw_device_notify(struct ccw_device *, int);
|
||||||
|
|
||||||
/* qdio needs this. */
|
/* qdio needs this. */
|
||||||
void ccw_device_set_timeout(struct ccw_device *, int);
|
void ccw_device_set_timeout(struct ccw_device *, int);
|
||||||
extern struct subchannel_id ccw_device_get_subchannel_id(struct ccw_device *);
|
extern struct subchannel_id ccw_device_get_subchannel_id(struct ccw_device *);
|
||||||
|
@ -2,8 +2,7 @@
|
|||||||
* drivers/s390/cio/device_fsm.c
|
* drivers/s390/cio/device_fsm.c
|
||||||
* finite state machine for device handling
|
* finite state machine for device handling
|
||||||
*
|
*
|
||||||
* Copyright (C) 2002 IBM Deutschland Entwicklung GmbH,
|
* Copyright IBM Corp. 2002,2008
|
||||||
* IBM Corporation
|
|
||||||
* Author(s): Cornelia Huck (cornelia.huck@de.ibm.com)
|
* Author(s): Cornelia Huck (cornelia.huck@de.ibm.com)
|
||||||
* Martin Schwidefsky (schwidefsky@de.ibm.com)
|
* Martin Schwidefsky (schwidefsky@de.ibm.com)
|
||||||
*/
|
*/
|
||||||
@ -27,65 +26,6 @@
|
|||||||
|
|
||||||
static int timeout_log_enabled;
|
static int timeout_log_enabled;
|
||||||
|
|
||||||
int
|
|
||||||
device_is_online(struct subchannel *sch)
|
|
||||||
{
|
|
||||||
struct ccw_device *cdev;
|
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
|
||||||
if (!cdev)
|
|
||||||
return 0;
|
|
||||||
return (cdev->private->state == DEV_STATE_ONLINE);
|
|
||||||
}
|
|
||||||
|
|
||||||
int
|
|
||||||
device_is_disconnected(struct subchannel *sch)
|
|
||||||
{
|
|
||||||
struct ccw_device *cdev;
|
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
|
||||||
if (!cdev)
|
|
||||||
return 0;
|
|
||||||
return (cdev->private->state == DEV_STATE_DISCONNECTED ||
|
|
||||||
cdev->private->state == DEV_STATE_DISCONNECTED_SENSE_ID);
|
|
||||||
}
|
|
||||||
|
|
||||||
void
|
|
||||||
device_set_disconnected(struct subchannel *sch)
|
|
||||||
{
|
|
||||||
struct ccw_device *cdev;
|
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
|
||||||
if (!cdev)
|
|
||||||
return;
|
|
||||||
ccw_device_set_timeout(cdev, 0);
|
|
||||||
cdev->private->flags.fake_irb = 0;
|
|
||||||
cdev->private->state = DEV_STATE_DISCONNECTED;
|
|
||||||
if (cdev->online)
|
|
||||||
ccw_device_schedule_recovery();
|
|
||||||
}
|
|
||||||
|
|
||||||
void device_set_intretry(struct subchannel *sch)
|
|
||||||
{
|
|
||||||
struct ccw_device *cdev;
|
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
|
||||||
if (!cdev)
|
|
||||||
return;
|
|
||||||
cdev->private->flags.intretry = 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
int device_trigger_verify(struct subchannel *sch)
|
|
||||||
{
|
|
||||||
struct ccw_device *cdev;
|
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
|
||||||
if (!cdev || !cdev->online)
|
|
||||||
return -EINVAL;
|
|
||||||
dev_fsm_event(cdev, DEV_EVENT_VERIFY);
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static int __init ccw_timeout_log_setup(char *unused)
|
static int __init ccw_timeout_log_setup(char *unused)
|
||||||
{
|
{
|
||||||
timeout_log_enabled = 1;
|
timeout_log_enabled = 1;
|
||||||
@ -171,18 +111,6 @@ ccw_device_set_timeout(struct ccw_device *cdev, int expires)
|
|||||||
add_timer(&cdev->private->timer);
|
add_timer(&cdev->private->timer);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kill any pending timers after machine check. */
|
|
||||||
void
|
|
||||||
device_kill_pending_timer(struct subchannel *sch)
|
|
||||||
{
|
|
||||||
struct ccw_device *cdev;
|
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
|
||||||
if (!cdev)
|
|
||||||
return;
|
|
||||||
ccw_device_set_timeout(cdev, 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Cancel running i/o. This is called repeatedly since halt/clear are
|
* Cancel running i/o. This is called repeatedly since halt/clear are
|
||||||
* asynchronous operations. We do one try with cio_cancel, two tries
|
* asynchronous operations. We do one try with cio_cancel, two tries
|
||||||
@ -388,25 +316,27 @@ ccw_device_sense_id_done(struct ccw_device *cdev, int err)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ccw_device_notify(struct ccw_device *cdev, int event)
|
||||||
|
{
|
||||||
|
if (!cdev->drv)
|
||||||
|
return 0;
|
||||||
|
if (!cdev->online)
|
||||||
|
return 0;
|
||||||
|
return cdev->drv->notify ? cdev->drv->notify(cdev, event) : 0;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
ccw_device_oper_notify(struct work_struct *work)
|
ccw_device_oper_notify(struct work_struct *work)
|
||||||
{
|
{
|
||||||
struct ccw_device_private *priv;
|
struct ccw_device_private *priv;
|
||||||
struct ccw_device *cdev;
|
struct ccw_device *cdev;
|
||||||
struct subchannel *sch;
|
|
||||||
int ret;
|
int ret;
|
||||||
unsigned long flags;
|
unsigned long flags;
|
||||||
|
|
||||||
priv = container_of(work, struct ccw_device_private, kick_work);
|
priv = container_of(work, struct ccw_device_private, kick_work);
|
||||||
cdev = priv->cdev;
|
cdev = priv->cdev;
|
||||||
|
ret = ccw_device_notify(cdev, CIO_OPER);
|
||||||
spin_lock_irqsave(cdev->ccwlock, flags);
|
spin_lock_irqsave(cdev->ccwlock, flags);
|
||||||
sch = to_subchannel(cdev->dev.parent);
|
|
||||||
if (sch->driver && sch->driver->notify) {
|
|
||||||
spin_unlock_irqrestore(cdev->ccwlock, flags);
|
|
||||||
ret = sch->driver->notify(sch, CIO_OPER);
|
|
||||||
spin_lock_irqsave(cdev->ccwlock, flags);
|
|
||||||
} else
|
|
||||||
ret = 0;
|
|
||||||
if (ret) {
|
if (ret) {
|
||||||
/* Reenable channel measurements, if needed. */
|
/* Reenable channel measurements, if needed. */
|
||||||
spin_unlock_irqrestore(cdev->ccwlock, flags);
|
spin_unlock_irqrestore(cdev->ccwlock, flags);
|
||||||
@ -986,12 +916,10 @@ ccw_device_killing_timeout(struct ccw_device *cdev, enum dev_event dev_event)
|
|||||||
ERR_PTR(-EIO));
|
ERR_PTR(-EIO));
|
||||||
}
|
}
|
||||||
|
|
||||||
void device_kill_io(struct subchannel *sch)
|
void ccw_device_kill_io(struct ccw_device *cdev)
|
||||||
{
|
{
|
||||||
int ret;
|
int ret;
|
||||||
struct ccw_device *cdev;
|
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
|
||||||
ret = ccw_device_cancel_halt_clear(cdev);
|
ret = ccw_device_cancel_halt_clear(cdev);
|
||||||
if (ret == -EBUSY) {
|
if (ret == -EBUSY) {
|
||||||
ccw_device_set_timeout(cdev, 3*HZ);
|
ccw_device_set_timeout(cdev, 3*HZ);
|
||||||
@ -1055,17 +983,14 @@ ccw_device_start_id(struct ccw_device *cdev, enum dev_event dev_event)
|
|||||||
ccw_device_sense_id_start(cdev);
|
ccw_device_sense_id_start(cdev);
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void ccw_device_trigger_reprobe(struct ccw_device *cdev)
|
||||||
device_trigger_reprobe(struct subchannel *sch)
|
|
||||||
{
|
{
|
||||||
struct ccw_device *cdev;
|
struct subchannel *sch;
|
||||||
|
|
||||||
cdev = sch_get_cdev(sch);
|
|
||||||
if (!cdev)
|
|
||||||
return;
|
|
||||||
if (cdev->private->state != DEV_STATE_DISCONNECTED)
|
if (cdev->private->state != DEV_STATE_DISCONNECTED)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
sch = to_subchannel(cdev->dev.parent);
|
||||||
/* Update some values. */
|
/* Update some values. */
|
||||||
if (stsch(sch->schid, &sch->schib))
|
if (stsch(sch->schid, &sch->schib))
|
||||||
return;
|
return;
|
||||||
@ -1081,7 +1006,6 @@ device_trigger_reprobe(struct subchannel *sch)
|
|||||||
sch->schib.pmcw.ena = 0;
|
sch->schib.pmcw.ena = 0;
|
||||||
if ((sch->lpm & (sch->lpm - 1)) != 0)
|
if ((sch->lpm & (sch->lpm - 1)) != 0)
|
||||||
sch->schib.pmcw.mp = 1;
|
sch->schib.pmcw.mp = 1;
|
||||||
sch->schib.pmcw.intparm = (u32)(addr_t)sch;
|
|
||||||
/* We should also udate ssd info, but this has to wait. */
|
/* We should also udate ssd info, but this has to wait. */
|
||||||
/* Check if this is another device which appeared on the same sch. */
|
/* Check if this is another device which appeared on the same sch. */
|
||||||
if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) {
|
if (sch->schib.pmcw.dev != cdev->private->dev_id.devno) {
|
||||||
|
Loading…
Reference in New Issue
Block a user