diff mbox

[RFC,02/15] drivers/base: add restrack framework

Message ID 1418226513-14105-3-git-send-email-a.hajda@samsung.com (mailing list archive)
State New, archived
Headers show

Commit Message

Andrzej Hajda Dec. 10, 2014, 3:48 p.m. UTC
restrack framework allows tracking presence of resources with dynamic life
time. Typical example of such resources are all resources provided by device
drivers, for example clocks, phys, regulators. Since drivers can be bound and
unbound dynamically and unconditionally, resources provided by such drivers
can appear and disappear at any time.

To use restrack in consumer user should call one of *restrack_register
functions. In the function arguments consumer should provide callback and
description of resources which should be tracked. Each resource description
should contain pointer to variable where restrack should store address of
allocated resource and parameters describing specific resource, for example
in case of clock it should be clock name and in case of gpio it should be
gpio name and flags.
The callback should have two arguments:
- dev - device for which callback have been registered,
- ret - return code.
If callback is called with ret == 0 it means all tracked resources are
present, allocated and provided resource pointers are set accordingly.
In case ret == -EPROBE_DEFER it means all resources are present but at least
one of the resources are to be removed after return form the callback.

Simplified example of framework usage on LCD panel driver.

static int lcd_probe(...)
{
	struct restrack *rtrack;

	(...initialization w/o resource allocation ...)

	rtrack = devm_restrack_register(dev, lcd_callback,
		regulator_bulk_restrack_desc(&ctx->supplies[0]),
		regulator_bulk_restrack_desc(&ctx->supplies[1]),
		clk_restrack_desc(&ctx->pll_clk, "pll_clk"),
		clk_restrack_desc(&ctx->bus_clk, "bus_clk"),
		phy_restrack_desc(&ctx->phy, "dsim"),
	);

	return PTR_ERR_OR_NULL(rtrack);
}

void lcd_callback(struct device *dev, int ret)
{
	struct lcd_ctx *ctx = dev_get_drvdata(dev);

	if (ret == 0)
		drm_panel_add(&ctx->panel);
	else if (ret == -EPROBE_DEFER)
		drm_panel_remove(&ctx->panel);
	else
		dev_err(dev, "restrack error %d\n", ret);
}

Please note few things:
1. drm_panel_add calls restrack_up and drm_panel_remove calls restrack_down.
   It is OK to call restrack framework from the callback.
2. In lcd_callback if ret is 0 or -EDEFER_PROBE all resources are valid, ie
   driver for example can call clk_prepare_enable(ctx->pll_clk).
3. No mutexes are needed to protect lcd_ctx in lcd_callback call.
4. All resources are freed by restrack_unregister, which in this case is
   called by devres framework.

To add restrack support to specific framework following things should be
defined:
- structure describing resource with embedded restrack_desc structure,
- at least one exported allocator of such structure,
- few simple operations according to description of struct restrack_ops,
- notifications about adding/removal of the resource.
For details please look at implementations.

Signed-off-by: Andrzej Hajda <a.hajda@samsung.com>
---
 drivers/base/Makefile    |   2 +-
 drivers/base/restrack.c  | 344 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/restrack.h | 137 +++++++++++++++++++
 3 files changed, 482 insertions(+), 1 deletion(-)
 create mode 100644 drivers/base/restrack.c
 create mode 100644 include/linux/restrack.h

Comments

Mark Brown Dec. 12, 2014, 4:52 p.m. UTC | #1
On Wed, Dec 10, 2014 at 04:48:20PM +0100, Andrzej Hajda wrote:
> restrack framework allows tracking presence of resources with dynamic life
> time. Typical example of such resources are all resources provided by device

I don't know about anyone else but I'm having a hard time reading the
restrack name, it looks like a misspelling of restack to me.

At a high level my biggest questions here are the relationship between
this and the component code and usability.  The usability concern I have
is that I see no diagnostics or trace here at all.  This means that if a
user looks at their system, sees that the device model claims the driver
for a device bound to the device but can't see any sign of the device
doing anything they don't have any way of telling why that is other than
to look in the driver code, see what resources it was trying to depend
on and then go back to the running system to try to understand which of
those resources hasn't appeared.

> +int restrack_up(unsigned long type, const void *id, void *data)

> +int restrack_down(unsigned long type, const void *id, void *data)

Again I'm not sure that the up and down naming is meaningful in the
context of this interface.

