diff options
author | ian <ian@FreeBSD.org> | 2015-10-20 21:20:34 +0000 |
---|---|---|
committer | ian <ian@FreeBSD.org> | 2015-10-20 21:20:34 +0000 |
commit | 011586e8add052f42fb85aa191f7de614cbb6250 (patch) | |
tree | 3266f74294e3a1c34bb493f1afcc626d9d723ccc /sys/dev/iicbus/iiconf.c | |
parent | 8db7e0379c3e878a653101ef72e1067985e03b2d (diff) | |
download | FreeBSD-src-011586e8add052f42fb85aa191f7de614cbb6250.zip FreeBSD-src-011586e8add052f42fb85aa191f7de614cbb6250.tar.gz |
MFC r281828, r289083, r289084, r289091, r289093, r289095, r289097, r289098,
r289104, r289105, r289118: various i2c fixes...
Fix numerous issues in iic(4) and iicbus(4):
--Allow multiple open iic fds by storing addressing state in cdevpriv
--Fix, as much as possible, the baked-in race conditions in the iic
ioctl interface by requesting bus ownership on I2CSTART, releasing it on
I2CSTOP/I2CRSTCARD, and requiring bus ownership by the current cdevpriv
to use the I/O ioctls
--Reduce internal iic buffer size and remove 1K read/write limit by
iteratively calling iicbus_read/iicbus_write
--Eliminate dynamic allocation in I2CWRITE/I2CREAD
--Move handling of I2CRDWR to separate function and improve error handling
--Add new I2CSADDR ioctl to store address in current cdevpriv so that
I2CSTART is not needed for read(2)/write(2) to work
--Redesign iicbus_request_bus() and iicbus_release_bus():
--iicbus_request_bus() no longer falls through if the bus is already
owned by the requesting device. Multiple threads on the same device may
want exclusive access. Also, iicbus_release_bus() was never
device-recursive anyway.
--Previously, if IICBUS_CALLBACK failed in iicbus_release_bus(), but
the following iicbus_poll() call succeeded, IICBUS_CALLBACK would not be
issued again
--Do not hold iicbus mtx during IICBUS_CALLBACK call. There are
several drivers that may sleep in IICBUS_CALLBACK, if IIC_WAIT is passed.
--Do not loop in iicbus_request_bus if IICBUS_CALLBACK returns
EWOULDBLOCK; instead pass that to the caller so that it can retry if so
desired.
Bugfix: Exit the transfer loop if any read or write operation fails. Also,
perform a stop operation on the bus if there was an error, otherwise the
bus will remain hung forever. Consistantly use 'if (error != 0)' style in
the function.
Mostly rewrite the imx i2c driver. This started out as an attempt to fix
one specific problem: the driver didn't check for ACK/NAK after writing a
slave address byte to the bus, and some slaves signal that they are busy
(such as when completing an internal write to flash memory) by sending a
NAK in response to being addressed.
Use IIC_EBUSBSY and IIC_BUSERR status values consistantly across all drivers.
Make it clearer what each one means in the comments that define them.
Add iic2errno(), a helper function to translate IIC_Exxxxx status values to
errno values that are at least vaguely equivelent. Also add a new status
value, IIC_ERESOURCE, to indicate a failure to acquire memory or other
required resources to complete a transaction.
Return only IIC_Exxxx status values from iicbus-layer functions. Most of
these functions are thin wrappers around calling the hardware-layer driver,
but some of them do sanity checks and return an error.
Add a short name, IIC_INTRWAIT, for the common case (IIC_INTR | IIC_WAIT).
Replace a local sx lock that allowed only one client at a time to access
an eeprom device with iicbus_request/release_bus(), which achieves the
same effect and also keeps other i2c slave drivers from clashing on the bus.
Diffstat (limited to 'sys/dev/iicbus/iiconf.c')
-rw-r--r-- | sys/dev/iicbus/iiconf.c | 129 |
1 files changed, 86 insertions, 43 deletions
diff --git a/sys/dev/iicbus/iiconf.c b/sys/dev/iicbus/iiconf.c index 940760b..e28d341 100644 --- a/sys/dev/iicbus/iiconf.c +++ b/sys/dev/iicbus/iiconf.c @@ -40,6 +40,28 @@ __FBSDID("$FreeBSD$"); #include "iicbus_if.h" /* + * Translate IIC_Exxxxx status values to vaguely-equivelent errno values. + */ +int +iic2errno(int iic_status) +{ + switch (iic_status) { + case IIC_NOERR: return (0); + case IIC_EBUSERR: return (EALREADY); + case IIC_ENOACK: return (EIO); + case IIC_ETIMEOUT: return (ETIMEDOUT); + case IIC_EBUSBSY: return (EWOULDBLOCK); + case IIC_ESTATUS: return (EPROTO); + case IIC_EUNDERFLOW: return (EIO); + case IIC_EOVERFLOW: return (EOVERFLOW); + case IIC_ENOTSUPP: return (EOPNOTSUPP); + case IIC_ENOADDR: return (EADDRNOTAVAIL); + case IIC_ERESOURCE: return (ENOMEM); + default: return (EIO); + } +} + +/* * iicbus_intr() */ void @@ -70,8 +92,7 @@ iicbus_poll(struct iicbus_softc *sc, int how) break; default: - return (EWOULDBLOCK); - break; + return (IIC_EBUSBSY); } return (error); @@ -90,31 +111,32 @@ iicbus_request_bus(device_t bus, device_t dev, int how) struct iicbus_softc *sc = (struct iicbus_softc *)device_get_softc(bus); int error = 0; - /* first, ask the underlying layers if the request is ok */ IICBUS_LOCK(sc); - do { - error = IICBUS_CALLBACK(device_get_parent(bus), - IIC_REQUEST_BUS, (caddr_t)&how); - if (error) - error = iicbus_poll(sc, how); - } while (error == EWOULDBLOCK); - - while (!error) { - if (sc->owner && sc->owner != dev) { - error = iicbus_poll(sc, how); - } else { - sc->owner = dev; + while ((error == 0) && (sc->owner != NULL)) + error = iicbus_poll(sc, how); + + if (error == 0) { + sc->owner = dev; + /* + * Drop the lock around the call to the bus driver. + * This call should be allowed to sleep in the IIC_WAIT case. + * Drivers might also need to grab locks that would cause LOR + * if our lock is held. + */ + IICBUS_UNLOCK(sc); + /* Ask the underlying layers if the request is ok */ + error = IICBUS_CALLBACK(device_get_parent(bus), + IIC_REQUEST_BUS, (caddr_t)&how); + IICBUS_LOCK(sc); - IICBUS_UNLOCK(sc); - return (0); + if (error != 0) { + sc->owner = NULL; + wakeup_one(sc); } - - /* free any allocated resource */ - if (error) - IICBUS_CALLBACK(device_get_parent(bus), IIC_RELEASE_BUS, - (caddr_t)&how); } + + IICBUS_UNLOCK(sc); return (error); @@ -131,26 +153,33 @@ iicbus_release_bus(device_t bus, device_t dev) struct iicbus_softc *sc = (struct iicbus_softc *)device_get_softc(bus); int error; - /* first, ask the underlying layers if the release is ok */ - error = IICBUS_CALLBACK(device_get_parent(bus), IIC_RELEASE_BUS, NULL); - - if (error) - return (error); - IICBUS_LOCK(sc); if (sc->owner != dev) { IICBUS_UNLOCK(sc); - return (EACCES); + return (IIC_EBUSBSY); } - sc->owner = NULL; - - /* wakeup waiting processes */ - wakeup(sc); + /* + * Drop the lock around the call to the bus driver. + * This call should be allowed to sleep in the IIC_WAIT case. + * Drivers might also need to grab locks that would cause LOR + * if our lock is held. + */ IICBUS_UNLOCK(sc); + /* Ask the underlying layers if the release is ok */ + error = IICBUS_CALLBACK(device_get_parent(bus), IIC_RELEASE_BUS, NULL); - return (0); + if (error == 0) { + IICBUS_LOCK(sc); + sc->owner = NULL; + + /* wakeup a waiting thread */ + wakeup_one(sc); + IICBUS_UNLOCK(sc); + } + + return (error); } /* @@ -178,7 +207,7 @@ iicbus_start(device_t bus, u_char slave, int timeout) int error = 0; if (sc->started) - return (EINVAL); /* bus already started */ + return (IIC_ESTATUS); /* protocol error, bus already started */ if (!(error = IICBUS_START(device_get_parent(bus), slave, timeout))) sc->started = slave; @@ -200,7 +229,7 @@ iicbus_repeated_start(device_t bus, u_char slave, int timeout) int error = 0; if (!sc->started) - return (EINVAL); /* bus should have been already started */ + return (IIC_ESTATUS); /* protocol error, bus not started */ if (!(error = IICBUS_REPEATED_START(device_get_parent(bus), slave, timeout))) sc->started = slave; @@ -222,7 +251,7 @@ iicbus_stop(device_t bus) int error = 0; if (!sc->started) - return (EINVAL); /* bus not started */ + return (IIC_ESTATUS); /* protocol error, bus not started */ error = IICBUS_STOP(device_get_parent(bus)); @@ -245,7 +274,7 @@ iicbus_write(device_t bus, const char *buf, int len, int *sent, int timeout) /* a slave must have been started for writing */ if (sc->started == 0 || (sc->strict != 0 && (sc->started & LSB) != 0)) - return (EINVAL); + return (IIC_ESTATUS); return (IICBUS_WRITE(device_get_parent(bus), buf, len, sent, timeout)); } @@ -263,7 +292,7 @@ iicbus_read(device_t bus, char *buf, int len, int *read, int last, int delay) /* a slave must have been started for reading */ if (sc->started == 0 || (sc->strict != 0 && (sc->started & LSB) == 0)) - return (EINVAL); + return (IIC_ESTATUS); return (IICBUS_READ(device_get_parent(bus), buf, len, read, last, delay)); } @@ -276,9 +305,14 @@ iicbus_read(device_t bus, char *buf, int len, int *read, int last, int delay) int iicbus_write_byte(device_t bus, char byte, int timeout) { + struct iicbus_softc *sc = device_get_softc(bus); char data = byte; int sent; + /* a slave must have been started for writing */ + if (sc->started == 0 || (sc->strict != 0 && (sc->started & LSB) != 0)) + return (IIC_ESTATUS); + return (iicbus_write(bus, &data, 1, &sent, timeout)); } @@ -290,8 +324,13 @@ iicbus_write_byte(device_t bus, char byte, int timeout) int iicbus_read_byte(device_t bus, char *byte, int timeout) { + struct iicbus_softc *sc = device_get_softc(bus); int read; + /* a slave must have been started for reading */ + if (sc->started == 0 || (sc->strict != 0 && (sc->started & LSB) == 0)) + return (IIC_ESTATUS); + return (iicbus_read(bus, byte, 1, &read, IIC_LAST_READ, timeout)); } @@ -352,6 +391,7 @@ iicbus_block_read(device_t bus, u_char slave, char *buf, int len, int *read) int iicbus_transfer(device_t bus, struct iic_msg *msgs, uint32_t nmsgs) { + return (IICBUS_TRANSFER(device_get_parent(bus), msgs, nmsgs)); } @@ -368,10 +408,10 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) bool nostop; if ((error = device_get_children(dev, &children, &nkid)) != 0) - return (error); + return (IIC_ERESOURCE); if (nkid != 1) { free(children, M_TEMP); - return (EIO); + return (IIC_ENOTSUPP); } bus = children[0]; rpstart = 0; @@ -390,8 +430,7 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) else error = iicbus_start(bus, addr, 0); } - - if (error) + if (error != 0) break; if (msgs[i].flags & IIC_M_RD) @@ -400,6 +439,8 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) else error = iicbus_write(bus, msgs[i].buf, msgs[i].len, &lenwrote, 0); + if (error != 0) + break; if ((msgs[i].flags & IIC_M_NOSTOP) != 0 || (nostop && i + 1 < nmsgs)) { @@ -409,5 +450,7 @@ iicbus_transfer_gen(device_t dev, struct iic_msg *msgs, uint32_t nmsgs) iicbus_stop(bus); } } + if (error != 0 && !nostop) + iicbus_stop(bus); return (error); } |