Commit 13952ec1 authored by Sebastian Ott's avatar Sebastian Ott Committed by Martin Schwidefsky

[S390] cio: introduce cio_commit_config

To change the configuration of a subchannel we alter the modifiable
bits of the subchannel's schib field and issue a modify subchannel.
There can be the case that not all changes were applied -or worse-
quietly overwritten by the hardware. With the next store subchannel
we obtain the current state of the hardware but lose our target
configuration.

With this patch we introduce a subchannel_config structure which
contains the target subchannel configuration. Additionally the msch
wrapper cio_modify is replaced with cio_commit_config which
copies the desired changes to a temporary schib. msch is then
called with the temporary schib. This schib is only written back
to the subchannel if all changes were applied.
Signed-off-by: default avatarSebastian Ott <sebott@linux.vnet.ibm.com>
Signed-off-by: default avatarMartin Schwidefsky <schwidefsky@de.ibm.com>
parent cdb912a4
...@@ -330,30 +330,70 @@ cio_cancel (struct subchannel *sch) ...@@ -330,30 +330,70 @@ cio_cancel (struct subchannel *sch)
} }
} }
static void cio_apply_config(struct subchannel *sch, struct schib *schib)
{
schib->pmcw.intparm = sch->config.intparm;
schib->pmcw.mbi = sch->config.mbi;
schib->pmcw.isc = sch->config.isc;
schib->pmcw.ena = sch->config.ena;
schib->pmcw.mme = sch->config.mme;
schib->pmcw.mp = sch->config.mp;
schib->pmcw.csense = sch->config.csense;
schib->pmcw.mbfc = sch->config.mbfc;
if (sch->config.mbfc)
schib->mba = sch->config.mba;
}
static int cio_check_config(struct subchannel *sch, struct schib *schib)
{
return (schib->pmcw.intparm == sch->config.intparm) &&
(schib->pmcw.mbi == sch->config.mbi) &&
(schib->pmcw.isc == sch->config.isc) &&
(schib->pmcw.ena == sch->config.ena) &&
(schib->pmcw.mme == sch->config.mme) &&
(schib->pmcw.mp == sch->config.mp) &&
(schib->pmcw.csense == sch->config.csense) &&
(schib->pmcw.mbfc == sch->config.mbfc) &&
(!sch->config.mbfc || (schib->mba == sch->config.mba));
}
/* /*
* Function: cio_modify * cio_commit_config - apply configuration to the subchannel
* Issues a "Modify Subchannel" on the specified subchannel
*/ */
int int cio_commit_config(struct subchannel *sch)
cio_modify (struct subchannel *sch)
{ {
int ccode, retry, ret; struct schib schib;
int ccode, retry, ret = 0;
if (stsch(sch->schid, &schib) || !css_sch_is_valid(&schib))
return -ENODEV;
ret = 0;
for (retry = 0; retry < 5; retry++) { for (retry = 0; retry < 5; retry++) {
ccode = msch_err (sch->schid, &sch->schib); /* copy desired changes to local schib */
if (ccode < 0) /* -EIO if msch gets a program check. */ cio_apply_config(sch, &schib);
ccode = msch_err(sch->schid, &schib);
if (ccode < 0) /* -EIO if msch gets a program check. */
return ccode; return ccode;
switch (ccode) { switch (ccode) {
case 0: /* successfull */ case 0: /* successfull */
return 0; if (stsch(sch->schid, &schib) ||
case 1: /* status pending */ !css_sch_is_valid(&schib))
return -ENODEV;
if (cio_check_config(sch, &schib)) {
/* commit changes from local schib */
memcpy(&sch->schib, &schib, sizeof(schib));
return 0;
}
ret = -EAGAIN;
break;
case 1: /* status pending */
return -EBUSY; return -EBUSY;
case 2: /* busy */ case 2: /* busy */
udelay (100); /* allow for recovery */ udelay(100); /* allow for recovery */
ret = -EBUSY; ret = -EBUSY;
break; break;
case 3: /* not operational */ case 3: /* not operational */
return -ENODEV; return -ENODEV;
} }
} }
...@@ -396,32 +436,24 @@ int cio_enable_subchannel(struct subchannel *sch, u32 intparm) ...@@ -396,32 +436,24 @@ int cio_enable_subchannel(struct subchannel *sch, u32 intparm)
if (cio_update_schib(sch)) if (cio_update_schib(sch))
return -ENODEV; return -ENODEV;
for (retry = 5, ret = 0; retry > 0; retry--) { sch->config.ena = 1;
sch->schib.pmcw.ena = 1; sch->config.isc = sch->isc;
sch->schib.pmcw.isc = sch->isc; sch->config.intparm = intparm;
sch->schib.pmcw.intparm = intparm;
ret = cio_modify(sch); for (retry = 0; retry < 3; retry++) {
if (ret == -ENODEV) ret = cio_commit_config(sch);
break; if (ret == -EIO) {
if (ret == -EIO)
/* /*
* Got a program check in cio_modify. Try without * Got a program check in msch. Try without
* the concurrent sense bit the next time. * the concurrent sense bit the next time.
*/ */
sch->schib.pmcw.csense = 0; sch->config.csense = 0;
if (ret == 0) { } else if (ret == -EBUSY) {
if (cio_update_schib(sch)) {
ret = -ENODEV;
break;
}
if (sch->schib.pmcw.ena)
break;
}
if (ret == -EBUSY) {
struct irb irb; struct irb irb;
if (tsch(sch->schid, &irb) != 0) if (tsch(sch->schid, &irb) != 0)
break; break;
} } else
break;
} }
sprintf (dbf_txt, "ret:%d", ret); sprintf (dbf_txt, "ret:%d", ret);
CIO_TRACE_EVENT (2, dbf_txt); CIO_TRACE_EVENT (2, dbf_txt);
...@@ -436,7 +468,6 @@ EXPORT_SYMBOL_GPL(cio_enable_subchannel); ...@@ -436,7 +468,6 @@ EXPORT_SYMBOL_GPL(cio_enable_subchannel);
int cio_disable_subchannel(struct subchannel *sch) int cio_disable_subchannel(struct subchannel *sch)
{ {
char dbf_txt[15]; char dbf_txt[15];
int retry;
int ret; int ret;
CIO_TRACE_EVENT (2, "dissch"); CIO_TRACE_EVENT (2, "dissch");
...@@ -454,27 +485,9 @@ int cio_disable_subchannel(struct subchannel *sch) ...@@ -454,27 +485,9 @@ int cio_disable_subchannel(struct subchannel *sch)
*/ */
return -EBUSY; return -EBUSY;
for (retry = 5, ret = 0; retry > 0; retry--) { sch->config.ena = 0;
sch->schib.pmcw.ena = 0; ret = cio_commit_config(sch);
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) {
if (cio_update_schib(sch)) {
ret = -ENODEV;
break;
}
if (!sch->schib.pmcw.ena)
break;
}
}
sprintf (dbf_txt, "ret:%d", ret); sprintf (dbf_txt, "ret:%d", ret);
CIO_TRACE_EVENT (2, dbf_txt); CIO_TRACE_EVENT (2, dbf_txt);
return ret; return ret;
...@@ -817,10 +830,9 @@ cio_probe_console(void) ...@@ -817,10 +830,9 @@ cio_probe_console(void)
* enable console I/O-interrupt subclass * enable console I/O-interrupt subclass
*/ */
isc_register(CONSOLE_ISC); isc_register(CONSOLE_ISC);
console_subchannel.schib.pmcw.isc = CONSOLE_ISC; console_subchannel.config.isc = CONSOLE_ISC;
console_subchannel.schib.pmcw.intparm = console_subchannel.config.intparm = (u32)(addr_t)&console_subchannel;
(u32)(addr_t)&console_subchannel; ret = cio_commit_config(&console_subchannel);
ret = cio_modify(&console_subchannel);
if (ret) { if (ret) {
isc_unregister(CONSOLE_ISC); isc_unregister(CONSOLE_ISC);
console_subchannel_in_use = 0; console_subchannel_in_use = 0;
...@@ -832,8 +844,8 @@ cio_probe_console(void) ...@@ -832,8 +844,8 @@ cio_probe_console(void)
void void
cio_release_console(void) cio_release_console(void)
{ {
console_subchannel.schib.pmcw.intparm = 0; console_subchannel.config.intparm = 0;
cio_modify(&console_subchannel); cio_commit_config(&console_subchannel);
isc_unregister(CONSOLE_ISC); isc_unregister(CONSOLE_ISC);
console_subchannel_in_use = 0; console_subchannel_in_use = 0;
} }
......
...@@ -45,6 +45,19 @@ struct pmcw { ...@@ -45,6 +45,19 @@ struct pmcw {
/* ... in an operand exception. */ /* ... in an operand exception. */
} __attribute__ ((packed)); } __attribute__ ((packed));
/* Target SCHIB configuration. */
struct schib_config {
u64 mba;
u32 intparm;
u16 mbi;
u32 isc:3;
u32 ena:1;
u32 mme:2;
u32 mp:1;
u32 csense:1;
u32 mbfc:1;
} __attribute__ ((packed));
/* /*
* subchannel information block * subchannel information block
*/ */
...@@ -83,6 +96,7 @@ struct subchannel { ...@@ -83,6 +96,7 @@ struct subchannel {
struct css_driver *driver; struct css_driver *driver;
void *private; /* private per subchannel type data */ void *private; /* private per subchannel type data */
struct work_struct work; struct work_struct work;
struct schib_config config;
} __attribute__ ((aligned(8))); } __attribute__ ((aligned(8)));
#define IO_INTERRUPT_TYPE 0 /* I/O interrupt type */ #define IO_INTERRUPT_TYPE 0 /* I/O interrupt type */
...@@ -101,8 +115,8 @@ extern int cio_start_key (struct subchannel *, struct ccw1 *, __u8, __u8); ...@@ -101,8 +115,8 @@ extern int cio_start_key (struct subchannel *, struct ccw1 *, __u8, __u8);
extern int cio_cancel (struct subchannel *); extern int cio_cancel (struct subchannel *);
extern int cio_set_options (struct subchannel *, int); extern int cio_set_options (struct subchannel *, int);
extern int cio_get_options (struct subchannel *); extern int cio_get_options (struct subchannel *);
extern int cio_modify (struct subchannel *);
extern int cio_update_schib(struct subchannel *sch); extern int cio_update_schib(struct subchannel *sch);
extern int cio_commit_config(struct subchannel *sch);
int cio_tm_start_key(struct subchannel *sch, struct tcw *tcw, u8 lpm, u8 key); int cio_tm_start_key(struct subchannel *sch, struct tcw *tcw, u8 lpm, u8 key);
int cio_tm_intrg(struct subchannel *sch); int cio_tm_intrg(struct subchannel *sch);
......
...@@ -185,58 +185,19 @@ static inline void cmf_activate(void *area, unsigned int onoff) ...@@ -185,58 +185,19 @@ static inline void cmf_activate(void *area, unsigned int onoff)
static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc, static int set_schib(struct ccw_device *cdev, u32 mme, int mbfc,
unsigned long address) unsigned long address)
{ {
int ret;
int retry;
struct subchannel *sch; struct subchannel *sch;
struct schib *schib;
sch = to_subchannel(cdev->dev.parent); sch = to_subchannel(cdev->dev.parent);
schib = &sch->schib;
/* msch can silently fail, so do it again if necessary */
for (retry = 0; retry < 3; retry++) {
/* prepare schib */
if (cio_update_schib(sch))
return -ENODEV;
schib->pmcw.mme = mme;
schib->pmcw.mbfc = mbfc;
/* address can be either a block address or a block index */
if (mbfc)
schib->mba = address;
else
schib->pmcw.mbi = address;
/* try to submit it */
switch(ret = msch_err(sch->schid, schib)) {
case 0:
break;
case 1:
case 2: /* in I/O or status pending */
ret = -EBUSY;
break;
case 3: /* subchannel is no longer valid */
ret = -ENODEV;
break;
default: /* msch caught an exception */
ret = -EINVAL;
break;
}
if (cio_update_schib(sch))
return -ENODEV;
if (ret)
break;
/* check if it worked */ sch->config.mme = mme;
if (schib->pmcw.mme == mme && sch->config.mbfc = mbfc;
schib->pmcw.mbfc == mbfc && /* address can be either a block address or a block index */
(mbfc ? (schib->mba == address) if (mbfc)
: (schib->pmcw.mbi == address))) sch->config.mba = address;
return 0; else
sch->config.mbi = address;
ret = -EINVAL; return cio_commit_config(sch);
}
return ret;
} }
struct set_schib_struct { struct set_schib_struct {
......
...@@ -128,8 +128,8 @@ css_free_subchannel(struct subchannel *sch) ...@@ -128,8 +128,8 @@ css_free_subchannel(struct subchannel *sch)
{ {
if (sch) { if (sch) {
/* Reset intparm to zeroes. */ /* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0; sch->config.intparm = 0;
cio_modify(sch); cio_commit_config(sch);
kfree(sch->lock); kfree(sch->lock);
kfree(sch); kfree(sch);
} }
......
...@@ -1020,8 +1020,8 @@ static void ccw_device_call_sch_unregister(struct work_struct *work) ...@@ -1020,8 +1020,8 @@ static void ccw_device_call_sch_unregister(struct work_struct *work)
sch = to_subchannel(cdev->dev.parent); sch = to_subchannel(cdev->dev.parent);
css_sch_device_unregister(sch); css_sch_device_unregister(sch);
/* Reset intparm to zeroes. */ /* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0; sch->config.intparm = 0;
cio_modify(sch); cio_commit_config(sch);
/* Release cdev reference for workqueue processing.*/ /* Release cdev reference for workqueue processing.*/
put_device(&cdev->dev); put_device(&cdev->dev);
/* Release subchannel reference for local processing. */ /* Release subchannel reference for local processing. */
...@@ -1148,8 +1148,8 @@ static void ccw_device_move_to_sch(struct work_struct *work) ...@@ -1148,8 +1148,8 @@ static void ccw_device_move_to_sch(struct work_struct *work)
spin_unlock_irq(former_parent->lock); spin_unlock_irq(former_parent->lock);
css_sch_device_unregister(former_parent); css_sch_device_unregister(former_parent);
/* Reset intparm to zeroes. */ /* Reset intparm to zeroes. */
former_parent->schib.pmcw.intparm = 0; former_parent->config.intparm = 0;
cio_modify(former_parent); cio_commit_config(former_parent);
} }
sch_attach_device(sch, cdev); sch_attach_device(sch, cdev);
out: out:
...@@ -1170,6 +1170,14 @@ static void io_subchannel_irq(struct subchannel *sch) ...@@ -1170,6 +1170,14 @@ static void io_subchannel_irq(struct subchannel *sch)
dev_fsm_event(cdev, DEV_EVENT_INTERRUPT); dev_fsm_event(cdev, DEV_EVENT_INTERRUPT);
} }
void io_subchannel_init_config(struct subchannel *sch)
{
memset(&sch->config, 0, sizeof(sch->config));
sch->config.csense = 1;
if ((sch->lpm & (sch->lpm - 1)) != 0)
sch->config.mp = 1;
}
static void io_subchannel_init_fields(struct subchannel *sch) static void io_subchannel_init_fields(struct subchannel *sch)
{ {
if (cio_is_console(sch->schid)) if (cio_is_console(sch->schid))
...@@ -1184,16 +1192,8 @@ static void io_subchannel_init_fields(struct subchannel *sch) ...@@ -1184,16 +1192,8 @@ static void io_subchannel_init_fields(struct subchannel *sch)
sch->schib.pmcw.dev, sch->schid.ssid, sch->schib.pmcw.dev, sch->schid.ssid,
sch->schid.sch_no, sch->schib.pmcw.pim, sch->schid.sch_no, sch->schib.pmcw.pim,
sch->schib.pmcw.pam, sch->schib.pmcw.pom); sch->schib.pmcw.pam, sch->schib.pmcw.pom);
/* Initially set up some fields in the pmcw. */
sch->schib.pmcw.ena = 0; io_subchannel_init_config(sch);
sch->schib.pmcw.csense = 1; /* concurrent sense */
if ((sch->lpm & (sch->lpm - 1)) != 0)
sch->schib.pmcw.mp = 1; /* multipath mode */
/* clean up possible residual cmf stuff */
sch->schib.pmcw.mme = 0;
sch->schib.pmcw.mbfc = 0;
sch->schib.pmcw.mbi = 0;
sch->schib.mba = 0;
} }
static void io_subchannel_do_unreg(struct work_struct *work) static void io_subchannel_do_unreg(struct work_struct *work)
...@@ -1203,8 +1203,8 @@ static void io_subchannel_do_unreg(struct work_struct *work) ...@@ -1203,8 +1203,8 @@ static void io_subchannel_do_unreg(struct work_struct *work)
sch = container_of(work, struct subchannel, work); sch = container_of(work, struct subchannel, work);
css_sch_device_unregister(sch); css_sch_device_unregister(sch);
/* Reset intparm to zeroes. */ /* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0; sch->config.intparm = 0;
cio_modify(sch); cio_commit_config(sch);
put_device(&sch->dev); put_device(&sch->dev);
} }
...@@ -1680,8 +1680,8 @@ static int io_subchannel_sch_event(struct subchannel *sch, int slow) ...@@ -1680,8 +1680,8 @@ static int io_subchannel_sch_event(struct subchannel *sch, int slow)
spin_lock_irqsave(sch->lock, flags); spin_lock_irqsave(sch->lock, flags);
/* Reset intparm to zeroes. */ /* Reset intparm to zeroes. */
sch->schib.pmcw.intparm = 0; sch->config.intparm = 0;
cio_modify(sch); cio_commit_config(sch);
break; break;
case REPROBE: case REPROBE:
ccw_device_trigger_reprobe(cdev); ccw_device_trigger_reprobe(cdev);
......
...@@ -76,6 +76,7 @@ extern wait_queue_head_t ccw_device_init_wq; ...@@ -76,6 +76,7 @@ extern wait_queue_head_t ccw_device_init_wq;
extern atomic_t ccw_device_init_count; extern atomic_t ccw_device_init_count;
void io_subchannel_recog_done(struct ccw_device *cdev); void io_subchannel_recog_done(struct ccw_device *cdev);
void io_subchannel_init_config(struct subchannel *sch);
int ccw_device_cancel_halt_clear(struct ccw_device *); int ccw_device_cancel_halt_clear(struct ccw_device *);
......
...@@ -1026,11 +1026,12 @@ void ccw_device_trigger_reprobe(struct ccw_device *cdev) ...@@ -1026,11 +1026,12 @@ void ccw_device_trigger_reprobe(struct ccw_device *cdev)
* we have before performing device selection :/ * we have before performing device selection :/
*/ */
sch->lpm = sch->schib.pmcw.pam & sch->opm; sch->lpm = sch->schib.pmcw.pam & sch->opm;
/* Re-set some bits in the pmcw that were lost. */ /*
sch->schib.pmcw.csense = 1; * Use the initial configuration since we can't be shure that the old
sch->schib.pmcw.ena = 0; * paths are valid.
if ((sch->lpm & (sch->lpm - 1)) != 0) */
sch->schib.pmcw.mp = 1; io_subchannel_init_config(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) {
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment