From: Serge Semin <fancer.lancer@gmail.com>
To: Richard Leitner <richard.leitner@skidata.com>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Serge Semin <Sergey.Semin@t-platforms.ru>,
linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: usb: usb251xb: Lock i2c-bus segment the hub resides
Date: Wed, 24 Apr 2019 17:49:14 +0300 [thread overview]
Message-ID: <20190424144914.10580-1-fancer.lancer@gmail.com> (raw)
SMBus slave configuration is activated by CFG_SEL[1:0]=0x1 pins
state. This is the mode the hub is supposed to be to let this driver
work correctly. But a race condition might happen right after reset
is cleared due to CFG_SEL[0] pin being multiplexed with SMBus SCL
function. In case if the reset pin is handled by a i2c GPIO expander,
which is also placed at the same i2c-bus segment as the usb251x
SMB-interface connected to, then the hub reset clearance might
cause the CFG_SEL[0] being latched in unpredictable state. So
sometimes the hub configuration mode might be 0x1 (as expected),
but sometimes being 0x0, which doesn't imply to have the hub SMBus-slave
interface activated and consequently causes this driver failure.
In order to fix the problem we must make sure the GPIO-reset chip doesn't
reside the same i2c-bus segment as the SMBus-interface of the hub. If
it doesn't, we can safely block the segment for the time the reset is
cleared to prevent anyone generating a traffic at the i2c-bus SCL lane
connected to the CFG_SEL[0] pin. But if it does, nothing we can do, so
just return an error. If we locked the i2c-bus segment and tried to
communicate with the GPIO-expander, it would cause a deadlock. If we didn't
lock the i2c-bus segment, it would randomly cause the CFG_SEL[0] bit flip.
Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
drivers/usb/misc/usb251xb.c | 55 +++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c
index 04684849d683..939b3bedd4c8 100644
--- a/drivers/usb/misc/usb251xb.c
+++ b/drivers/usb/misc/usb251xb.c
@@ -12,6 +12,7 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/nls.h>
@@ -222,11 +223,44 @@ static const struct usb251xb_data usb2517i_data = {
.product_str = "USB2517i",
};
+static int usb251xb_check_dev_children(struct device *dev, void *child)
+{
+ if (dev->type == &i2c_adapter_type) {
+ return device_for_each_child(dev, child,
+ usb251xb_check_dev_children);
+ }
+
+ return (dev == child);
+}
+
+static int usb251x_check_gpio_chip(struct usb251xb *hub)
+{
+ struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset);
+ struct i2c_adapter *adap = hub->i2c->adapter;
+ int ret;
+
+ if (!hub->gpio_reset)
+ return 0;
+
+ if (!gc)
+ return -EINVAL;
+
+ ret = usb251xb_check_dev_children(&adap->dev, gc->parent);
+ if (ret) {
+ dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static void usb251xb_reset(struct usb251xb *hub, int state)
{
if (!hub->gpio_reset)
return;
+ i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+
gpiod_set_value_cansleep(hub->gpio_reset, state);
/* wait for hub recovery/stabilization */
@@ -234,6 +268,8 @@ static void usb251xb_reset(struct usb251xb *hub, int state)
usleep_range(500, 750); /* >=500us at power on */
else
usleep_range(1, 10); /* >=1us at power down */
+
+ i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
}
static int usb251xb_connect(struct usb251xb *hub)
@@ -621,6 +657,25 @@ static int usb251xb_probe(struct usb251xb *hub)
}
}
+ /*
+ * usb251x SMBus-slave SCL lane is muxed with CFG_SEL0 pin. So if anyone
+ * tries to work with the bus at the moment the hub reset is released,
+ * it may cause an invalid config being latched by usb251x. Particularly
+ * one of the config modes makes the hub loading a default registers
+ * value without SMBus-slave interface activation. If the hub
+ * accidentally gets this mode, this will cause the driver SMBus-
+ * functions failure. Normally we could just lock the SMBus-segment the
+ * hub i2c-interface resides for the device-specific reset timing. But
+ * the GPIO controller, which is used to handle the hub reset, might be
+ * placed at the same i2c-bus segment. In this case an error should be
+ * returned since we can't safely use the GPIO controller to clear the
+ * reset state (it may affect the hub configuration) and we can't lock
+ * the i2c-bus segment (it will cause a deadlock).
+ */
+ err = usb251x_check_gpio_chip(hub);
+ if (err)
+ return err;
+
err = usb251xb_connect(hub);
if (err) {
dev_err(dev, "Failed to connect hub (%d)\n", err);
WARNING: multiple messages have this Message-ID (diff)
From: Serge Semin <fancer.lancer@gmail.com>
To: Richard Leitner <richard.leitner@skidata.com>,
Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Cc: Serge Semin <Sergey.Semin@t-platforms.ru>,
linux-usb@vger.kernel.org, linux-kernel@vger.kernel.org
Subject: [PATCH] usb: usb251xb: Lock i2c-bus segment the hub resides
Date: Wed, 24 Apr 2019 17:49:14 +0300 [thread overview]
Message-ID: <20190424144914.10580-1-fancer.lancer@gmail.com> (raw)
Message-ID: <20190424144914.W3FyCmS4s0Mj7VIlyUeqHqOZFK9cZmye9uLcf05gbqU@z> (raw)
SMBus slave configuration is activated by CFG_SEL[1:0]=0x1 pins
state. This is the mode the hub is supposed to be to let this driver
work correctly. But a race condition might happen right after reset
is cleared due to CFG_SEL[0] pin being multiplexed with SMBus SCL
function. In case if the reset pin is handled by a i2c GPIO expander,
which is also placed at the same i2c-bus segment as the usb251x
SMB-interface connected to, then the hub reset clearance might
cause the CFG_SEL[0] being latched in unpredictable state. So
sometimes the hub configuration mode might be 0x1 (as expected),
but sometimes being 0x0, which doesn't imply to have the hub SMBus-slave
interface activated and consequently causes this driver failure.
In order to fix the problem we must make sure the GPIO-reset chip doesn't
reside the same i2c-bus segment as the SMBus-interface of the hub. If
it doesn't, we can safely block the segment for the time the reset is
cleared to prevent anyone generating a traffic at the i2c-bus SCL lane
connected to the CFG_SEL[0] pin. But if it does, nothing we can do, so
just return an error. If we locked the i2c-bus segment and tried to
communicate with the GPIO-expander, it would cause a deadlock. If we didn't
lock the i2c-bus segment, it would randomly cause the CFG_SEL[0] bit flip.
Signed-off-by: Serge Semin <fancer.lancer@gmail.com>
---
drivers/usb/misc/usb251xb.c | 55 +++++++++++++++++++++++++++++++++++++
1 file changed, 55 insertions(+)
diff --git a/drivers/usb/misc/usb251xb.c b/drivers/usb/misc/usb251xb.c
index 04684849d683..939b3bedd4c8 100644
--- a/drivers/usb/misc/usb251xb.c
+++ b/drivers/usb/misc/usb251xb.c
@@ -12,6 +12,7 @@
#include <linux/delay.h>
#include <linux/gpio/consumer.h>
+#include <linux/gpio/driver.h>
#include <linux/i2c.h>
#include <linux/module.h>
#include <linux/nls.h>
@@ -222,11 +223,44 @@ static const struct usb251xb_data usb2517i_data = {
.product_str = "USB2517i",
};
+static int usb251xb_check_dev_children(struct device *dev, void *child)
+{
+ if (dev->type == &i2c_adapter_type) {
+ return device_for_each_child(dev, child,
+ usb251xb_check_dev_children);
+ }
+
+ return (dev == child);
+}
+
+static int usb251x_check_gpio_chip(struct usb251xb *hub)
+{
+ struct gpio_chip *gc = gpiod_to_chip(hub->gpio_reset);
+ struct i2c_adapter *adap = hub->i2c->adapter;
+ int ret;
+
+ if (!hub->gpio_reset)
+ return 0;
+
+ if (!gc)
+ return -EINVAL;
+
+ ret = usb251xb_check_dev_children(&adap->dev, gc->parent);
+ if (ret) {
+ dev_err(hub->dev, "Reset GPIO chip is at the same i2c-bus\n");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
static void usb251xb_reset(struct usb251xb *hub, int state)
{
if (!hub->gpio_reset)
return;
+ i2c_lock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
+
gpiod_set_value_cansleep(hub->gpio_reset, state);
/* wait for hub recovery/stabilization */
@@ -234,6 +268,8 @@ static void usb251xb_reset(struct usb251xb *hub, int state)
usleep_range(500, 750); /* >=500us at power on */
else
usleep_range(1, 10); /* >=1us at power down */
+
+ i2c_unlock_bus(hub->i2c->adapter, I2C_LOCK_SEGMENT);
}
static int usb251xb_connect(struct usb251xb *hub)
@@ -621,6 +657,25 @@ static int usb251xb_probe(struct usb251xb *hub)
}
}
+ /*
+ * usb251x SMBus-slave SCL lane is muxed with CFG_SEL0 pin. So if anyone
+ * tries to work with the bus at the moment the hub reset is released,
+ * it may cause an invalid config being latched by usb251x. Particularly
+ * one of the config modes makes the hub loading a default registers
+ * value without SMBus-slave interface activation. If the hub
+ * accidentally gets this mode, this will cause the driver SMBus-
+ * functions failure. Normally we could just lock the SMBus-segment the
+ * hub i2c-interface resides for the device-specific reset timing. But
+ * the GPIO controller, which is used to handle the hub reset, might be
+ * placed at the same i2c-bus segment. In this case an error should be
+ * returned since we can't safely use the GPIO controller to clear the
+ * reset state (it may affect the hub configuration) and we can't lock
+ * the i2c-bus segment (it will cause a deadlock).
+ */
+ err = usb251x_check_gpio_chip(hub);
+ if (err)
+ return err;
+
err = usb251xb_connect(hub);
if (err) {
dev_err(dev, "Failed to connect hub (%d)\n", err);
--
2.21.0
next reply other threads:[~2019-04-24 14:49 UTC|newest]
Thread overview: 20+ messages / expand[flat|nested] mbox.gz Atom feed top
2019-04-24 14:49 Serge Semin [this message]
2019-04-24 14:49 ` [PATCH] usb: usb251xb: Lock i2c-bus segment the hub resides Serge Semin
-- strict thread matches above, loose matches on Subject: below --
2019-04-26 10:46 [v2] " Serge Semin
2019-04-26 10:46 ` [PATCH v2] " Serge Semin
2019-04-27 7:00 [v2] " Greg Kroah-Hartman
2019-04-27 7:00 ` [PATCH v2] " Greg Kroah-Hartman
2019-04-27 7:10 [v2] " Serge Semin
2019-04-27 7:10 ` [PATCH v2] " Serge Semin
2019-04-27 7:32 [v2] " Greg Kroah-Hartman
2019-04-27 7:32 ` [PATCH v2] " Greg Kroah-Hartman
2019-04-27 7:39 [v2] " Serge Semin
2019-04-27 7:39 ` [PATCH v2] " Serge Semin
2019-04-27 7:53 [v2] " Greg Kroah-Hartman
2019-04-27 7:53 ` [PATCH v2] " Greg Kroah-Hartman
2019-04-27 9:31 [v2] " Serge Semin
2019-04-27 9:31 ` [PATCH v2] " Serge Semin
2019-04-27 10:45 [v2] " Greg Kroah-Hartman
2019-04-27 10:45 ` [PATCH v2] " Greg Kroah-Hartman
2019-04-27 10:58 [v2] " Serge Semin
2019-04-27 10:58 ` [PATCH v2] " Serge Semin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20190424144914.10580-1-fancer.lancer@gmail.com \
--to=fancer.lancer@gmail.com \
--cc=Sergey.Semin@t-platforms.ru \
--cc=gregkh@linuxfoundation.org \
--cc=linux-kernel@vger.kernel.org \
--cc=linux-usb@vger.kernel.org \
--cc=richard.leitner@skidata.com \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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.