* [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method @ 2017-10-20 20:02 Eddie James 2017-10-20 20:30 ` Christopher Bostic 2017-10-23 0:31 ` Andrew Jeffery 0 siblings, 2 replies; 8+ messages in thread From: Eddie James @ 2017-10-20 20:02 UTC (permalink / raw) To: openbmc; +Cc: andrew, cbostic, Edward A. James From: "Edward A. James" <eajames@us.ibm.com> Driver wasn't cleaning up on timeout or in an error situation properly. Need to do a full reset if we fail in order to re-stablish a good state of the engine. Signed-off-by: Edward A. James <eajames@us.ibm.com> --- Changes since v1: * switch to TASK_UNINTERRUPTIBLE for waiting for command complete drivers/i2c/busses/i2c-fsi.c | 259 +++++++++++++++++++++++++++++++------------ 1 file changed, 191 insertions(+), 68 deletions(-) diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c index 1af9c01..fa44017 100644 --- a/drivers/i2c/busses/i2c-fsi.c +++ b/drivers/i2c/busses/i2c-fsi.c @@ -133,6 +133,11 @@ #define I2C_ESTAT_SELF_BUSY 0x00000040 #define I2C_ESTAT_VERSION 0x0000001f +#define I2C_PORT_BUSY_RESET 0x80000000 + +#define I2C_LOCAL_WAIT_TIMEOUT 2 /* jiffies */ +#define I2C_ABORT_TIMEOUT msecs_to_jiffies(100) + struct fsi_i2c_master { struct fsi_device *fsi; u8 fifo_size; @@ -351,22 +356,185 @@ static int fsi_i2c_read_fifo(struct fsi_i2c_port *port, struct i2c_msg *msg, return rc; } +static int fsi_i2c_reset_bus(struct fsi_i2c_master *i2c) +{ + int i, rc; + u32 mode, dummy = 0; + + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + + mode |= I2C_MODE_DIAG; + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + + for (i = 0; i < 9; ++i) { + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); + if (rc) + return rc; + + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); + if (rc) + return rc; + } + + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); + if (rc) + return rc; + + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); + if (rc) + return rc; + + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); + if (rc) + return rc; + + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); + if (rc) + return rc; + + mode &= ~I2C_MODE_DIAG; + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); + + return rc; +} + +static int fsi_i2c_reset(struct fsi_i2c_master *i2c, u16 port) +{ + int rc; + u32 mode, stat, dummy = 0; + + /* reset engine */ + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); + if (rc) + return rc; + + /* re-init engine */ + rc = fsi_i2c_dev_init(i2c); + if (rc) + return rc; + + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + + /* set port; default after reset is 0 */ + if (port) { + mode = SETFIELD(I2C_MODE_PORT, mode, port); + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); + if (rc) + return rc; + } + + /* reset busy register; hw workaround */ + dummy = I2C_PORT_BUSY_RESET; + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_PORT_BUSY, &dummy); + if (rc) + return rc; + + /* force bus reset */ + rc = fsi_i2c_reset_bus(i2c); + if (rc) + return rc; + + /* reset errors */ + dummy = 0; + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); + if (rc) + return rc; + + /* wait for command complete */ + set_current_state(TASK_UNINTERRUPTIBLE); + schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT); + + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); + if (rc) + return rc; + + if (stat & I2C_STAT_CMD_COMP) + return 0; + + /* failed to get command complete; reset engine again */ + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); + if (rc) + return rc; + + /* re-init engine again */ + rc = fsi_i2c_dev_init(i2c); + if (rc) + return rc; + + return 0; +} + +static int fsi_i2c_abort(struct fsi_i2c_port *port, u32 status) +{ + int rc; + unsigned long start; + u32 cmd = I2C_CMD_WITH_STOP; + struct fsi_device *fsi = port->master->fsi; + + rc = fsi_i2c_reset(port->master, port->port); + if (rc) + return rc; + + /* skip final stop command for these errors */ + if (status & (I2C_STAT_PARITY | I2C_STAT_LOST_ARB | I2C_STAT_STOP_ERR)) + return 0; + + rc = fsi_i2c_write_reg(fsi, I2C_FSI_CMD, &cmd); + if (rc) + return rc; + + start = jiffies; + + do { + rc = fsi_i2c_read_reg(fsi, I2C_FSI_STAT, &status); + if (rc) + return rc; + + if (status & I2C_STAT_CMD_COMP) + return 0; + + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) + return -EINTR; + + } while (time_after(start + I2C_ABORT_TIMEOUT, jiffies)); + + return -ETIME; +} + static int fsi_i2c_handle_status(struct fsi_i2c_port *port, struct i2c_msg *msg, u32 status) { int rc; u8 fifo_count; - struct fsi_i2c_master *i2c = port->master; - u32 dummy = 0; if (status & I2C_STAT_ERR) { - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); + rc = fsi_i2c_abort(port, status); if (rc) return rc; + if (status & I2C_STAT_INV_CMD) + return -EINVAL; + + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | + I2C_STAT_BE_ACCESS)) + return -EPROTO; + if (status & I2C_STAT_NACK) return -EFAULT; + if (status & I2C_STAT_LOST_ARB) + return -ECANCELED; + + if (status & I2C_STAT_STOP_ERR) + return -EBADMSG; + return -EIO; } @@ -396,9 +564,9 @@ static int fsi_i2c_handle_status(struct fsi_i2c_port *port, static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, unsigned long timeout) { - const unsigned long local_timeout = 2; /* jiffies */ u32 status = 0; - int rc; + int rc, rc_abort; + unsigned long start = jiffies; do { rc = fsi_i2c_read_reg(port->master->fsi, I2C_FSI_STAT, @@ -419,13 +587,21 @@ static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, continue; } - set_current_state(TASK_UNINTERRUPTIBLE); - schedule_timeout(local_timeout); - timeout = (timeout < local_timeout) ? 0 : - timeout - local_timeout; - } while (timeout); + set_current_state(TASK_INTERRUPTIBLE); + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) { + rc = -EINTR; + goto abort; + } + } while (time_after(start + timeout, jiffies)); - return -ETIME; + rc = -ETIME; + +abort: + rc_abort = fsi_i2c_abort(port, status); + if (rc_abort) + return rc_abort; + + return rc; } static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, @@ -469,72 +645,19 @@ static u32 fsi_i2c_functionality(struct i2c_adapter *adap) | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; } -static int fsi_i2c_low_level_recover_bus(struct fsi_i2c_master *i2c) -{ - int i, rc; - u32 mode, dummy = 0; - - rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); - if (rc) - return rc; - - mode |= I2C_MODE_DIAG; - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); - if (rc) - return rc; - - for (i = 0; i < 9; ++i) { - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); - if (rc) - return rc; - - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); - if (rc) - return rc; - } - - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); - if (rc) - return rc; - - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); - if (rc) - return rc; - - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); - if (rc) - return rc; - - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); - if (rc) - return rc; - - mode &= ~I2C_MODE_DIAG; - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); - - return rc; -} - static int fsi_i2c_recover_bus(struct i2c_adapter *adap) { int rc; - u32 dummy = 0; struct fsi_i2c_port *port = adap->algo_data; - struct fsi_i2c_master *i2c = port->master; + struct fsi_i2c_master *master = port->master; - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); + rc = fsi_i2c_lock_master(master, adap->timeout); if (rc) return rc; - rc = fsi_i2c_dev_init(i2c); - if (rc) - return rc; + rc = fsi_i2c_reset(master, port->port); - rc = fsi_i2c_low_level_recover_bus(i2c); - if (rc) - return rc; - - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); + fsi_i2c_unlock_master(master); return rc; } -- 1.8.3.1 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method 2017-10-20 20:02 [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method Eddie James @ 2017-10-20 20:30 ` Christopher Bostic 2017-10-23 0:31 ` Andrew Jeffery 1 sibling, 0 replies; 8+ messages in thread From: Christopher Bostic @ 2017-10-20 20:30 UTC (permalink / raw) To: Eddie James, openbmc; +Cc: andrew, Edward A. James Reviewed-by: Christopher Bostic <cbostic@linux.vnet.ibm.com> On 10/20/17 3:02 PM, Eddie James wrote: > From: "Edward A. James" <eajames@us.ibm.com> > > Driver wasn't cleaning up on timeout or in an error situation properly. > Need to do a full reset if we fail in order to re-stablish a good state > of the engine. > > Signed-off-by: Edward A. James <eajames@us.ibm.com> > --- > > Changes since v1: > * switch to TASK_UNINTERRUPTIBLE for waiting for command complete > > drivers/i2c/busses/i2c-fsi.c | 259 +++++++++++++++++++++++++++++++------------ > 1 file changed, 191 insertions(+), 68 deletions(-) > > diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c > index 1af9c01..fa44017 100644 > --- a/drivers/i2c/busses/i2c-fsi.c > +++ b/drivers/i2c/busses/i2c-fsi.c > @@ -133,6 +133,11 @@ > #define I2C_ESTAT_SELF_BUSY 0x00000040 > #define I2C_ESTAT_VERSION 0x0000001f > > +#define I2C_PORT_BUSY_RESET 0x80000000 > + > +#define I2C_LOCAL_WAIT_TIMEOUT 2 /* jiffies */ > +#define I2C_ABORT_TIMEOUT msecs_to_jiffies(100) > + > struct fsi_i2c_master { > struct fsi_device *fsi; > u8 fifo_size; > @@ -351,22 +356,185 @@ static int fsi_i2c_read_fifo(struct fsi_i2c_port *port, struct i2c_msg *msg, > return rc; > } > > +static int fsi_i2c_reset_bus(struct fsi_i2c_master *i2c) > +{ > + int i, rc; > + u32 mode, dummy = 0; > + > + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + if (rc) > + return rc; > + > + mode |= I2C_MODE_DIAG; > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + if (rc) > + return rc; > + > + for (i = 0; i < 9; ++i) { > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); > + if (rc) > + return rc; > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); > + if (rc) > + return rc; > + } > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); > + if (rc) > + return rc; > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); > + if (rc) > + return rc; > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); > + if (rc) > + return rc; > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); > + if (rc) > + return rc; > + > + mode &= ~I2C_MODE_DIAG; > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + > + return rc; > +} > + > +static int fsi_i2c_reset(struct fsi_i2c_master *i2c, u16 port) > +{ > + int rc; > + u32 mode, stat, dummy = 0; > + > + /* reset engine */ > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); > + if (rc) > + return rc; > + > + /* re-init engine */ > + rc = fsi_i2c_dev_init(i2c); > + if (rc) > + return rc; > + > + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + if (rc) > + return rc; > + > + /* set port; default after reset is 0 */ > + if (port) { > + mode = SETFIELD(I2C_MODE_PORT, mode, port); > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + if (rc) > + return rc; > + } > + > + /* reset busy register; hw workaround */ > + dummy = I2C_PORT_BUSY_RESET; > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_PORT_BUSY, &dummy); > + if (rc) > + return rc; > + > + /* force bus reset */ > + rc = fsi_i2c_reset_bus(i2c); > + if (rc) > + return rc; > + > + /* reset errors */ > + dummy = 0; > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > + if (rc) > + return rc; > + > + /* wait for command complete */ > + set_current_state(TASK_UNINTERRUPTIBLE); > + schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT); > + > + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); > + if (rc) > + return rc; > + > + if (stat & I2C_STAT_CMD_COMP) > + return 0; > + > + /* failed to get command complete; reset engine again */ > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); > + if (rc) > + return rc; > + > + /* re-init engine again */ > + rc = fsi_i2c_dev_init(i2c); > + if (rc) > + return rc; > + > + return 0; > +} > + > +static int fsi_i2c_abort(struct fsi_i2c_port *port, u32 status) > +{ > + int rc; > + unsigned long start; > + u32 cmd = I2C_CMD_WITH_STOP; > + struct fsi_device *fsi = port->master->fsi; > + > + rc = fsi_i2c_reset(port->master, port->port); > + if (rc) > + return rc; > + > + /* skip final stop command for these errors */ > + if (status & (I2C_STAT_PARITY | I2C_STAT_LOST_ARB | I2C_STAT_STOP_ERR)) > + return 0; > + > + rc = fsi_i2c_write_reg(fsi, I2C_FSI_CMD, &cmd); > + if (rc) > + return rc; > + > + start = jiffies; > + > + do { > + rc = fsi_i2c_read_reg(fsi, I2C_FSI_STAT, &status); > + if (rc) > + return rc; > + > + if (status & I2C_STAT_CMD_COMP) > + return 0; > + > + set_current_state(TASK_INTERRUPTIBLE); > + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) > + return -EINTR; > + > + } while (time_after(start + I2C_ABORT_TIMEOUT, jiffies)); > + > + return -ETIME; > +} > + > static int fsi_i2c_handle_status(struct fsi_i2c_port *port, > struct i2c_msg *msg, u32 status) > { > int rc; > u8 fifo_count; > - struct fsi_i2c_master *i2c = port->master; > - u32 dummy = 0; > > if (status & I2C_STAT_ERR) { > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > + rc = fsi_i2c_abort(port, status); > if (rc) > return rc; > > + if (status & I2C_STAT_INV_CMD) > + return -EINVAL; > + > + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | > + I2C_STAT_BE_ACCESS)) > + return -EPROTO; > + > if (status & I2C_STAT_NACK) > return -EFAULT; > > + if (status & I2C_STAT_LOST_ARB) > + return -ECANCELED; > + > + if (status & I2C_STAT_STOP_ERR) > + return -EBADMSG; > + > return -EIO; > } > > @@ -396,9 +564,9 @@ static int fsi_i2c_handle_status(struct fsi_i2c_port *port, > static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, > unsigned long timeout) > { > - const unsigned long local_timeout = 2; /* jiffies */ > u32 status = 0; > - int rc; > + int rc, rc_abort; > + unsigned long start = jiffies; > > do { > rc = fsi_i2c_read_reg(port->master->fsi, I2C_FSI_STAT, > @@ -419,13 +587,21 @@ static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, > continue; > } > > - set_current_state(TASK_UNINTERRUPTIBLE); > - schedule_timeout(local_timeout); > - timeout = (timeout < local_timeout) ? 0 : > - timeout - local_timeout; > - } while (timeout); > + set_current_state(TASK_INTERRUPTIBLE); > + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) { > + rc = -EINTR; > + goto abort; > + } > + } while (time_after(start + timeout, jiffies)); > > - return -ETIME; > + rc = -ETIME; > + > +abort: > + rc_abort = fsi_i2c_abort(port, status); > + if (rc_abort) > + return rc_abort; > + > + return rc; > } > > static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, > @@ -469,72 +645,19 @@ static u32 fsi_i2c_functionality(struct i2c_adapter *adap) > | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; > } > > -static int fsi_i2c_low_level_recover_bus(struct fsi_i2c_master *i2c) > -{ > - int i, rc; > - u32 mode, dummy = 0; > - > - rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); > - if (rc) > - return rc; > - > - mode |= I2C_MODE_DIAG; > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > - if (rc) > - return rc; > - > - for (i = 0; i < 9; ++i) { > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); > - if (rc) > - return rc; > - } > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); > - if (rc) > - return rc; > - > - mode &= ~I2C_MODE_DIAG; > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > - > - return rc; > -} > - > static int fsi_i2c_recover_bus(struct i2c_adapter *adap) > { > int rc; > - u32 dummy = 0; > struct fsi_i2c_port *port = adap->algo_data; > - struct fsi_i2c_master *i2c = port->master; > + struct fsi_i2c_master *master = port->master; > > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); > + rc = fsi_i2c_lock_master(master, adap->timeout); > if (rc) > return rc; > > - rc = fsi_i2c_dev_init(i2c); > - if (rc) > - return rc; > + rc = fsi_i2c_reset(master, port->port); > > - rc = fsi_i2c_low_level_recover_bus(i2c); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > + fsi_i2c_unlock_master(master); > > return rc; > } ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method 2017-10-20 20:02 [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method Eddie James 2017-10-20 20:30 ` Christopher Bostic @ 2017-10-23 0:31 ` Andrew Jeffery 2017-10-24 15:39 ` Eddie James 1 sibling, 1 reply; 8+ messages in thread From: Andrew Jeffery @ 2017-10-23 0:31 UTC (permalink / raw) To: Eddie James, openbmc; +Cc: cbostic, Edward A. James [-- Attachment #1: Type: text/plain, Size: 10312 bytes --] Hi Eddie, Apologies for not being more thorough initially, I'm back again with comments. On Fri, 2017-10-20 at 15:02 -0500, Eddie James wrote: > From: "Edward A. James" <eajames@us.ibm.com> > > Driver wasn't cleaning up on timeout or in an error situation properly. > Need to do a full reset if we fail in order to re-stablish a good state > of the engine. > > Signed-off-by: Edward A. James <eajames@us.ibm.com> > --- > > Changes since v1: > * switch to TASK_UNINTERRUPTIBLE for waiting for command complete > > drivers/i2c/busses/i2c-fsi.c | 259 +++++++++++++++++++++++++++++++------------ > 1 file changed, 191 insertions(+), 68 deletions(-) > > diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c > index 1af9c01..fa44017 100644 > --- a/drivers/i2c/busses/i2c-fsi.c > +++ b/drivers/i2c/busses/i2c-fsi.c > @@ -133,6 +133,11 @@ > #define I2C_ESTAT_SELF_BUSY 0x00000040 > #define I2C_ESTAT_VERSION 0x0000001f > > +#define I2C_PORT_BUSY_RESET 0x80000000 > + > +#define I2C_LOCAL_WAIT_TIMEOUT 2 /* jiffies */ > +#define I2C_ABORT_TIMEOUT msecs_to_jiffies(100) > + > struct fsi_i2c_master { > struct fsi_device *fsi; > u8 fifo_size; > @@ -351,22 +356,185 @@ static int fsi_i2c_read_fifo(struct fsi_i2c_port *port, struct i2c_msg *msg, > return rc; > } > > +static int fsi_i2c_reset_bus(struct fsi_i2c_master *i2c) > +{ > + int i, rc; > + u32 mode, dummy = 0; > + > + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + if (rc) > + return rc; > + > + mode |= I2C_MODE_DIAG; > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + if (rc) > + return rc; > + > + for (i = 0; i < 9; ++i) { > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); > + if (rc) > + return rc; > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); > + if (rc) > + return rc; I take it we're okay with cycling the bus as fast as we can, and not try to maintain any particular rate? Not suggesting you need to change anything, just asking the question. > + } > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); > + if (rc) > + return rc; > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); > + if (rc) > + return rc; > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); > + if (rc) > + return rc; > + > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); > + if (rc) > + return rc; > + > + mode &= ~I2C_MODE_DIAG; > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + > + return rc; > +} > + > +static int fsi_i2c_reset(struct fsi_i2c_master *i2c, u16 port) > +{ I can't see that this (the whole reset sequence) is called with pre-emption disabled, perhaps we should do so? It looks like this will be called in process context through fsi_i2c_xfer(). Stalling the recovery will just hold up other processes wanting to use the bus as we'll hold the bus mutex across the task switch. > + int rc; > + u32 mode, stat, dummy = 0; > + > + /* reset engine */ > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); > + if (rc) > + return rc; > + > + /* re-init engine */ > + rc = fsi_i2c_dev_init(i2c); > + if (rc) > + return rc; > + > + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + if (rc) > + return rc; > + > + /* set port; default after reset is 0 */ > + if (port) { > + mode = SETFIELD(I2C_MODE_PORT, mode, port); > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > + if (rc) > + return rc; > + } > + > + /* reset busy register; hw workaround */ > + dummy = I2C_PORT_BUSY_RESET; > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_PORT_BUSY, &dummy); > + if (rc) > + return rc; > + > + /* force bus reset */ > + rc = fsi_i2c_reset_bus(i2c); > + if (rc) > + return rc; > + > + /* reset errors */ > + dummy = 0; > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > + if (rc) > + return rc; > + > + /* wait for command complete */ > + set_current_state(TASK_UNINTERRUPTIBLE); > + schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT); > + > + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); > + if (rc) > + return rc; > + > + if (stat & I2C_STAT_CMD_COMP) > + return 0; > + > + /* failed to get command complete; reset engine again */ > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); > + if (rc) > + return rc; > + > + /* re-init engine again */ > + rc = fsi_i2c_dev_init(i2c); > + if (rc) > + return rc; > + > + return 0; > +} > + > +static int fsi_i2c_abort(struct fsi_i2c_port *port, u32 status) > +{ > + int rc; > + unsigned long start; > + u32 cmd = I2C_CMD_WITH_STOP; > + struct fsi_device *fsi = port->master->fsi; > + > + rc = fsi_i2c_reset(port->master, port->port); > + if (rc) > + return rc; > + > + /* skip final stop command for these errors */ > + if (status & (I2C_STAT_PARITY | I2C_STAT_LOST_ARB | I2C_STAT_STOP_ERR)) > + return 0; > + > + rc = fsi_i2c_write_reg(fsi, I2C_FSI_CMD, &cmd); > + if (rc) > + return rc; > + > + start = jiffies; > + > + do { > + rc = fsi_i2c_read_reg(fsi, I2C_FSI_STAT, &status); > + if (rc) > + return rc; > + > + if (status & I2C_STAT_CMD_COMP) > + return 0; > + > + set_current_state(TASK_INTERRUPTIBLE); > + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) > + return -EINTR; > + > + } while (time_after(start + I2C_ABORT_TIMEOUT, jiffies)); I'm a bit ignorant here, but why do we expect the abort operation to take up to 100ms? You mentioned you invented the numbers, but it's not clear what the justification is. > + > + return -ETIME; > +} > + > static int fsi_i2c_handle_status(struct fsi_i2c_port *port, > struct i2c_msg *msg, u32 status) > { > int rc; > u8 fifo_count; > - struct fsi_i2c_master *i2c = port->master; > - u32 dummy = 0; > > if (status & I2C_STAT_ERR) { > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > + rc = fsi_i2c_abort(port, status); > if (rc) > return rc; > > + if (status & I2C_STAT_INV_CMD) > + return -EINVAL; > + > + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | > + I2C_STAT_BE_ACCESS)) > + return -EPROTO; > + > if (status & I2C_STAT_NACK) > return -EFAULT; EFAULT is "Bad address", but slaves can NACK things that aren't addresses. As an example i2c-aspeed calls a NACK an EIO. > > + if (status & I2C_STAT_LOST_ARB) > + return -ECANCELED; So checking around the tree, all of i2c-aspeed, i2c-designware-core, i2c-hix5hd2, i2c-kempld to name a few (I stopped checking at that point) use -EAGAIN for arbitration loss. I don't think -ECANCELLED is appropriate, and certainly think that -EAGAIN is what we want: Nothing went wrong aside from this master lost the arbitration race, so the caller should really just retry. > + > + if (status & I2C_STAT_STOP_ERR) > + return -EBADMSG; > + > return -EIO; > } > > @@ -396,9 +564,9 @@ static int fsi_i2c_handle_status(struct fsi_i2c_port *port, > static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, > unsigned long timeout) > { > - const unsigned long local_timeout = 2; /* jiffies */ > u32 status = 0; > - int rc; > + int rc, rc_abort; > + unsigned long start = jiffies; > > do { > rc = fsi_i2c_read_reg(port->master->fsi, I2C_FSI_STAT, > @@ -419,13 +587,21 @@ static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, > continue; > } > > - set_current_state(TASK_UNINTERRUPTIBLE); > - schedule_timeout(local_timeout); > - timeout = (timeout < local_timeout) ? 0 : > - timeout - local_timeout; > - } while (timeout); > + set_current_state(TASK_INTERRUPTIBLE); > + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) { > + rc = -EINTR; > + goto abort; > + } > + } while (time_after(start + timeout, jiffies)); > > - return -ETIME; > + rc = -ETIME; > + > +abort: > + rc_abort = fsi_i2c_abort(port, status); > + if (rc_abort) > + return rc_abort; > + > + return rc; > } > > static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, > @@ -469,72 +645,19 @@ static u32 fsi_i2c_functionality(struct i2c_adapter *adap) > | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; > } > > -static int fsi_i2c_low_level_recover_bus(struct fsi_i2c_master *i2c) > -{ > - int i, rc; > - u32 mode, dummy = 0; > - > - rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); > - if (rc) > - return rc; > - > - mode |= I2C_MODE_DIAG; > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > - if (rc) > - return rc; > - > - for (i = 0; i < 9; ++i) { > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); > - if (rc) > - return rc; > - } > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); > - if (rc) > - return rc; > - > - mode &= ~I2C_MODE_DIAG; > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > - > - return rc; > -} > - > static int fsi_i2c_recover_bus(struct i2c_adapter *adap) > { > int rc; > - u32 dummy = 0; > struct fsi_i2c_port *port = adap->algo_data; > - struct fsi_i2c_master *i2c = port->master; > + struct fsi_i2c_master *master = port->master; > > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); > + rc = fsi_i2c_lock_master(master, adap->timeout); > if (rc) > return rc; > > - rc = fsi_i2c_dev_init(i2c); > - if (rc) > - return rc; > + rc = fsi_i2c_reset(master, port->port); > > - rc = fsi_i2c_low_level_recover_bus(i2c); > - if (rc) > - return rc; > - > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > + fsi_i2c_unlock_master(master); > > return rc; > } Cheers, Andrew [-- Attachment #2: This is a digitally signed message part --] [-- Type: application/pgp-signature, Size: 801 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method 2017-10-23 0:31 ` Andrew Jeffery @ 2017-10-24 15:39 ` Eddie James 2017-10-25 1:03 ` Andrew Jeffery 0 siblings, 1 reply; 8+ messages in thread From: Eddie James @ 2017-10-24 15:39 UTC (permalink / raw) To: Andrew Jeffery, openbmc; +Cc: Edward A. James, cbostic On 10/22/2017 07:31 PM, Andrew Jeffery wrote: > Hi Eddie, > > Apologies for not being more thorough initially, I'm back again with comments. > > On Fri, 2017-10-20 at 15:02 -0500, Eddie James wrote: >> From: "Edward A. James" <eajames@us.ibm.com> >> >> Driver wasn't cleaning up on timeout or in an error situation properly. >> Need to do a full reset if we fail in order to re-stablish a good state >> of the engine. >> >> Signed-off-by: Edward A. James <eajames@us.ibm.com> >> --- >> >> Changes since v1: >> * switch to TASK_UNINTERRUPTIBLE for waiting for command complete >> >> drivers/i2c/busses/i2c-fsi.c | 259 +++++++++++++++++++++++++++++++------------ >> 1 file changed, 191 insertions(+), 68 deletions(-) >> >> diff --git a/drivers/i2c/busses/i2c-fsi.c b/drivers/i2c/busses/i2c-fsi.c >> index 1af9c01..fa44017 100644 >> --- a/drivers/i2c/busses/i2c-fsi.c >> +++ b/drivers/i2c/busses/i2c-fsi.c >> @@ -133,6 +133,11 @@ >> #define I2C_ESTAT_SELF_BUSY 0x00000040 >> #define I2C_ESTAT_VERSION 0x0000001f >> >> +#define I2C_PORT_BUSY_RESET 0x80000000 >> + >> +#define I2C_LOCAL_WAIT_TIMEOUT 2 /* jiffies */ >> +#define I2C_ABORT_TIMEOUT msecs_to_jiffies(100) >> + >> struct fsi_i2c_master { >> struct fsi_device *fsi; >> u8 fifo_size; >> @@ -351,22 +356,185 @@ static int fsi_i2c_read_fifo(struct fsi_i2c_port *port, struct i2c_msg *msg, >> return rc; >> } >> >> +static int fsi_i2c_reset_bus(struct fsi_i2c_master *i2c) >> +{ >> + int i, rc; >> + u32 mode, dummy = 0; >> + >> + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); >> + if (rc) >> + return rc; >> + >> + mode |= I2C_MODE_DIAG; >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); >> + if (rc) >> + return rc; >> + >> + for (i = 0; i < 9; ++i) { >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); >> + if (rc) >> + return rc; >> + >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); >> + if (rc) >> + return rc; > I take it we're okay with cycling the bus as fast as we can, and not try to > maintain any particular rate? Not suggesting you need to change anything, just > asking the question. Yep, the spec doesn't give any indication of needing to rate-limit this toggling. This is always how it's been done for this master, and it seems to work. > >> + } >> + >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); >> + if (rc) >> + return rc; >> + >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); >> + if (rc) >> + return rc; >> + >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); >> + if (rc) >> + return rc; >> + >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); >> + if (rc) >> + return rc; >> + >> + mode &= ~I2C_MODE_DIAG; >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); >> + >> + return rc; >> +} >> + >> +static int fsi_i2c_reset(struct fsi_i2c_master *i2c, u16 port) >> +{ > I can't see that this (the whole reset sequence) is called with pre-emption > disabled, perhaps we should do so? It looks like this will be called in process > context through fsi_i2c_xfer(). Stalling the recovery will just hold up other > processes wanting to use the bus as we'll hold the bus mutex across the task > switch. Well, presumably we want to block other access to the bus? We need to get the master back in a good state before releasing it for other transfers. > >> + int rc; >> + u32 mode, stat, dummy = 0; >> + >> + /* reset engine */ >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); >> + if (rc) >> + return rc; >> + >> + /* re-init engine */ >> + rc = fsi_i2c_dev_init(i2c); >> + if (rc) >> + return rc; >> + >> + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); >> + if (rc) >> + return rc; >> + >> + /* set port; default after reset is 0 */ >> + if (port) { >> + mode = SETFIELD(I2C_MODE_PORT, mode, port); >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); >> + if (rc) >> + return rc; >> + } >> + >> + /* reset busy register; hw workaround */ >> + dummy = I2C_PORT_BUSY_RESET; >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_PORT_BUSY, &dummy); >> + if (rc) >> + return rc; >> + >> + /* force bus reset */ >> + rc = fsi_i2c_reset_bus(i2c); >> + if (rc) >> + return rc; >> + >> + /* reset errors */ >> + dummy = 0; >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); >> + if (rc) >> + return rc; >> + >> + /* wait for command complete */ >> + set_current_state(TASK_UNINTERRUPTIBLE); >> + schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT); >> + >> + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); >> + if (rc) >> + return rc; >> + >> + if (stat & I2C_STAT_CMD_COMP) >> + return 0; >> + >> + /* failed to get command complete; reset engine again */ >> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); >> + if (rc) >> + return rc; >> + >> + /* re-init engine again */ >> + rc = fsi_i2c_dev_init(i2c); >> + if (rc) >> + return rc; >> + >> + return 0; >> +} >> + >> +static int fsi_i2c_abort(struct fsi_i2c_port *port, u32 status) >> +{ >> + int rc; >> + unsigned long start; >> + u32 cmd = I2C_CMD_WITH_STOP; >> + struct fsi_device *fsi = port->master->fsi; >> + >> + rc = fsi_i2c_reset(port->master, port->port); >> + if (rc) >> + return rc; >> + >> + /* skip final stop command for these errors */ >> + if (status & (I2C_STAT_PARITY | I2C_STAT_LOST_ARB | I2C_STAT_STOP_ERR)) >> + return 0; >> + >> + rc = fsi_i2c_write_reg(fsi, I2C_FSI_CMD, &cmd); >> + if (rc) >> + return rc; >> + >> + start = jiffies; >> + >> + do { >> + rc = fsi_i2c_read_reg(fsi, I2C_FSI_STAT, &status); >> + if (rc) >> + return rc; >> + >> + if (status & I2C_STAT_CMD_COMP) >> + return 0; >> + >> + set_current_state(TASK_INTERRUPTIBLE); >> + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) >> + return -EINTR; >> + >> + } while (time_after(start + I2C_ABORT_TIMEOUT, jiffies)); > I'm a bit ignorant here, but why do we expect the abort operation to take up to > 100ms? You mentioned you invented the numbers, but it's not clear what the > justification is. I used the same timeout used in the FSP driver, which seems to work. I really have no other justification for this value. > >> + >> + return -ETIME; >> +} >> + >> static int fsi_i2c_handle_status(struct fsi_i2c_port *port, >> struct i2c_msg *msg, u32 status) >> { >> int rc; >> u8 fifo_count; >> - struct fsi_i2c_master *i2c = port->master; >> - u32 dummy = 0; >> >> if (status & I2C_STAT_ERR) { >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); >> + rc = fsi_i2c_abort(port, status); >> if (rc) >> return rc; >> >> + if (status & I2C_STAT_INV_CMD) >> + return -EINVAL; >> + >> + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | >> + I2C_STAT_BE_ACCESS)) >> + return -EPROTO; >> + >> if (status & I2C_STAT_NACK) >> return -EFAULT; > EFAULT is "Bad address", but slaves can NACK things that aren't addresses. > As an example i2c-aspeed calls a NACK an EIO. True, but I dislike returning EIO as it's somewhat of a default option and makes it very difficult to determine what actually went wrong. How about ENXIO? > >> >> + if (status & I2C_STAT_LOST_ARB) >> + return -ECANCELED; > So checking around the tree, all of i2c-aspeed, i2c-designware-core, > i2c-hix5hd2, i2c-kempld to name a few (I stopped checking at that point) use > -EAGAIN for arbitration loss. I don't think -ECANCELLED is appropriate, and > certainly think that -EAGAIN is what we want: Nothing went wrong aside from > this master lost the arbitration race, so the caller should really just retry. Good point, however, for this master, it can often return arbitration lost if the clock or data line is stuck, and retries will not work. I'll switch it to EAGAIN, callers shouldn't try too many times hopefully... Thanks! Eddie > >> + >> + if (status & I2C_STAT_STOP_ERR) >> + return -EBADMSG; >> + >> return -EIO; >> } >> >> @@ -396,9 +564,9 @@ static int fsi_i2c_handle_status(struct fsi_i2c_port *port, >> static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, >> unsigned long timeout) >> { >> - const unsigned long local_timeout = 2; /* jiffies */ >> u32 status = 0; >> - int rc; >> + int rc, rc_abort; >> + unsigned long start = jiffies; >> >> do { >> rc = fsi_i2c_read_reg(port->master->fsi, I2C_FSI_STAT, >> @@ -419,13 +587,21 @@ static int fsi_i2c_wait(struct fsi_i2c_port *port, struct i2c_msg *msg, >> continue; >> } >> >> - set_current_state(TASK_UNINTERRUPTIBLE); >> - schedule_timeout(local_timeout); >> - timeout = (timeout < local_timeout) ? 0 : >> - timeout - local_timeout; >> - } while (timeout); >> + set_current_state(TASK_INTERRUPTIBLE); >> + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) { >> + rc = -EINTR; >> + goto abort; >> + } >> + } while (time_after(start + timeout, jiffies)); >> >> - return -ETIME; >> + rc = -ETIME; >> + >> +abort: >> + rc_abort = fsi_i2c_abort(port, status); >> + if (rc_abort) >> + return rc_abort; >> + >> + return rc; >> } >> >> static int fsi_i2c_xfer(struct i2c_adapter *adap, struct i2c_msg *msgs, >> @@ -469,72 +645,19 @@ static u32 fsi_i2c_functionality(struct i2c_adapter *adap) >> | I2C_FUNC_SMBUS_EMUL | I2C_FUNC_SMBUS_BLOCK_DATA; >> } >> >> -static int fsi_i2c_low_level_recover_bus(struct fsi_i2c_master *i2c) >> -{ >> - int i, rc; >> - u32 mode, dummy = 0; >> - >> - rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); >> - if (rc) >> - return rc; >> - >> - mode |= I2C_MODE_DIAG; >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); >> - if (rc) >> - return rc; >> - >> - for (i = 0; i < 9; ++i) { >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); >> - if (rc) >> - return rc; >> - >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); >> - if (rc) >> - return rc; >> - } >> - >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SCL, &dummy); >> - if (rc) >> - return rc; >> - >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_SDA, &dummy); >> - if (rc) >> - return rc; >> - >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SCL, &dummy); >> - if (rc) >> - return rc; >> - >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_SET_SDA, &dummy); >> - if (rc) >> - return rc; >> - >> - mode &= ~I2C_MODE_DIAG; >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); >> - >> - return rc; >> -} >> - >> static int fsi_i2c_recover_bus(struct i2c_adapter *adap) >> { >> int rc; >> - u32 dummy = 0; >> struct fsi_i2c_port *port = adap->algo_data; >> - struct fsi_i2c_master *i2c = port->master; >> + struct fsi_i2c_master *master = port->master; >> >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); >> + rc = fsi_i2c_lock_master(master, adap->timeout); >> if (rc) >> return rc; >> >> - rc = fsi_i2c_dev_init(i2c); >> - if (rc) >> - return rc; >> + rc = fsi_i2c_reset(master, port->port); >> >> - rc = fsi_i2c_low_level_recover_bus(i2c); >> - if (rc) >> - return rc; >> - >> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); >> + fsi_i2c_unlock_master(master); >> >> return rc; >> } > Cheers, > > Andrew ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method 2017-10-24 15:39 ` Eddie James @ 2017-10-25 1:03 ` Andrew Jeffery 2017-10-27 17:10 ` Eddie James 0 siblings, 1 reply; 8+ messages in thread From: Andrew Jeffery @ 2017-10-25 1:03 UTC (permalink / raw) To: Eddie James, openbmc; +Cc: Edward A. James, cbostic [-- Attachment #1: Type: text/plain, Size: 7347 bytes --] > > > +static int fsi_i2c_reset(struct fsi_i2c_master *i2c, u16 port) > > > +{ > > > > I can't see that this (the whole reset sequence) is called with pre-emption > > disabled, perhaps we should do so? It looks like this will be called in process > > context through fsi_i2c_xfer(). Stalling the recovery will just hold up other > > processes wanting to use the bus as we'll hold the bus mutex across the task > > switch. > > Well, presumably we want to block other access to the bus? We need to > get the master back in a good state before releasing it for other transfers. Right, that's my point. By disabling pre-emption we do that as fast as possible by avoiding switching to other process contexts whilst holding the mutex. This would happen at the cost of processes that don't care about the bus (those that do are, or will, be waiting on the mutex anyway), but as we're performing a hardware recovery sequence I feel it should be prioritised. Only the context owning the mutex can recover the bus, therefore I think it's more useful to avoid switching to other process contexts until we finish the recovery. I guess we need to think about system robustness in that case though. Can we get stuck in this recovery sequence? fsi_*() calls time out if I recall correctly, so the answer would appear to be no, in which case this shouldn't be problematic. The system might be "sticky" if we start hitting FSI timeouts but beyond that things should continue (aside from either FSI or the I2C bus being broken). So this is all coming from my gut feeling that things like bus recovery would usually happen under a spinlock, which disables pre-emption. Maybe we should discuss that before getting down in the weeds like we are. > > > > > > + int rc; > > > + u32 mode, stat, dummy = 0; > > > + > > > + /* reset engine */ > > > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); > > > + if (rc) > > > + return rc; > > > + > > > + /* re-init engine */ > > > + rc = fsi_i2c_dev_init(i2c); > > > + if (rc) > > > + return rc; > > > + > > > + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); > > > + if (rc) > > > + return rc; > > > + > > > + /* set port; default after reset is 0 */ > > > + if (port) { > > > + mode = SETFIELD(I2C_MODE_PORT, mode, port); > > > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); > > > + if (rc) > > > + return rc; > > > + } > > > + > > > + /* reset busy register; hw workaround */ > > > + dummy = I2C_PORT_BUSY_RESET; > > > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_PORT_BUSY, &dummy); > > > + if (rc) > > > + return rc; > > > + > > > + /* force bus reset */ > > > + rc = fsi_i2c_reset_bus(i2c); > > > + if (rc) > > > + return rc; > > > + > > > + /* reset errors */ > > > + dummy = 0; > > > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > > > + if (rc) > > > + return rc; > > > + > > > + /* wait for command complete */ > > > + set_current_state(TASK_UNINTERRUPTIBLE); > > > + schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT); > > > + > > > + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); > > > + if (rc) > > > + return rc; > > > + > > > + if (stat & I2C_STAT_CMD_COMP) > > > + return 0; > > > + > > > + /* failed to get command complete; reset engine again */ > > > + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); > > > + if (rc) > > > + return rc; > > > + > > > + /* re-init engine again */ > > > + rc = fsi_i2c_dev_init(i2c); > > > + if (rc) > > > + return rc; > > > + > > > + return 0; > > > +} > > > + > > > +static int fsi_i2c_abort(struct fsi_i2c_port *port, u32 status) > > > +{ > > > + int rc; > > > + unsigned long start; > > > + u32 cmd = I2C_CMD_WITH_STOP; > > > + struct fsi_device *fsi = port->master->fsi; > > > + > > > + rc = fsi_i2c_reset(port->master, port->port); > > > + if (rc) > > > + return rc; > > > + > > > + /* skip final stop command for these errors */ > > > + if (status & (I2C_STAT_PARITY | I2C_STAT_LOST_ARB | I2C_STAT_STOP_ERR)) > > > + return 0; > > > + > > > + rc = fsi_i2c_write_reg(fsi, I2C_FSI_CMD, &cmd); > > > + if (rc) > > > + return rc; > > > + > > > + start = jiffies; > > > + > > > + do { > > > + rc = fsi_i2c_read_reg(fsi, I2C_FSI_STAT, &status); > > > + if (rc) > > > + return rc; > > > + > > > + if (status & I2C_STAT_CMD_COMP) > > > + return 0; > > > + > > > + set_current_state(TASK_INTERRUPTIBLE); > > > + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) > > > + return -EINTR; > > > + > > > + } while (time_after(start + I2C_ABORT_TIMEOUT, jiffies)); > > > > I'm a bit ignorant here, but why do we expect the abort operation to take up to > > 100ms? You mentioned you invented the numbers, but it's not clear what the > > justification is. > > I used the same timeout used in the FSP driver, which seems to work. I > really have no other justification for this value. That's probably worth a comment in the code. > > > > > > + > > > + return -ETIME; > > > +} > > > + > > > static int fsi_i2c_handle_status(struct fsi_i2c_port *port, > > > struct i2c_msg *msg, u32 status) > > > { > > > int rc; > > > u8 fifo_count; > > > - struct fsi_i2c_master *i2c = port->master; > > > - u32 dummy = 0; > > > > > > if (status & I2C_STAT_ERR) { > > > - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > > > + rc = fsi_i2c_abort(port, status); > > > if (rc) > > > return rc; > > > > > > + if (status & I2C_STAT_INV_CMD) > > > + return -EINVAL; > > > + > > > + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | > > > + I2C_STAT_BE_ACCESS)) > > > + return -EPROTO; > > > + > > > if (status & I2C_STAT_NACK) > > > return -EFAULT; > > > > EFAULT is "Bad address", but slaves can NACK things that aren't addresses. > > As an example i2c-aspeed calls a NACK an EIO. > > True, but I dislike returning EIO as it's somewhat of a default option > and makes it very difficult to determine what actually went wrong. How > about ENXIO? What does userspace do with that knowledge? Is it worth differentiating? I can't immediately see that it is. Maybe it's worth a dev_dbg() or something? I feel ENXIO doesn't address my point either, as it means "No such device or address", which is still inappropriate for data NACKs. > > > > > > > > > + if (status & I2C_STAT_LOST_ARB) > > > + return -ECANCELED; > > > > So checking around the tree, all of i2c-aspeed, i2c-designware-core, > > i2c-hix5hd2, i2c-kempld to name a few (I stopped checking at that point) use > > -EAGAIN for arbitration loss. I don't think -ECANCELLED is appropriate, and > > certainly think that -EAGAIN is what we want: Nothing went wrong aside from > > this master lost the arbitration race, so the caller should really just retry. > > Good point, however, for this master, it can often return arbitration > lost if the clock or data line is stuck, and retries will not work. I'll > switch it to EAGAIN, callers shouldn't try too many times hopefully... Can we test status again when a new transfer is starting to see if we need to recover the bus before proceeding? Cheers, Andrew [-- Attachment #2: This is a digitally signed message part --] [-- Type: application/pgp-signature, Size: 801 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method 2017-10-25 1:03 ` Andrew Jeffery @ 2017-10-27 17:10 ` Eddie James 2017-10-27 20:41 ` Andrew Jeffery 0 siblings, 1 reply; 8+ messages in thread From: Eddie James @ 2017-10-27 17:10 UTC (permalink / raw) To: Andrew Jeffery, openbmc; +Cc: Edward A. James, cbostic On 10/24/2017 08:03 PM, Andrew Jeffery wrote: >>>> +static int fsi_i2c_reset(struct fsi_i2c_master *i2c, u16 port) >>>> +{ >>> >>> I can't see that this (the whole reset sequence) is called with pre-emption >>> disabled, perhaps we should do so? It looks like this will be called in process >>> context through fsi_i2c_xfer(). Stalling the recovery will just hold up other >>> processes wanting to use the bus as we'll hold the bus mutex across the task >>> switch. >> >> Well, presumably we want to block other access to the bus? We need to >> get the master back in a good state before releasing it for other transfers. > Right, that's my point. By disabling pre-emption we do that as fast as > possible by avoiding switching to other process contexts whilst holding the > mutex. This would happen at the cost of processes that don't care about the > bus (those that do are, or will, be waiting on the mutex anyway), but as we're > performing a hardware recovery sequence I feel it should be prioritised. Only > the context owning the mutex can recover the bus, therefore I think it's more > useful to avoid switching to other process contexts until we finish the > recovery. > > I guess we need to think about system robustness in that case though. Can we > get stuck in this recovery sequence? fsi_*() calls time out if I recall > correctly, so the answer would appear to be no, in which case this shouldn't be > problematic. The system might be "sticky" if we start hitting FSI timeouts but > beyond that things should continue (aside from either FSI or the I2C bus being > broken). > > So this is all coming from my gut feeling that things like bus recovery would > usually happen under a spinlock, which disables pre-emption. Maybe we should > discuss that before getting down in the weeds like we are. Yea, that makes sense, I can add a spinlock here. I don't think FSI will hang. If it does, we will have similar problems in spinlocks in the SBEFIFO driver. > >> >>> >>>> + int rc; >>>> + u32 mode, stat, dummy = 0; >>>> + >>>> + /* reset engine */ >>>> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + /* re-init engine */ >>>> + rc = fsi_i2c_dev_init(i2c); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_MODE, &mode); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + /* set port; default after reset is 0 */ >>>> + if (port) { >>>> + mode = SETFIELD(I2C_MODE_PORT, mode, port); >>>> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_MODE, &mode); >>>> + if (rc) >>>> + return rc; >>>> + } >>>> + >>>> + /* reset busy register; hw workaround */ >>>> + dummy = I2C_PORT_BUSY_RESET; >>>> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_PORT_BUSY, &dummy); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + /* force bus reset */ >>>> + rc = fsi_i2c_reset_bus(i2c); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + /* reset errors */ >>>> + dummy = 0; >>>> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + /* wait for command complete */ >>>> + set_current_state(TASK_UNINTERRUPTIBLE); >>>> + schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT); >>>> + >>>> + rc = fsi_i2c_read_reg(i2c->fsi, I2C_FSI_STAT, &stat); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + if (stat & I2C_STAT_CMD_COMP) >>>> + return 0; >>>> + >>>> + /* failed to get command complete; reset engine again */ >>>> + rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_I2C, &dummy); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + /* re-init engine again */ >>>> + rc = fsi_i2c_dev_init(i2c); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + return 0; >>>> +} >>>> + >>>> +static int fsi_i2c_abort(struct fsi_i2c_port *port, u32 status) >>>> +{ >>>> + int rc; >>>> + unsigned long start; >>>> + u32 cmd = I2C_CMD_WITH_STOP; >>>> + struct fsi_device *fsi = port->master->fsi; >>>> + >>>> + rc = fsi_i2c_reset(port->master, port->port); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + /* skip final stop command for these errors */ >>>> + if (status & (I2C_STAT_PARITY | I2C_STAT_LOST_ARB | I2C_STAT_STOP_ERR)) >>>> + return 0; >>>> + >>>> + rc = fsi_i2c_write_reg(fsi, I2C_FSI_CMD, &cmd); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + start = jiffies; >>>> + >>>> + do { >>>> + rc = fsi_i2c_read_reg(fsi, I2C_FSI_STAT, &status); >>>> + if (rc) >>>> + return rc; >>>> + >>>> + if (status & I2C_STAT_CMD_COMP) >>>> + return 0; >>>> + >>>> + set_current_state(TASK_INTERRUPTIBLE); >>>> + if (schedule_timeout(I2C_LOCAL_WAIT_TIMEOUT) > 0) >>>> + return -EINTR; >>>> + >>>> + } while (time_after(start + I2C_ABORT_TIMEOUT, jiffies)); >>> >>> I'm a bit ignorant here, but why do we expect the abort operation to take up to >>> 100ms? You mentioned you invented the numbers, but it's not clear what the >>> justification is. >> >> I used the same timeout used in the FSP driver, which seems to work. I >> really have no other justification for this value. > That's probably worth a comment in the code. > >> >>> >>>> + >>>> + return -ETIME; >>>> +} >>>> + >>>> static int fsi_i2c_handle_status(struct fsi_i2c_port *port, >>>> struct i2c_msg *msg, u32 status) >>>> { >>>> int rc; >>>> u8 fifo_count; >>>> - struct fsi_i2c_master *i2c = port->master; >>>> - u32 dummy = 0; >>>> >>>> if (status & I2C_STAT_ERR) { >>>> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); >>>> + rc = fsi_i2c_abort(port, status); >>>> if (rc) >>>> return rc; >>>> >>>> + if (status & I2C_STAT_INV_CMD) >>>> + return -EINVAL; >>>> + >>>> + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | >>>> + I2C_STAT_BE_ACCESS)) >>>> + return -EPROTO; >>>> + >>>> if (status & I2C_STAT_NACK) >>>> return -EFAULT; >>> >>> EFAULT is "Bad address", but slaves can NACK things that aren't addresses. >>> As an example i2c-aspeed calls a NACK an EIO. >> >> True, but I dislike returning EIO as it's somewhat of a default option >> and makes it very difficult to determine what actually went wrong. How >> about ENXIO? > What does userspace do with that knowledge? Is it worth differentiating? I > can't immediately see that it is. Maybe it's worth a dev_dbg() or something? > I feel ENXIO doesn't address my point either, as it means "No such device or > address", which is still inappropriate for data NACKs. Well, when someone sees an i2c failure and opens a defect and assigns it to me, I would like to know if it was a NACK or something else :) If I see EIO, it could be either some other I2C error, an FSI bus error, o probably even a GPIO error. And dev_dbg probably won't help in the field since it'll be compiled out. > >> >>> >>>> >>>> + if (status & I2C_STAT_LOST_ARB) >>>> + return -ECANCELED; >>> >>> So checking around the tree, all of i2c-aspeed, i2c-designware-core, >>> i2c-hix5hd2, i2c-kempld to name a few (I stopped checking at that point) use >>> -EAGAIN for arbitration loss. I don't think -ECANCELLED is appropriate, and >>> certainly think that -EAGAIN is what we want: Nothing went wrong aside from >>> this master lost the arbitration race, so the caller should really just retry. >> >> Good point, however, for this master, it can often return arbitration >> lost if the clock or data line is stuck, and retries will not work. I'll >> switch it to EAGAIN, callers shouldn't try too many times hopefully... > Can we test status again when a new transfer is starting to see if we need to > recover the bus before proceeding? Well with this change, bus should be recovered after any failure, if recovery is possible. I more meant the case when the clock or data line is really stuck such that the master cannot recover them. Thanks for the review! Eddie > > Cheers, > > Andrew ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method 2017-10-27 17:10 ` Eddie James @ 2017-10-27 20:41 ` Andrew Jeffery 2017-10-30 16:27 ` Eddie James 0 siblings, 1 reply; 8+ messages in thread From: Andrew Jeffery @ 2017-10-27 20:41 UTC (permalink / raw) To: Eddie James, openbmc; +Cc: Edward A. James, cbostic > >>>> + > >>>> + return -ETIME; > >>>> +} > >>>> + > >>>> static int fsi_i2c_handle_status(struct fsi_i2c_port *port, > >>>> struct i2c_msg *msg, u32 status) > >>>> { > >>>> int rc; > >>>> u8 fifo_count; > >>>> - struct fsi_i2c_master *i2c = port->master; > >>>> - u32 dummy = 0; > >>>> > >>>> if (status & I2C_STAT_ERR) { > >>>> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); > >>>> + rc = fsi_i2c_abort(port, status); > >>>> if (rc) > >>>> return rc; > >>>> > >>>> + if (status & I2C_STAT_INV_CMD) > >>>> + return -EINVAL; > >>>> + > >>>> + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | > >>>> + I2C_STAT_BE_ACCESS)) > >>>> + return -EPROTO; > >>>> + > >>>> if (status & I2C_STAT_NACK) > >>>> return -EFAULT; > >>> > >>> EFAULT is "Bad address", but slaves can NACK things that aren't addresses. > >>> As an example i2c-aspeed calls a NACK an EIO. > >> > >> True, but I dislike returning EIO as it's somewhat of a default option > >> and makes it very difficult to determine what actually went wrong. How > >> about ENXIO? > > What does userspace do with that knowledge? Is it worth differentiating? I > > can't immediately see that it is. Maybe it's worth a dev_dbg() or something? > > I feel ENXIO doesn't address my point either, as it means "No such device or > > address", which is still inappropriate for data NACKs. > > Well, when someone sees an i2c failure and opens a defect and assigns it > to me, I would like to know if it was a NACK or something else :) If I > see EIO, it could be either some other I2C error, an FSI bus error, o > probably even a GPIO error. And dev_dbg probably won't help in the field > since it'll be compiled out. That's not true currently as we set CONFIG_DYNAMIC_DEBUG: https://github.com/openbmc/openbmc/blob/51af974f81c1d8a6df250fb38c27c1a0444b2cf3/meta-openbmc-bsp/meta-aspeed/meta-ast2500/recipes-kernel/linux/linux-obmc/defconfig#L200 unless we plan to change that? You can control it via debugfs. We really must return something sensible with respect to the error that occurred, and neither EFAULT nor ENXIO fit that bill. > > > > >> > >>> > >>>> > >>>> + if (status & I2C_STAT_LOST_ARB) > >>>> + return -ECANCELED; > >>> > >>> So checking around the tree, all of i2c-aspeed, i2c-designware-core, > >>> i2c-hix5hd2, i2c-kempld to name a few (I stopped checking at that point) use > >>> -EAGAIN for arbitration loss. I don't think -ECANCELLED is appropriate, and > >>> certainly think that -EAGAIN is what we want: Nothing went wrong aside from > >>> this master lost the arbitration race, so the caller should really just retry. > >> > >> Good point, however, for this master, it can often return arbitration > >> lost if the clock or data line is stuck, and retries will not work. I'll > >> switch it to EAGAIN, callers shouldn't try too many times hopefully... > > Can we test status again when a new transfer is starting to see if we need to > > recover the bus before proceeding? > > Well with this change, bus should be recovered after any failure, if > recovery is possible. I more meant the case when the clock or data line > is really stuck such that the master cannot recover them. What do we do in this case then? Power cycle? Andrew ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method 2017-10-27 20:41 ` Andrew Jeffery @ 2017-10-30 16:27 ` Eddie James 0 siblings, 0 replies; 8+ messages in thread From: Eddie James @ 2017-10-30 16:27 UTC (permalink / raw) To: Andrew Jeffery, openbmc; +Cc: Edward A. James, cbostic On 10/27/2017 03:41 PM, Andrew Jeffery wrote: >>>>>> + >>>>>> + return -ETIME; >>>>>> +} >>>>>> + >>>>>> static int fsi_i2c_handle_status(struct fsi_i2c_port *port, >>>>>> struct i2c_msg *msg, u32 status) >>>>>> { >>>>>> int rc; >>>>>> u8 fifo_count; >>>>>> - struct fsi_i2c_master *i2c = port->master; >>>>>> - u32 dummy = 0; >>>>>> >>>>>> if (status & I2C_STAT_ERR) { >>>>>> - rc = fsi_i2c_write_reg(i2c->fsi, I2C_FSI_RESET_ERR, &dummy); >>>>>> + rc = fsi_i2c_abort(port, status); >>>>>> if (rc) >>>>>> return rc; >>>>>> >>>>>> + if (status & I2C_STAT_INV_CMD) >>>>>> + return -EINVAL; >>>>>> + >>>>>> + if (status & (I2C_STAT_PARITY | I2C_STAT_BE_OVERRUN | >>>>>> + I2C_STAT_BE_ACCESS)) >>>>>> + return -EPROTO; >>>>>> + >>>>>> if (status & I2C_STAT_NACK) >>>>>> return -EFAULT; >>>>> >>>>> EFAULT is "Bad address", but slaves can NACK things that aren't addresses. >>>>> As an example i2c-aspeed calls a NACK an EIO. >>>> >>>> True, but I dislike returning EIO as it's somewhat of a default option >>>> and makes it very difficult to determine what actually went wrong. How >>>> about ENXIO? >>> What does userspace do with that knowledge? Is it worth differentiating? I >>> can't immediately see that it is. Maybe it's worth a dev_dbg() or something? >>> I feel ENXIO doesn't address my point either, as it means "No such device or >>> address", which is still inappropriate for data NACKs. >> Well, when someone sees an i2c failure and opens a defect and assigns it >> to me, I would like to know if it was a NACK or something else :) If I >> see EIO, it could be either some other I2C error, an FSI bus error, o >> probably even a GPIO error. And dev_dbg probably won't help in the field >> since it'll be compiled out. > That's not true currently as we set CONFIG_DYNAMIC_DEBUG: > > https://github.com/openbmc/openbmc/blob/51af974f81c1d8a6df250fb38c27c1a0444b2cf3/meta-openbmc-bsp/meta-aspeed/meta-ast2500/recipes-kernel/linux/linux-obmc/defconfig#L200 > > unless we plan to change that? You can control it via debugfs. We really > must return something sensible with respect to the error that occurred, > and neither EFAULT nor ENXIO fit that bill. Ah, didn't know that was enabled, thanks! However, that still doesn't help the situation where we hit the failure and can't recreate it after enabling that level of tracing... I still argue for ENXIO. The Broadcom iProc I2C driver uses ENXIO for both data and addr NACKs: http://elixir.free-electrons.com/linux/latest/source/drivers/i2c/busses/i2c-bcm-iproc.c#L231 I forgot to switch ECANCELED to EAGAIN in v3, so I'll send up v4. Cheers, Eddie >>>> >>>>> >>>>>> >>>>>> + if (status & I2C_STAT_LOST_ARB) >>>>>> + return -ECANCELED; >>>>> >>>>> So checking around the tree, all of i2c-aspeed, i2c-designware-core, >>>>> i2c-hix5hd2, i2c-kempld to name a few (I stopped checking at that point) use >>>>> -EAGAIN for arbitration loss. I don't think -ECANCELLED is appropriate, and >>>>> certainly think that -EAGAIN is what we want: Nothing went wrong aside from >>>>> this master lost the arbitration race, so the caller should really just retry. >>>> >>>> Good point, however, for this master, it can often return arbitration >>>> lost if the clock or data line is stuck, and retries will not work. I'll >>>> switch it to EAGAIN, callers shouldn't try too many times hopefully... >>> Can we test status again when a new transfer is starting to see if we need to >>> recover the bus before proceeding? >> Well with this change, bus should be recovered after any failure, if >> recovery is possible. I more meant the case when the clock or data line >> is really stuck such that the master cannot recover them. > What do we do in this case then? Power cycle? Yes... Shouldn't happen except for a hardware fault on the slave side, from my experience. > > Andrew > ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2017-10-30 16:27 UTC | newest] Thread overview: 8+ messages (download: mbox.gz follow: Atom feed -- links below jump to the message on this page -- 2017-10-20 20:02 [PATCH linux dev-4.10 v2] drivers: i2c: fsi: Add proper abort method Eddie James 2017-10-20 20:30 ` Christopher Bostic 2017-10-23 0:31 ` Andrew Jeffery 2017-10-24 15:39 ` Eddie James 2017-10-25 1:03 ` Andrew Jeffery 2017-10-27 17:10 ` Eddie James 2017-10-27 20:41 ` Andrew Jeffery 2017-10-30 16:27 ` Eddie James
This is an external index of several public inboxes, see mirroring instructions on how to clone and mirror all data and code used by this external index.