2020-07-10 15:31:20

by Andrzej Hajda

[permalink] [raw]
Subject: [PATCH v8 0/5] driver core: add probe error check helper

Hi All,

Thanks for comments.

Changes since v7:
- improved commit message
- added R-Bs

Changes since v6:
- removed leftovers from old naming scheme in commit descritions,
- added R-Bs.

Changes since v5:
- removed patch adding macro, dev_err_probe(dev, PTR_ERR(ptr), ...) should be used instead,
- added dev_dbg logging in case of -EPROBE_DEFER,
- renamed functions and vars according to comments,
- extended docs,
- cosmetics.

Original message (with small adjustments):

Recently I took some time to re-check error handling in drivers probe code,
and I have noticed that number of incorrect resource acquisition error handling
increased and there are no other propositions which can cure the situation.

So I have decided to resend my old proposition of probe_err helper which should
simplify resource acquisition error handling, it also extend it with adding defer
probe reason to devices_deferred debugfs property, which should improve debugging
experience for developers/testers.

I have also added two patches showing usage and benefits of the helper.

My dirty/ad-hoc cocci scripts shows that this helper can be used in at least 2700 places
saving about 3500 lines of code.

Regards
Andrzej


Andrzej Hajda (5):
driver core: add device probe log helper
driver core: add deferring probe reason to devices_deferred property
drm/bridge/sii8620: fix resource acquisition error handling
drm/bridge: lvds-codec: simplify error handling
coccinelle: add script looking for cases where probe__err can be used

drivers/base/base.h | 3 +
drivers/base/core.c | 46 +++++
drivers/base/dd.c | 23 ++-
drivers/gpu/drm/bridge/lvds-codec.c | 10 +-
drivers/gpu/drm/bridge/sil-sii8620.c | 21 +--
include/linux/device.h | 3 +
probe_err.cocci | 247 +++++++++++++++++++++++++++
7 files changed, 333 insertions(+), 20 deletions(-)
create mode 100644 probe_err.cocci

--
2.17.1


2020-07-10 15:31:40

by Andrzej Hajda

[permalink] [raw]
Subject: [PATCH v8 1/5] driver core: add device probe log helper

During probe every time driver gets resource it should usually check for
error printk some message if it is not -EPROBE_DEFER and return the error.
This pattern is simple but requires adding few lines after any resource
acquisition code, as a result it is often omitted or implemented only
partially.
dev_err_probe helps to replace such code sequences with simple call,
so code:
if (err != -EPROBE_DEFER)
dev_err(dev, ...);
return err;
becomes:
return dev_err_probe(dev, err, ...);

Signed-off-by: Andrzej Hajda <[email protected]>
Reviewed-by: Rafael J. Wysocki <[email protected]>
Reviewed-by: Mark Brown <[email protected]>
---
drivers/base/core.c | 42 ++++++++++++++++++++++++++++++++++++++++++
include/linux/device.h | 3 +++
2 files changed, 45 insertions(+)

diff --git a/drivers/base/core.c b/drivers/base/core.c
index 67d39a90b45c..3a827c82933f 100644
--- a/drivers/base/core.c
+++ b/drivers/base/core.c
@@ -3953,6 +3953,48 @@ define_dev_printk_level(_dev_info, KERN_INFO);

#endif