> +static void restrack_itb_cb(struct track_block *itb, void *data, bool on)
> +{

itb_cb?
Andrzej Hajda Dec. 15, 2014, 8:28 a.m. UTC | #2
On 12/12/2014 05:52 PM, Mark Brown wrote:
> On Wed, Dec 10, 2014 at 04:48:20PM +0100, Andrzej Hajda wrote:
>> restrack framework allows tracking presence of resources with dynamic life
>> time. Typical example of such resources are all resources provided by device
> I don't know about anyone else but I'm having a hard time reading the
> restrack name, it looks like a misspelling of restack to me.

Any alternative names?

>
> At a high level my biggest questions here are the relationship between
> this and the component code and usability.  The usability concern I have
> is that I see no diagnostics or trace here at all.  This means that if a
> user looks at their system, sees that the device model claims the driver
> for a device bound to the device but can't see any sign of the device
> doing anything they don't have any way of telling why that is other than
> to look in the driver code, see what resources it was trying to depend
> on and then go back to the running system to try to understand which of
> those resources hasn't appeared.

I will move the code for provider matching to frameworks,
so it will be easy to add just dev_info after every failed attempt
of getting resource, including deferring. This is the simplest solution
and it should be similar in verbosity to deferred probing.

Maybe other solution is to provide debug_fs (or device) attribute showing
restrack status per device.

>
>> +int restrack_up(unsigned long type, const void *id, void *data)
>> +int restrack_down(unsigned long type, const void *id, void *data)
> Again I'm not sure that the up and down naming is meaningful in the
> context of this interface.
>
>> +static void restrack_itb_cb(struct track_block *itb, void *data, bool on)
>> +{
> itb_cb?

Ups I forgot to rename few variables from my previous attempt.
itb - stayed for interface tracker block.

Regards
Andrzej
Mark Brown Dec. 15, 2014, 11:38 a.m. UTC | #3
On Mon, Dec 15, 2014 at 09:28:41AM +0100, Andrzej Hajda wrote:
> On 12/12/2014 05:52 PM, Mark Brown wrote:
> > On Wed, Dec 10, 2014 at 04:48:20PM +0100, Andrzej Hajda wrote:

> > I don't know about anyone else but I'm having a hard time reading the
> > restrack name, it looks like a misspelling of restack to me.

> Any alternative names?

Well, even just res_track would help.

> I will move the code for provider matching to frameworks,
> so it will be easy to add just dev_info after every failed attempt
> of getting resource, including deferring. This is the simplest solution
> and it should be similar in verbosity to deferred probing.

> Maybe other solution is to provide debug_fs (or device) attribute showing
> restrack status per device.

I think both are useful - it's often helpful to have a listing of what
resources have actually been registered, for example to help spot typos.
diff mbox

Patch

diff --git a/drivers/base/Makefile b/drivers/base/Makefile
index 4edff7d..cf9a21e 100644
--- a/drivers/base/Makefile
+++ b/drivers/base/Makefile
@@ -4,7 +4,7 @@  obj-y			:= component.o core.o bus.o dd.o syscore.o \
 			   driver.o class.o platform.o \
 			   cpu.o firmware.o init.o map.o devres.o \
 			   attribute_container.o transport_class.o \
-			   topology.o container.o track.o
+			   topology.o container.o track.o restrack.o
 obj-$(CONFIG_DEVTMPFS)	+= devtmpfs.o
 obj-$(CONFIG_DMA_CMA) += dma-contiguous.o
 obj-y			+= power/
diff --git a/drivers/base/restrack.c b/drivers/base/restrack.c
new file mode 100644
index 0000000..e16d8ed
--- /dev/null
+++ b/drivers/base/restrack.c
@@ -0,0 +1,344 @@ 
+/*
+ * Resource tracking framework
+ *
+ * Copyright (c) 2014 Samsung Electronics Co., Ltd
+ * Andrzej Hajda <a.hajda@samsung.com>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 as
+ * published by the Free Software Foundation.
+ *
+ * restrack framework allows to track presence of resources with dynamic life
+ * time. Typical example of such resources are all resources provided by device
+ * drivers, for example clocks, phys, regulators. Since drivers can be bound and
+ * unbound dynamically and unconditionally, resources provided by such drivers
+ * can appear and disappear at any time.
+ *
+ * To use restrack in consumer user should call one of *restrack_register
+ * functions. In the function arguments consumer should provide callback and
+ * description of resources which should be tracked. Each resource description
+ * should contain pointer to variable where restrack should store address of
+ * allocated resource and parameters describing specific resource, for example
+ * in case of clock it should be clock name and in case of gpio it should be
+ * gpio name and flags.
+ * The callback should have two arguments:
+ * - dev - device for which callback have been registered,
+ * - ret - return code.
+ * If callback is called with ret == 0 it means all tracked resources are
+ * present, allocated and provided resource pointers are set accordingly.
+ * In case ret == -EPROBE_DEFER it means all resources are present but at least
+ * one of the resources are to be removed after return form the callback.
+ *
+ * Simplified example of framework usage on LCD panel driver.
+ *
+ * static int lcd_probe(...)
+ * {
+ *	struct restrack *rtrack;
+ *
+ *	(...initialization w/o resource allocation ...)
+ *
+ *	rtrack = devm_restrack_register(dev, lcd_callback,
+ *		regulator_bulk_restrack_desc(&ctx->supplies[0]),
+ *		regulator_bulk_restrack_desc(&ctx->supplies[1]),
+ *		clk_restrack_desc(&ctx->pll_clk, "pll_clk"),
+ *		clk_restrack_desc(&ctx->bus_clk, "bus_clk"),
+ *		phy_restrack_desc(&ctx->phy, "dsim"),
+ *	);
+ *
+ *	return PTR_ERR_OR_NULL(rtrack);
+ * }
+ *
+ * void lcd_callback(struct device *dev, int ret)
+ * {
+ *	struct lcd_ctx *ctx = dev_get_drvdata(dev);
+ *
+ *	if (ret == 0)
+ *		drm_panel_add(&ctx->panel);
+ *	else if (ret == -EPROBE_DEFER)
+ *		drm_panel_remove(&ctx->panel);
+ *	else
+ *		dev_err(dev, "restrack error %d\n", ret);
+ * }
+ *
+ * Please note few things:
+ * 1. drm_panel_add calls restrack_up and drm_panel_remove calls restrack_down.
+ *    It is OK to call restrack framework from the callback.
+ * 2. In lcd_callback if ret is 0 or -EDEFER_PROBE all resources are valid, ie
+ *    driver for example can call clk_prepare_enable(ctx->pll_clk).
+ * 3. No mutexes are needed to protect lcd_ctx in lcd_callback call.
+ * 4. All resources are freed by restrack_unregister, which in this case is
+ *    called by devres framework.
+ *
+ * To add restrack support to specific framework following things should be
+ * defined:
+ * - structure describing resource with embedded restrack_desc structure,
+ * - at least one exported allocator of such structure,
+ * - few simple operations according to description of struct restrack_ops,
+ * - notifications about adding/removal of the resource.
+ * For details please look at existing implementations.
+ *
+ */
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/restrack.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/track.h>
+
+static DEFINE_TRACKER(restrack);
+
+struct restrack_ctx {
+	struct device *dev;
+	unsigned int count;
+	unsigned int get_count;
+	restrack_fn_t callback;
+	struct restrack_desc *desc[0];
+};
+
+/**
+ * restrack_up - notify that resource provider is up
+ * @type: resource type
+ * @id: resource id
+ * @data: @type dependent cookie, usually pointer to resource provider
+ */
+int restrack_up(unsigned long type, const void *id, void *data)
+{
+	return track_up(&restrack, type, id, data);
+}
+
+/**
+ * restrack_down - notify that resource provider is down
+ * @type: resource type
+ * @id: resource id
+ * @data: @type dependent cookie, usually pointer to resource provider
+ */
+int restrack_down(unsigned long type, const void *id, void *data)
+{
+	return track_down(&restrack, type, id, data);
+}
+
+static void restrack_itb_cb(struct track_block *itb, void *data, bool on)
+{
+	struct restrack_desc *desc;
+	struct restrack_ctx *ctx;
+	int i;
+
+	desc = container_of(itb, struct restrack_desc, itb);
+	ctx = desc->ctx;
+
+	for (i = 0; i < ctx->count; ++i) {
+		struct restrack_desc *d = ctx->desc[i];
+
+		if ((d->if_id != desc->if_id) ||
+		    (d->ops->if_type != desc->ops->if_type))
+			continue;
+
+		pr_debug("%s:%d: if_type=%ld on=%d res=%d/%d\n", __func__,
+			 __LINE__, d->ops->if_type, on, i, ctx->count);
+
+		if (on) {
+			if (!d->status)
+				continue;
+
+			d->status = d->ops->if_up(ctx->dev, d, data);
+
+			if (!d->status)
+				++ctx->get_count;
+
+			if (d->status == -EPROBE_DEFER)
+				continue;
+
+			if (d->status || ctx->get_count == ctx->count)
+				ctx->callback(ctx->dev, d->status);
+		} else {
+			if (!d->status) {
+				if (ctx->get_count-- == ctx->count)
+					ctx->callback(ctx->dev, -EPROBE_DEFER);
+				d->ops->if_down(ctx->dev, d, data);
+			}
+			d->status = -EPROBE_DEFER;
+		}
+	}
+}
+
+/* check if the same interface is present in previous restrack descriptors */
+static bool restrack_find_prev_if(struct restrack_ctx *ctx, int n)
+{
+	struct restrack_desc *desc = ctx->desc[n];
+	int i;
+
+	for (i = 0; i < n; ++i) {
+		struct restrack_desc *d = ctx->desc[i];
+
+		if (d->if_id == desc->if_id &&
+		    d->ops->if_type == desc->ops->if_type)
+			return true;
+	}
+
+	return false;
+}
+
+/**
+ * restrack_unregister - unregister restrack callback
+ * @ctx: restrack context, previously allocated by __restrack_register
+ */
+
+void restrack_unregister(struct restrack_ctx *ctx)
+{
+	int i;
+
+	i = ctx->count;
+	while (i-- > 0) {
+		struct restrack_desc *desc = ctx->desc[i];
+
+		if (IS_ERR(desc))
+			continue;
+
+		if (restrack_find_prev_if(ctx, i))
+			continue;
+
+		track_unregister(&restrack, &desc->itb, desc->ops->if_type,
+				 desc->if_id);
+	}
+
+	i = ctx->count;
+	while (i-- > 0) {
+		struct restrack_desc *desc = ctx->desc[i];
+
+		if (IS_ERR(desc))
+			continue;
+
+		if (desc->ops->destroy)
+			desc->ops->destroy(ctx->dev, desc);
+		else
+			kfree(desc);
+	}
+
+	kfree(ctx);
+}
+
+/**
+ * __restrack_register - register resource tracker callback
+ * @dev: consumer device
+ * @callback: callback which will be called when:
+ *	- all tracked resources become available, with @ret = 0
+ *	- one of the resources becomes unavailable, with @ret = -EPROBE_DEFER,
+ *	- resource allocation errors, with @ret equal to this error
+ * @count: number of resource descriptors to track
+ * @descs: list of pointers to resource descriptors to track
+ *
+ * It can be called as follows:
+ *	struct restrack_desc *descriptors[] = {
+ *		regulator_bulk_restrack_desc(&ctx->supplies[0]),
+ *		regulator_bulk_restrack_desc(&ctx->supplies[1]),
+ *		clk_restrack_desc(&ctx->pll_clk, "pll_clk"),
+ *		clk_restrack_desc(&ctx->bus_clk, "bus_clk"),
+ *		phy_restrack_desc(&ctx->phy, "dsim"),
+ *	};
+ *	rtrack = __restrack_register(dev, callback,
+ *			ARRAY_SIZE(descriptors), descriptors);
+ */
+struct restrack_ctx *__restrack_register(struct device *dev, restrack_fn_t cb,
+		int count, struct restrack_desc **descriptors)
+{
+	struct restrack_ctx *ctx;
+	int ret, i;
+
+	ctx = kzalloc(sizeof(*ctx) + count * sizeof(ctx->desc[0]),
+			 GFP_KERNEL);
+	if (!ctx)
+		return ERR_PTR(-ENOMEM);
+
+	ctx->dev = dev;
+	ctx->count = count;
+	ctx->callback = cb;
+	memcpy(ctx->desc, descriptors, count * sizeof(*descriptors));
+
+	for (i = 0; i < count; ++i) {
+		struct restrack_desc *desc = descriptors[i];
+
+		if (IS_ERR(desc)) {
+			ret = PTR_ERR(desc);
+			goto err_free;
+		}
+		INIT_LIST_HEAD(&desc->itb.list);
+		desc->itb.callback = restrack_itb_cb;
+		desc->ctx = ctx;
+		desc->status = -EPROBE_DEFER;
+		ret = desc->ops->init(dev, desc);
+		if (ret)
+			goto err_free;
+	}
+
+	/* Callbacks should be registered after all fields are initialized,
+	 * otherwise callback could access partially initialized data. */
+	for (i = 0; i < count; ++i) {
+		struct restrack_desc *desc = descriptors[i];
+		const struct restrack_ops *ops = desc->ops;
+
+		/* do not track the same interface twice */
+		if (restrack_find_prev_if(ctx, i))
+			continue;
+
+		ret = track_register(&restrack, &desc->itb, ops->if_type,
+				     desc->if_id);
+		if (ret)
+			goto err_free;
+	}
+
+	return ctx;
+
+err_free:
+	restrack_unregister(ctx);
+
+	return ERR_PTR(ret);
+}
+
+static void devm_restrack_release(struct device *dev, void *res)
+{
+	struct restrack_ctx **ptr = res;
+
+	if (!IS_ERR(*ptr))
+		restrack_unregister(*ptr);
+}
+
+/**
+ * __devm_restrack_register - devm version of __restrack_register
+ * @dev: consumer device
+ * @callback: callback which will be called when:
+ *	- all tracked resources become available, with @ret = 0
+ *	- one of the resources becomes unavailable, with @ret = -EPROBE_DEFER,
+ *	- resource allocation errors, with @ret equal to this error
+ * @count: number of resource descriptors to track
+ * @descs: list of pointers to resource descriptors to track
+ *
+ * It can be called as follows:
+ *	struct restrack_desc *descriptors[] = {
+ *		regulator_bulk_restrack_desc(&ctx->supplies[0]),
+ *		regulator_bulk_restrack_desc(&ctx->supplies[1]),
+ *		clk_restrack_desc(&ctx->pll_clk, "pll_clk"),
+ *		clk_restrack_desc(&ctx->bus_clk, "bus_clk"),
+ *		phy_restrack_desc(&ctx->phy, "dsim"),
+ *	};
+ *	rtrack = __devm_restrack_register(dev, callback,
+ *			ARRAY_SIZE(descriptors), descriptors);
+ */
+struct restrack_ctx *__devm_restrack_register(struct device *dev,
+		restrack_fn_t cb, int count, struct restrack_desc **descs)
+{
+	struct restrack_ctx **ptr;
+
+	ptr = devres_alloc(devm_restrack_release, sizeof(*ptr), GFP_KERNEL);
+	if (!ptr)
+		return ERR_PTR(-ENOMEM);
+
+	/* It should be added before __restrack_register to keep correct order
+	 * of resources - restrack callback can also use devres framework.
+	 */
+	devres_add(dev, ptr);
+
+	*ptr = __restrack_register(dev, cb, count, descs);
+
+	return *ptr;
+}
+EXPORT_SYMBOL_GPL(__devm_restrack_register);
diff --git a/include/linux/restrack.h b/include/linux/restrack.h
new file mode 100644
index 0000000..6cf8144f
--- /dev/null
+++ b/include/linux/restrack.h
@@ -0,0 +1,137 @@ 
+#ifndef RESTRACK_H
+#define RESTRACK_H
+
+#include <linux/track.h>
+
+struct device;
+struct restrack_ctx;
+struct restrack_desc;
+
+int restrack_up(unsigned long type, const void *id, void *data);
+int restrack_down(unsigned long type, const void *id, void *data);
+
+/**
+ * struct restrack_ops - Resource tracker operations for specific resource
+ * @if_type: interface type of provider associated with given resource,
+ *	multiple restrack_ops per if_type are allowed
+ * @init: called by restrack_register to initialize device depended fields
+ *	of the resource descriptor
+ * @destroy: destroy resource descriptor
+ * @if_up: called when provider of given resource becomes available, usually
+ *	it should allocate the resource and fill the variable which was
+ *	provided by restrack_register.
+ * @if_down: called before provider of given resource becomes unavailable. It
+ *	should release the resource and set associated variable to
+ *	-EPROBE_DEFER.
+ *
+ * struct restrack_ops provides set of operations to handle resource specific
+ * operations. It translates resource names specific to consumer device to
+ * track interface id exposed by providers and translates back track
+ * notifications to callback convenient for device driver.
+ * @desc parameter provided in all callbacks is a pointer to common
+ * restrack_desc structure embedded in bigger structure which is usually
+ * allocated by resource specific allocator function.
+ * Callbacks should return 0 on success or error code.
+ */
+struct restrack_ops {
+	unsigned long if_type;
+	int (*init)(struct device *dev, struct restrack_desc *desc);
+	void (*destroy)(struct device *dev, struct restrack_desc *desc);
+	int (*if_up)(struct device *dev, struct restrack_desc *desc,
+		     void *data);
+	void (*if_down)(struct device *dev, struct restrack_desc *desc,
+			void *data);
+};
+
+#define restrack_desc_to_rd(_rd, _var) container_of(_var, typeof(*rd), desc)
+
+#define RESTRACK_DESC_ALLOC(_rd, _ops, _ptr, _name) \
+({ \
+	_rd = kzalloc(sizeof(*_rd), GFP_KERNEL); \
+	if (_rd) { \
+		*_ptr = ERR_PTR(-EPROBE_DEFER); \
+		_rd->ptr = _ptr; \
+		_rd->name = _name; \
+		_rd->desc.ops = &_ops; \
+	} \
+})
+
+/**
+ * struct restrack_desc - resource descriptor
+ * @ops: operations associated with the resource, filled by resource descriptor
+ *	 allocator
+ * @if_id: interface id associated with the resource, filled by @init operation
+ * @itb: iftrack block used by restrack core
+ * @ctx: restrack context used by restrack core
+ * @status: status of the descriptor, used by restrack core
+ */
+struct restrack_desc {
+	const struct restrack_ops *ops;
+	void *if_id;
+	struct track_block itb;
+	struct restrack_ctx *ctx;
+	int status;
+};
+
+typedef void (*restrack_fn_t)(struct device *dev, int ret);
+
+void restrack_unregister(struct restrack_ctx *ctx);
+struct restrack_ctx *__restrack_register(struct device *dev, restrack_fn_t cb,
+		int count, struct restrack_desc **descriptors);
+struct restrack_ctx *__devm_restrack_register(struct device *dev,
+		restrack_fn_t cb, int count,
+		struct restrack_desc **descriptors);
+
+/**
+ * restrack_register - register resource tracking callback
+ * @dev: consumer device
+ * @callback: callback which will be called when:
+ *	- all tracked resources become available, with @ret = 0
+ *	- one of the resources becomes unavailable, with @ret = -EPROBE_DEFER,
+ *	- resource allocation errors, with @ret equal to this error
+ * @descs: list of pointers to resource descriptors to track
+ *
+ * restrack_register is a wrapper macro around @__restrack_register. It can be
+ * called as follows:
+ *	ctx->rtrack = restrack_register(dev, callback,
+ *		regulator_bulk_restrack_desc(&ctx->supplies[0]),
+ *		regulator_bulk_restrack_desc(&ctx->supplies[1]),
+ *		clk_restrack_desc(&ctx->pll_clk, "pll_clk"),
+ *		clk_restrack_desc(&ctx->bus_clk, "bus_clk"),
+ *		phy_restrack_desc(&ctx->phy, "dsim"),
+ *	);
+ */
+#define restrack_register(dev, callback, descs...) \
+({ \
+	struct restrack_desc *desc[] = { descs }; \
+\
+	__restrack_register(dev, callback, ARRAY_SIZE(desc), desc); \
+})
+
+/**
+ * devm_restrack_register - devm version of restrack_register
+ * @dev: consumer device
+ * @callback: callback which will be called when:
+ *	- all tracked resources become available, with @ret = 0
+ *	- one of the resources becomes unavailable, with @ret = -EPROBE_DEFER,
+ *	- resource allocation errors, with @ret equal to this error
+ * @descs: list of pointers to resource descriptors to track
+ *
+ * restrack_register is a wrapper macro around @__restrack_register. It can be
+ * called as follows:
+ *	rtrack = devm_restrack_register(dev, callback,
+ *		regulator_bulk_restrack_desc(&ctx->supplies[0]),
+ *		regulator_bulk_restrack_desc(&ctx->supplies[1]),
+ *		clk_restrack_desc(&ctx->pll_clk, "pll_clk"),
+ *		clk_restrack_desc(&ctx->bus_clk, "bus_clk"),
+ *		phy_restrack_desc(&ctx->phy, "dsim"),
+ *	);
+ */
+#define devm_restrack_register(dev, callback, descs...) \
+({ \
+	struct restrack_desc *desc[] = { descs }; \
+\
+	__devm_restrack_register(dev, callback, ARRAY_SIZE(desc), desc); \
+})
+
+#endif /* RESTRACK_H */