#include <linux/reset.h>
#include <linux/reset-controller.h>
#include <linux/slab.h>
+#include <linux/srcu.h>
static DEFINE_MUTEX(reset_list_mutex);
static LIST_HEAD(reset_controller_list);
* struct reset_control - a reset control
* @rcdev: a pointer to the reset controller device
* this reset control belongs to
+ * @srcu: protects the rcdev pointer from removal during consumer access
* @list: list entry for the rcdev's reset controller list
* @id: ID of the reset controller in the reset
* controller device
* will be either 0 or 1.
*/
struct reset_control {
- struct reset_controller_dev *rcdev;
+ struct reset_controller_dev __rcu *rcdev;
+ struct srcu_struct srcu;
struct list_head list;
unsigned int id;
struct kref refcnt;
}
EXPORT_SYMBOL_GPL(reset_controller_register);
+static void reset_controller_remove(struct reset_controller_dev *rcdev,
+ struct reset_control *rstc)
+{
+ list_del(&rstc->list);
+ module_put(rcdev->owner);
+ put_device(rcdev->dev);
+}
+
/**
* reset_controller_unregister - unregister a reset controller device
* @rcdev: a pointer to the reset controller device
*/
void reset_controller_unregister(struct reset_controller_dev *rcdev)
{
+ struct reset_control *rstc, *pos;
+
guard(mutex)(&reset_list_mutex);
list_del(&rcdev->list);
+
+ /*
+ * Numb but don't free the remaining reset control handles that are
+ * still held by consumers.
+ */
+ list_for_each_entry_safe(rstc, pos, &rcdev->reset_control_head, list) {
+ rcu_assign_pointer(rstc->rcdev, NULL);
+ synchronize_srcu(&rstc->srcu);
+ reset_controller_remove(rcdev, rstc);
+ }
}
EXPORT_SYMBOL_GPL(reset_controller_unregister);
*/
int reset_control_reset(struct reset_control *rstc)
{
+ struct reset_controller_dev *rcdev;
int ret;
if (!rstc)
if (reset_control_is_array(rstc))
return reset_control_array_reset(rstc_to_array(rstc));
- if (!rstc->rcdev->ops->reset)
+ guard(srcu)(&rstc->srcu);
+
+ rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
+ if (!rcdev)
+ return -ENODEV;
+
+ if (!rcdev->ops->reset)
return -ENOTSUPP;
if (rstc->shared) {
return -EPERM;
}
- ret = rstc->rcdev->ops->reset(rstc->rcdev, rstc->id);
+ ret = rcdev->ops->reset(rcdev, rstc->id);
if (rstc->shared && ret)
atomic_dec(&rstc->triggered_count);
*/
int reset_control_assert(struct reset_control *rstc)
{
+ struct reset_controller_dev *rcdev;
+
if (!rstc)
return 0;
if (reset_control_is_array(rstc))
return reset_control_array_assert(rstc_to_array(rstc));
+ guard(srcu)(&rstc->srcu);
+
+ rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
+ if (!rcdev)
+ return -ENODEV;
+
if (rstc->shared) {
if (WARN_ON(atomic_read(&rstc->triggered_count) != 0))
return -EINVAL;
* Shared reset controls allow the reset line to be in any state
* after this call, so doing nothing is a valid option.
*/
- if (!rstc->rcdev->ops->assert)
+ if (!rcdev->ops->assert)
return 0;
} else {
/*
* is no way to guarantee that the reset line is asserted after
* this call.
*/
- if (!rstc->rcdev->ops->assert)
+ if (!rcdev->ops->assert)
return -ENOTSUPP;
if (!rstc->acquired) {
WARN(1, "reset %s (ID: %u) is not acquired\n",
- rcdev_name(rstc->rcdev), rstc->id);
+ rcdev_name(rcdev), rstc->id);
return -EPERM;
}
}
- return rstc->rcdev->ops->assert(rstc->rcdev, rstc->id);
+ return rcdev->ops->assert(rcdev, rstc->id);
}
EXPORT_SYMBOL_GPL(reset_control_assert);
*/
int reset_control_deassert(struct reset_control *rstc)
{
+ struct reset_controller_dev *rcdev;
+
if (!rstc)
return 0;
if (reset_control_is_array(rstc))
return reset_control_array_deassert(rstc_to_array(rstc));
+ guard(srcu)(&rstc->srcu);
+
+ rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
+ if (!rcdev)
+ return -ENODEV;
+
if (rstc->shared) {
if (WARN_ON(atomic_read(&rstc->triggered_count) != 0))
return -EINVAL;
} else {
if (!rstc->acquired) {
WARN(1, "reset %s (ID: %u) is not acquired\n",
- rcdev_name(rstc->rcdev), rstc->id);
+ rcdev_name(rcdev), rstc->id);
return -EPERM;
}
}
* case, the reset controller driver should implement .deassert() and
* return -ENOTSUPP.
*/
- if (!rstc->rcdev->ops->deassert)
+ if (!rcdev->ops->deassert)
return 0;
- return rstc->rcdev->ops->deassert(rstc->rcdev, rstc->id);
+ return rcdev->ops->deassert(rcdev, rstc->id);
}
EXPORT_SYMBOL_GPL(reset_control_deassert);
*/
int reset_control_status(struct reset_control *rstc)
{
+ struct reset_controller_dev *rcdev;
+
if (!rstc)
return 0;
if (WARN_ON(IS_ERR(rstc)) || reset_control_is_array(rstc))
return -EINVAL;
- if (rstc->rcdev->ops->status)
- return rstc->rcdev->ops->status(rstc->rcdev, rstc->id);
+ guard(srcu)(&rstc->srcu);
+
+ rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
+ if (!rcdev)
+ return -ENODEV;
+
+ if (rcdev->ops->status)
+ return rcdev->ops->status(rcdev, rstc->id);
return -ENOTSUPP;
}
*/
int reset_control_acquire(struct reset_control *rstc)
{
+ struct reset_controller_dev *rcdev;
struct reset_control *rc;
if (!rstc)
if (rstc->acquired)
return 0;
- list_for_each_entry(rc, &rstc->rcdev->reset_control_head, list) {
+ guard(srcu)(&rstc->srcu);
+
+ rcdev = srcu_dereference(rstc->rcdev, &rstc->srcu);
+ if (!rcdev)
+ return -ENODEV;
+
+ list_for_each_entry(rc, &rcdev->reset_control_head, list) {
if (rstc != rc && rstc->id == rc->id) {
if (rc->acquired)
return -EBUSY;
bool shared = flags & RESET_CONTROL_FLAGS_BIT_SHARED;
bool acquired = flags & RESET_CONTROL_FLAGS_BIT_ACQUIRED;
struct reset_control *rstc;
+ int ret;
lockdep_assert_held(&reset_list_mutex);
if (!rstc)
return ERR_PTR(-ENOMEM);
+ ret = init_srcu_struct(&rstc->srcu);
+ if (ret) {
+ kfree(rstc);
+ return ERR_PTR(ret);
+ }
+
if (!try_module_get(rcdev->owner)) {
+ cleanup_srcu_struct(&rstc->srcu);
kfree(rstc);
return ERR_PTR(-ENODEV);
}
- rstc->rcdev = rcdev;
+ rcu_assign_pointer(rstc->rcdev, rcdev);
list_add(&rstc->list, &rcdev->reset_control_head);
rstc->id = index;
kref_init(&rstc->refcnt);
{
struct reset_control *rstc = container_of(kref, struct reset_control,
refcnt);
+ struct reset_controller_dev *rcdev;
lockdep_assert_held(&reset_list_mutex);
- module_put(rstc->rcdev->owner);
+ scoped_guard(srcu, &rstc->srcu) {
+ rcdev = rcu_replace_pointer(rstc->rcdev, NULL, true);
+ if (rcdev)
+ reset_controller_remove(rcdev, rstc);
+ }
- list_del(&rstc->list);
- put_device(rstc->rcdev->dev);
+ synchronize_srcu(&rstc->srcu);
+ cleanup_srcu_struct(&rstc->srcu);
kfree(rstc);
}