+/**
+ * dev_err_probe - probe error check and log helper
+ * @dev: the pointer to the struct device
+ * @err: error value to test
+ * @fmt: printf-style format string
+ * @...: arguments as specified in the format string
+ *
+ * This helper implements common pattern present in probe functions for error
+ * checking: print debug or error message depending if the error value is
+ * -EPROBE_DEFER and propagate error upwards.
+ * It replaces code sequence:
+ * if (err != -EPROBE_DEFER)
+ * dev_err(dev, ...);
+ * else
+ * dev_dbg(dev, ...);
+ * return err;
+ * with
+ * return dev_err_probe(dev, err, ...);
+ *
+ * Returns @err.
+ *
+ */
+int dev_err_probe(const struct device *dev, int err, const char *fmt, ...)
+{
+ struct va_format vaf;
+ va_list args;
+
+ va_start(args, fmt);
+ vaf.fmt = fmt;
+ vaf.va = &args;
+
+ if (err != -EPROBE_DEFER)
+ dev_err(dev, "error %d: %pV", err, &vaf);
+ else
+ dev_dbg(dev, "error %d: %pV", err, &vaf);
+
+ va_end(args);
+
+ return err;
+}
+EXPORT_SYMBOL_GPL(dev_err_probe);
+
static inline bool fwnode_is_primary(struct fwnode_handle *fwnode)
{
return fwnode && !IS_ERR(fwnode->secondary);
diff --git a/include/linux/device.h b/include/linux/device.h
index 15460a5ac024..6b2272ae9af8 100644
--- a/include/linux/device.h
+++ b/include/linux/device.h
@@ -964,6 +964,9 @@ void device_link_remove(void *consumer, struct device *supplier);
void device_links_supplier_sync_state_pause(void);
void device_links_supplier_sync_state_resume(void);

+extern __printf(3, 4)
+int dev_err_probe(const struct device *dev, int err, const char *fmt, ...);
+
/* Create alias, so I can be autoloaded. */
#define MODULE_ALIAS_CHARDEV(major,minor) \
MODULE_ALIAS("char-major-" __stringify(major) "-" __stringify(minor))
--
2.17.1

2020-07-10 15:33:14

by Andrzej Hajda

[permalink] [raw]
Subject: [PATCH v8 3/5] drm/bridge/sii8620: fix resource acquisition error handling

In case of error during resource acquisition driver should print error
message only in case it is not deferred probe, using dev_err_probe helper
solves the issue. Moreover it records defer probe reason for debugging.

Signed-off-by: Andrzej Hajda <[email protected]>
Reviewed-by: Neil Armstrong <[email protected]>
---
drivers/gpu/drm/bridge/sil-sii8620.c | 21 +++++++++------------
1 file changed, 9 insertions(+), 12 deletions(-)

diff --git a/drivers/gpu/drm/bridge/sil-sii8620.c b/drivers/gpu/drm/bridge/sil-sii8620.c
index 92acd336aa89..389c1f029774 100644
--- a/drivers/gpu/drm/bridge/sil-sii8620.c
+++ b/drivers/gpu/drm/bridge/sil-sii8620.c
@@ -2299,10 +2299,9 @@ static int sii8620_probe(struct i2c_client *client,
INIT_LIST_HEAD(&ctx->mt_queue);

ctx->clk_xtal = devm_clk_get(dev, "xtal");
- if (IS_ERR(ctx->clk_xtal)) {
- dev_err(dev, "failed to get xtal clock from DT\n");
- return PTR_ERR(ctx->clk_xtal);
- }
+ if (IS_ERR(ctx->clk_xtal))
+ return dev_err_probe(dev, PTR_ERR(ctx->clk_xtal),
+ "failed to get xtal clock from DT\n");

if (!client->irq) {
dev_err(dev, "no irq provided\n");
@@ -2313,16 +2312,14 @@ static int sii8620_probe(struct i2c_client *client,
sii8620_irq_thread,
IRQF_TRIGGER_HIGH | IRQF_ONESHOT,
"sii8620", ctx);
- if (ret < 0) {
- dev_err(dev, "failed to install IRQ handler\n");
- return ret;
- }
+ if (ret < 0)
+ return dev_err_probe(dev, ret,
+ "failed to install IRQ handler\n");

ctx->gpio_reset = devm_gpiod_get(dev, "reset", GPIOD_OUT_HIGH);
- if (IS_ERR(ctx->gpio_reset)) {
- dev_err(dev, "failed to get reset gpio from DT\n");
- return PTR_ERR(ctx->gpio_reset);
- }
+ if (IS_ERR(ctx->gpio_reset))
+ return dev_err_probe(dev, PTR_ERR(ctx->gpio_reset),
+ "failed to get reset gpio from DT\n");

ctx->supplies[0].supply = "cvcc10";
ctx->supplies[1].supply = "iovcc18";
--
2.17.1

2020-07-10 15:33:29

by Andrzej Hajda

[permalink] [raw]
Subject: [PATCH v8 5/5] coccinelle: add script looking for cases where probe__err can be used

This is AD-HOC script, it should nt be merged.

Signed-off-by: Andrzej Hajda <[email protected]>
---
probe_err.cocci | 247 ++++++++++++++++++++++++++++++++++++++++++++++++
1 file changed, 247 insertions(+)
create mode 100644 probe_err.cocci

diff --git a/probe_err.cocci b/probe_err.cocci
new file mode 100644
index 000000000000..0ef9a9b4c9bc
--- /dev/null
+++ b/probe_err.cocci
@@ -0,0 +1,247 @@
+virtual context
+virtual patch
+
+@initialize:python@
+@@
+
+import re
+
+@@
+expression err, dev;
+constant char [] fmt;
+expression list args;
+@@
+
+- if (err != -EPROBE_DEFER) { dev_err(dev, fmt, args); }
++ dev_err_probe(dev, err, fmt, args);
+
+@@
+expression ptr, dev;
+constant char [] fmt;
+expression list args;
+@@
+
+- if (ptr != ERR_PTR(-EPROBE_DEFER)) { dev_err(dev, fmt, args); }
++ dev_err_probe(dev, PTR_ERR(ptr), fmt, args);
+
+@@
+expression e, dev;
+identifier err;
+identifier fget =~ "^(devm_)?(clk_get|gpiod_get|gpiod_get_optional|gpiod_get_index|gpiod_get_index_optional|regulator_get|regulator_get_optional|reset_control_get|reset_control_get_exclusive|reset_control_get_shared|phy_get|pinctrl_get|iio_channel_get|pwm_get)$";
+identifier flog =~ "^(dev_err|dev_warn|dev_info)$";
+expression list args;
+@@
+ e = fget(...);
+ if (IS_ERR(e)) {
+(
+ err = PTR_ERR(e);
+- flog(dev, args);
++ dev_err_probe(dev, err, args);
+|
+- flog(dev, args);
++ dev_err_probe(dev, PTR_ERR(e), args);
+)
+ ...
+ }
+
+@@
+expression dev;
+identifier err;
+identifier fget =~ "^(devm_)?(request_irq|request_threaded_irq|regulator_bulk_get)$";
+identifier flog =~ "^(dev_err|dev_warn|dev_info)$";
+expression list args;
+@@
+ err = fget(...);
+ if ( \( err \| err < 0 \) ) {
+ ...
+- flog(dev, args);
++ dev_err_probe(dev, err, args);
+ ...
+ }
+
+@catch_no_nl@
+expression dev, err;
+constant char [] fmt !~ "\\n$";
+@@
+ dev_err_probe(dev, err, fmt, ...)
+
+@script:python add_nl depends on catch_no_nl@
+fmt << catch_no_nl.fmt;
+nfmt;
+@@
+print "add_nl " + fmt
+coccinelle.nfmt = fmt[:-1] + '\\n"';
+
+@fix_no_nl depends on catch_no_nl@
+constant char [] catch_no_nl.fmt;
+identifier add_nl.nfmt;
+@@
+- fmt
++ nfmt
+
+@catch_fmt@
+expression err, dev;
+expression fmt;
+position p;
+@@
+
+ dev_err_probe@p(dev, err, fmt, ..., \( (int)err \| err \) )
+
+@script:python trim_fmt@
+fmt << catch_fmt.fmt;
+new_fmt;
+@@
+
+tmp = fmt
+tmp = re.sub('failed: irq request (IRQ: %d, error :%d)', 'irq request %d', tmp)
+tmp = re.sub('Error %l?[di] ', 'Error ', tmp)
+tmp = re.sub(' as irq = %d\\\\n', ', bad irq\\\\n', tmp)
+tmp = re.sub('[:,]? ?((ret|err|with|error)[ =]?)?%l?[di]\.?\\\\n', '\\\\n', tmp)
+tmp = re.sub(' ?\(((err|ret|error)\s*=?\s*)?%l?[diu]\)[!.]?\\\\n', '\\\\n', tmp)
+
+assert tmp != fmt, "cannot trim_fmt in: " + fmt
+print "trim_fmt " + fmt + " " + tmp
+coccinelle.new_fmt = tmp
+
+@fix_fmt@
+expression err, err1, dev;
+expression fmt;
+expression list l;
+identifier trim_fmt.new_fmt;
+position catch_fmt.p;
+@@
+
+- dev_err_probe@p(dev, err, fmt, l, err1)
++ dev_err_probe(dev, err, new_fmt, l)
+
+@err_ass1@
+identifier err;
+expression dev, ptr;
+expression list args;
+@@
+
+- err = PTR_ERR(ptr);
+- dev_err_probe(dev, err, args);
+- return ERR_PTR(err);
++ dev_err_probe(dev, PTR_ERR(ptr), args);
++ return ERR_CAST(ptr);
+
+@err_ass2@
+identifier err, f1, f2;
+expression dev, e;
+expression list args;
+@@
+- err = PTR_ERR(e);
+- dev_err_probe(dev, err, args);
+(
+|
+ f1(...);
+|
+ f1(...);
+ f2(...);
+)
+- return err;
++ return dev_err_probe(dev, PTR_ERR(e), args);
+
+@@
+identifier err;
+expression dev, e;
+expression list args;
+@@
+
+- int err = e;
+- dev_err_probe(dev, err, args);
+- return err;
++ return dev_err_probe(dev, e, args);
+
+@@
+expression err, dev;
+expression list args;
+@@
+
+- dev_err_probe(dev, err, args);
+- return err;
++ return dev_err_probe(dev, err, args);
+
+@@
+expression err, dev, ptr;
+expression list args;
+@@
+
+- dev_err_probe(dev, PTR_ERR(ptr), args);
+ err = PTR_ERR(ptr);
++ dev_err_probe(dev, err, args);
+
+@@
+expression e;
+expression list args;
+statement s, s1;
+@@
+
+// without s1 spatch generates extra empty line after s
+- if (e) { return dev_err_probe(args); } else s s1
++ if (e) return dev_err_probe(args); s s1
+
+@@
+expression e;
+expression list args;
+@@
+
+- if (e) { return dev_err_probe(args); }
++ if (e) return dev_err_probe(args);
+
+@@
+expression e, s, v;
+expression list args;
+@@
+
+- if (e == v) { s; } else { return dev_err_probe(args); }
++ if (e != v) return dev_err_probe(args); s;
+
+@err_ass3@
+identifier err;
+expression dev, ptr;
+expression list args;
+@@
+
+- err = PTR_ERR_OR_ZERO(ptr);
+- if (err) return dev_err_probe(dev, err, args);
++ if (IS_ERR(ptr)) return dev_err_probe(dev, PTR_ERR(ptr), args);
+
+@@
+expression dev, ptr;
+expression list args;
+@@
+
+- DROP_probe_err(dev, PTR_ERR(ptr), args)
++ probe_err(dev, ptr, args)
+
+@@
+identifier err_ass1.err;
+expression e;
+identifier f =~ "^(dev_err_probe|probe_err_ptr)$";
+@@
+- \( int err; \| int err = e; \)
+ <+... when != err
+ f
+ ...+>
+
+@@
+identifier err_ass2.err;
+expression e;
+identifier f =~ "^(dev_err_probe|probe_err_ptr)$";
+@@
+- \( int err; \| int err = e; \)
+ <+... when != err
+ f
+ ...+>
+
+@@
+identifier err_ass3.err;
+expression e;
+identifier f =~ "^(dev_err_probe|probe_err_ptr)$";
+@@
+- \( int err; \| int err = e; \)
+ <+... when != err
+ f
+ ...+>
--
2.17.1

2020-07-10 15:34:17

by Andrzej Hajda

[permalink] [raw]
Subject: [PATCH v8 4/5] drm/bridge: lvds-codec: simplify error handling

Using dev_err_probe code has following advantages:
- shorter code,
- recorded defer probe reason for debugging,
- uniform error code logging.

Signed-off-by: Andrzej Hajda <[email protected]>
Reviewed-by: Neil Armstrong <[email protected]>
---
drivers/gpu/drm/bridge/lvds-codec.c | 10 +++-------
1 file changed, 3 insertions(+), 7 deletions(-)

diff --git a/drivers/gpu/drm/bridge/lvds-codec.c b/drivers/gpu/drm/bridge/lvds-codec.c
index 24fb1befdfa2..f19d9f7a5db2 100644
--- a/drivers/gpu/drm/bridge/lvds-codec.c
+++ b/drivers/gpu/drm/bridge/lvds-codec.c
@@ -71,13 +71,9 @@ static int lvds_codec_probe(struct platform_device *pdev)
lvds_codec->connector_type = (uintptr_t)of_device_get_match_data(dev);
lvds_codec->powerdown_gpio = devm_gpiod_get_optional(dev, "powerdown",
GPIOD_OUT_HIGH);
- if (IS_ERR(lvds_codec->powerdown_gpio)) {
- int err = PTR_ERR(lvds_codec->powerdown_gpio);
-
- if (err != -EPROBE_DEFER)
- dev_err(dev, "powerdown GPIO failure: %d\n", err);
- return err;
- }
+ if (IS_ERR(lvds_codec->powerdown_gpio))
+ return dev_err_probe(dev, PTR_ERR(lvds_codec->powerdown_gpio),
+ "powerdown GPIO failure\n");

/* Locate the panel DT node. */
panel_node = of_graph_get_remote_node(dev->of_node, 1, 0);
--
2.17.1