// SPDX-License-Identifier: GPL-2.0/* * dpll_core.c - DPLL subsystem kernel-space interface implementation. * * Copyright (c) 2023 Meta Platforms, Inc. and affiliates * Copyright (c) 2023 Intel Corporation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/device.h>#include <linux/err.h>#include <linux/idr.h>#include <linux/module.h>#include <linux/property.h>#include <linux/slab.h>#include <linux/string.h> #include "dpll_core.h"#include "dpll_netlink.h" /* Mutex lock to protect DPLL subsystem devices and pins */DEFINE_MUTEX(dpll_lock); DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC);DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC); static RAW_NOTIFIER_HEAD(dpll_notifier_chain);static DEFINE_IDA(dpll_pin_idx_ida); static u32 dpll_device_xa_id;static u32 dpll_pin_xa_id; #define ASSERT_DPLL_REGISTERED(d) \ WARN_ON_ONCE(!xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED))#define ASSERT_DPLL_NOT_REGISTERED(d) \ WARN_ON_ONCE(xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED))#define ASSERT_DPLL_PIN_REGISTERED(p) \ WARN_ON_ONCE(!xa_get_mark(&dpll_pin_xa, (p)->id, DPLL_REGISTERED)) struct dpll_device_registration { struct list_head list; const struct dpll_device_ops *ops; void *priv; dpll_tracker tracker;}; struct dpll_pin_registration { struct list_head list; const struct dpll_pin_ops *ops; void *priv; void *cookie; dpll_tracker tracker;}; static int call_dpll_notifiers(unsigned long action, void *info){ lockdep_assert_held(&dpll_lock); return raw_notifier_call_chain(&dpll_notifier_chain, action, info);} void dpll_device_notify(struct dpll_device *dpll, unsigned long action){ struct dpll_device_notifier_info info = { .dpll = dpll, .id = dpll->id, .idx = dpll->device_idx, .clock_id = dpll->clock_id, .type = dpll->type, }; call_dpll_notifiers(action, &info);} void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id, unsigned long action){ struct dpll_pin_notifier_info info = { .pin = pin, .id = pin->id, .idx = pin->pin_idx, .clock_id = pin->clock_id, .fwnode = pin->fwnode, .prop = &pin->prop, .src_clock_id = src_clock_id, }; call_dpll_notifiers(action, &info);} static void dpll_device_tracker_alloc(struct dpll_device *dpll, dpll_tracker *tracker){#ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_alloc(&dpll->refcnt_tracker, tracker, GFP_KERNEL);#endif} static void dpll_device_tracker_free(struct dpll_device *dpll, dpll_tracker *tracker){#ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_free(&dpll->refcnt_tracker, tracker);#endif} static void __dpll_device_hold(struct dpll_device *dpll, dpll_tracker *tracker){ dpll_device_tracker_alloc(dpll, tracker); refcount_inc(&dpll->refcount);} static void __dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker){ dpll_device_tracker_free(dpll, tracker); if (refcount_dec_and_test(&dpll->refcount)) { ASSERT_DPLL_NOT_REGISTERED(dpll); WARN_ON_ONCE(!xa_empty(&dpll->pin_refs)); xa_destroy(&dpll->pin_refs); xa_erase(&dpll_device_xa, dpll->id); WARN_ON(!list_empty(&dpll->registration_list)); ref_tracker_dir_exit(&dpll->refcnt_tracker); kfree(dpll); }} static void dpll_pin_tracker_alloc(struct dpll_pin *pin, dpll_tracker *tracker){#ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_alloc(&pin->refcnt_tracker, tracker, GFP_KERNEL);#endif} static void dpll_pin_tracker_free(struct dpll_pin *pin, dpll_tracker *tracker){#ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_free(&pin->refcnt_tracker, tracker);#endif} static void __dpll_pin_hold(struct dpll_pin *pin, dpll_tracker *tracker){ dpll_pin_tracker_alloc(pin, tracker); refcount_inc(&pin->refcount);} static void dpll_pin_idx_free(u32 pin_idx);static void dpll_pin_prop_free(struct dpll_pin_properties *prop); static void __dpll_pin_put(struct dpll_pin *pin, dpll_tracker *tracker){ dpll_pin_tracker_free(pin, tracker); if (refcount_dec_and_test(&pin->refcount)) { xa_erase(&dpll_pin_xa, pin->id); xa_destroy(&pin->dpll_refs); xa_destroy(&pin->parent_refs); xa_destroy(&pin->ref_sync_pins); dpll_pin_prop_free(&pin->prop); fwnode_handle_put(pin->fwnode); dpll_pin_idx_free(pin->pin_idx); ref_tracker_dir_exit(&pin->refcnt_tracker); kfree_rcu(pin, rcu); }} struct dpll_device *dpll_device_get_by_id(int id){ if (xa_get_mark(&dpll_device_xa, id, DPLL_REGISTERED)) return xa_load(&dpll_device_xa, id); return NULL;} static struct dpll_pin_registration *dpll_pin_registration_find(struct dpll_pin_ref *ref, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; list_for_each_entry(reg, &ref->registration_list, list) { if (reg->ops == ops && reg->priv == priv && reg->cookie == cookie) return reg; } return NULL;} static intdpll_xa_ref_pin_add(struct xarray *xa_pins, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; bool ref_exists = false; unsigned long i; int ret; xa_for_each(xa_pins, i, ref) { if (ref->pin != pin) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (reg) return -EEXIST; ref_exists = true; break; } if (!ref_exists) { ref = kzalloc_obj(*ref); if (!ref) return -ENOMEM; ref->pin = pin; INIT_LIST_HEAD(&ref->registration_list); ret = xa_insert(xa_pins, pin->pin_idx, ref, GFP_KERNEL); if (ret) { kfree(ref); return ret; } refcount_set(&ref->refcount, 1); } reg = kzalloc_obj(*reg); if (!reg) { if (!ref_exists) { xa_erase(xa_pins, pin->pin_idx); kfree(ref); } return -ENOMEM; } reg->ops = ops; reg->priv = priv; reg->cookie = cookie; __dpll_pin_hold(pin, ®->tracker); if (ref_exists) refcount_inc(&ref->refcount); list_add_tail(®->list, &ref->registration_list); return 0;} static int dpll_xa_ref_pin_del(struct xarray *xa_pins, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; unsigned long i; xa_for_each(xa_pins, i, ref) { if (ref->pin != pin) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (WARN_ON(!reg)) return -EINVAL; list_del(®->list); __dpll_pin_put(pin, ®->tracker); kfree(reg); if (refcount_dec_and_test(&ref->refcount)) { xa_erase(xa_pins, i); WARN_ON(!list_empty(&ref->registration_list)); kfree(ref); } return 0; } return -EINVAL;} static intdpll_xa_ref_dpll_add(struct xarray *xa_dplls, struct dpll_device *dpll, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; bool ref_exists = false; unsigned long i; int ret; xa_for_each(xa_dplls, i, ref) { if (ref->dpll != dpll) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (reg) return -EEXIST; ref_exists = true; break; } if (!ref_exists) { ref = kzalloc_obj(*ref); if (!ref) return -ENOMEM; ref->dpll = dpll; INIT_LIST_HEAD(&ref->registration_list); ret = xa_insert(xa_dplls, dpll->id, ref, GFP_KERNEL); if (ret) { kfree(ref); return ret; } refcount_set(&ref->refcount, 1); } reg = kzalloc_obj(*reg); if (!reg) { if (!ref_exists) { xa_erase(xa_dplls, dpll->id); kfree(ref); } return -ENOMEM; } reg->ops = ops; reg->priv = priv; reg->cookie = cookie; __dpll_device_hold(dpll, ®->tracker); if (ref_exists) refcount_inc(&ref->refcount); list_add_tail(®->list, &ref->registration_list); return 0;} static voiddpll_xa_ref_dpll_del(struct xarray *xa_dplls, struct dpll_device *dpll, const struct dpll_pin_ops *ops, void *priv, void *cookie){ struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; unsigned long i; xa_for_each(xa_dplls, i, ref) { if (ref->dpll != dpll) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (WARN_ON(!reg)) return; list_del(®->list); __dpll_device_put(dpll, ®->tracker); kfree(reg); if (refcount_dec_and_test(&ref->refcount)) { xa_erase(xa_dplls, i); WARN_ON(!list_empty(&ref->registration_list)); kfree(ref); } return; }} struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs){ struct dpll_pin_ref *ref; unsigned long i = 0; ref = xa_find(xa_refs, &i, ULONG_MAX, XA_PRESENT); WARN_ON(!ref); return ref;} static struct dpll_device *dpll_device_alloc(const u64 clock_id, u32 device_idx, struct module *module){ struct dpll_device *dpll; int ret; dpll = kzalloc_obj(*dpll); if (!dpll) return ERR_PTR(-ENOMEM); refcount_set(&dpll->refcount, 1); INIT_LIST_HEAD(&dpll->registration_list); dpll->device_idx = device_idx; dpll->clock_id = clock_id; dpll->module = module; ret = xa_alloc_cyclic(&dpll_device_xa, &dpll->id, dpll, xa_limit_32b, &dpll_device_xa_id, GFP_KERNEL); if (ret < 0) { kfree(dpll); return ERR_PTR(ret); } xa_init_flags(&dpll->pin_refs, XA_FLAGS_ALLOC); ref_tracker_dir_init(&dpll->refcnt_tracker, 128, "dpll_device"); return dpll;} /** * dpll_device_get - find existing or create new dpll device * @clock_id: clock_id of creator * @device_idx: idx given by device driver * @module: reference to registering module * @tracker: tracking object for the acquired reference * * Get existing object of a dpll device, unique for given arguments. * Create new if doesn't exist yet. * * Context: Acquires a lock (dpll_lock) * Return: * * valid dpll_device struct pointer if succeeded * * ERR_PTR(X) - error */struct dpll_device *dpll_device_get(u64 clock_id, u32 device_idx, struct module *module, dpll_tracker *tracker){ struct dpll_device *dpll, *ret = NULL; unsigned long index; mutex_lock(&dpll_lock); xa_for_each(&dpll_device_xa, index, dpll) { if (dpll->clock_id == clock_id && dpll->device_idx == device_idx && dpll->module == module) { __dpll_device_hold(dpll, tracker); ret = dpll; break; } } if (!ret) { ret = dpll_device_alloc(clock_id, device_idx, module); if (!IS_ERR(ret)) dpll_device_tracker_alloc(ret, tracker); } mutex_unlock(&dpll_lock); return ret;}EXPORT_SYMBOL_GPL(dpll_device_get); /** * dpll_device_put - decrease the refcount and free memory if possible * @dpll: dpll_device struct pointer * @tracker: tracking object for the acquired reference * * Context: Acquires a lock (dpll_lock) * Drop reference for a dpll device, if all references are gone, delete * dpll device object. */void dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker){ mutex_lock(&dpll_lock); __dpll_device_put(dpll, tracker); mutex_unlock(&dpll_lock);}EXPORT_SYMBOL_GPL(dpll_device_put); static struct dpll_device_registration *dpll_device_registration_find(struct dpll_device *dpll, const struct dpll_device_ops *ops, void *priv){ struct dpll_device_registration *reg; list_for_each_entry(reg, &dpll->registration_list, list) { if (reg->ops == ops && reg->priv == priv) return reg; } return NULL;} /** * dpll_device_register - register the dpll device in the subsystem * @dpll: pointer to a dpll * @type: type of a dpll * @ops: ops for a dpll device * @priv: pointer to private information of owner * * Make dpll device available for user space. * * Context: Acquires a lock (dpll_lock) * Return: * * 0 on success * * negative - error value */int dpll_device_register(struct dpll_device *dpll, enum dpll_type type, const struct dpll_device_ops *ops, void *priv){ struct dpll_device_registration *reg; bool first_registration = false; if (WARN_ON(!ops)) return -EINVAL; if (WARN_ON(!ops->mode_get)) return -EINVAL; if (WARN_ON(!ops->lock_status_get)) return -EINVAL; if (WARN_ON(type < DPLL_TYPE_PPS || type > DPLL_TYPE_MAX)) return -EINVAL; mutex_lock(&dpll_lock); reg = dpll_device_registration_find(dpll, ops, priv); if (reg) { mutex_unlock(&dpll_lock); return -EEXIST; } reg = kzalloc_obj(*reg); if (!reg) { mutex_unlock(&dpll_lock); return -ENOMEM; }Lines 1–500 of 1212. Load more
// SPDX-License-Identifier: GPL-2.0 /* * dpll_core.c - DPLL subsystem kernel-space interface implementation. * * Copyright (c) 2023 Meta Platforms, Inc. and affiliates * Copyright (c) 2023 Intel Corporation. */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt #include <linux/device.h> #include <linux/err.h> #include <linux/idr.h> #include <linux/module.h> #include <linux/property.h> #include <linux/slab.h> #include <linux/string.h> #include "dpll_core.h" #include "dpll_netlink.h" /* Mutex lock to protect DPLL subsystem devices and pins */ DEFINE_MUTEX(dpll_lock); DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC); DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC); static RAW_NOTIFIER_HEAD(dpll_notifier_chain); static DEFINE_IDA(dpll_pin_idx_ida); static u32 dpll_device_xa_id; static u32 dpll_pin_xa_id; #define ASSERT_DPLL_REGISTERED(d) \ WARN_ON_ONCE(!xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED)) #define ASSERT_DPLL_NOT_REGISTERED(d) \ WARN_ON_ONCE(xa_get_mark(&dpll_device_xa, (d)->id, DPLL_REGISTERED)) #define ASSERT_DPLL_PIN_REGISTERED(p) \ WARN_ON_ONCE(!xa_get_mark(&dpll_pin_xa, (p)->id, DPLL_REGISTERED)) struct dpll_device_registration { struct list_head list; const struct dpll_device_ops *ops; void *priv; dpll_tracker tracker; }; struct dpll_pin_registration { struct list_head list; const struct dpll_pin_ops *ops; void *priv; void *cookie; dpll_tracker tracker; }; static int call_dpll_notifiers(unsigned long action, void *info) { lockdep_assert_held(&dpll_lock); return raw_notifier_call_chain(&dpll_notifier_chain, action, info); } void dpll_device_notify(struct dpll_device *dpll, unsigned long action) { struct dpll_device_notifier_info info = { .dpll = dpll, .id = dpll->id, .idx = dpll->device_idx, .clock_id = dpll->clock_id, .type = dpll->type, }; call_dpll_notifiers(action, &info); } void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id, unsigned long action) { struct dpll_pin_notifier_info info = { .pin = pin, .id = pin->id, .idx = pin->pin_idx, .clock_id = pin->clock_id, .fwnode = pin->fwnode, .prop = &pin->prop, .src_clock_id = src_clock_id, }; call_dpll_notifiers(action, &info); } static void dpll_device_tracker_alloc(struct dpll_device *dpll, dpll_tracker *tracker) { #ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_alloc(&dpll->refcnt_tracker, tracker, GFP_KERNEL); #endif } static void dpll_device_tracker_free(struct dpll_device *dpll, dpll_tracker *tracker) { #ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_free(&dpll->refcnt_tracker, tracker); #endif } static void __dpll_device_hold(struct dpll_device *dpll, dpll_tracker *tracker) { dpll_device_tracker_alloc(dpll, tracker); refcount_inc(&dpll->refcount); } static void __dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker) { dpll_device_tracker_free(dpll, tracker); if (refcount_dec_and_test(&dpll->refcount)) { ASSERT_DPLL_NOT_REGISTERED(dpll); WARN_ON_ONCE(!xa_empty(&dpll->pin_refs)); xa_destroy(&dpll->pin_refs); xa_erase(&dpll_device_xa, dpll->id); WARN_ON(!list_empty(&dpll->registration_list)); ref_tracker_dir_exit(&dpll->refcnt_tracker); kfree(dpll); } } static void dpll_pin_tracker_alloc(struct dpll_pin *pin, dpll_tracker *tracker) { #ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_alloc(&pin->refcnt_tracker, tracker, GFP_KERNEL); #endif } static void dpll_pin_tracker_free(struct dpll_pin *pin, dpll_tracker *tracker) { #ifdef CONFIG_DPLL_REFCNT_TRACKER ref_tracker_free(&pin->refcnt_tracker, tracker); #endif } static void __dpll_pin_hold(struct dpll_pin *pin, dpll_tracker *tracker) { dpll_pin_tracker_alloc(pin, tracker); refcount_inc(&pin->refcount); } static void dpll_pin_idx_free(u32 pin_idx); static void dpll_pin_prop_free(struct dpll_pin_properties *prop); static void __dpll_pin_put(struct dpll_pin *pin, dpll_tracker *tracker) { dpll_pin_tracker_free(pin, tracker); if (refcount_dec_and_test(&pin->refcount)) { xa_erase(&dpll_pin_xa, pin->id); xa_destroy(&pin->dpll_refs); xa_destroy(&pin->parent_refs); xa_destroy(&pin->ref_sync_pins); dpll_pin_prop_free(&pin->prop); fwnode_handle_put(pin->fwnode); dpll_pin_idx_free(pin->pin_idx); ref_tracker_dir_exit(&pin->refcnt_tracker); kfree_rcu(pin, rcu); } } struct dpll_device *dpll_device_get_by_id(int id) { if (xa_get_mark(&dpll_device_xa, id, DPLL_REGISTERED)) return xa_load(&dpll_device_xa, id); return NULL; } static struct dpll_pin_registration * dpll_pin_registration_find(struct dpll_pin_ref *ref, const struct dpll_pin_ops *ops, void *priv, void *cookie) { struct dpll_pin_registration *reg; list_for_each_entry(reg, &ref->registration_list, list) { if (reg->ops == ops && reg->priv == priv && reg->cookie == cookie) return reg; } return NULL; } static int dpll_xa_ref_pin_add(struct xarray *xa_pins, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie) { struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; bool ref_exists = false; unsigned long i; int ret; xa_for_each(xa_pins, i, ref) { if (ref->pin != pin) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (reg) return -EEXIST; ref_exists = true; break; } if (!ref_exists) { ref = kzalloc_obj(*ref); if (!ref) return -ENOMEM; ref->pin = pin; INIT_LIST_HEAD(&ref->registration_list); ret = xa_insert(xa_pins, pin->pin_idx, ref, GFP_KERNEL); if (ret) { kfree(ref); return ret; } refcount_set(&ref->refcount, 1); } reg = kzalloc_obj(*reg); if (!reg) { if (!ref_exists) { xa_erase(xa_pins, pin->pin_idx); kfree(ref); } return -ENOMEM; } reg->ops = ops; reg->priv = priv; reg->cookie = cookie; __dpll_pin_hold(pin, ®->tracker); if (ref_exists) refcount_inc(&ref->refcount); list_add_tail(®->list, &ref->registration_list); return 0; } static int dpll_xa_ref_pin_del(struct xarray *xa_pins, struct dpll_pin *pin, const struct dpll_pin_ops *ops, void *priv, void *cookie) { struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; unsigned long i; xa_for_each(xa_pins, i, ref) { if (ref->pin != pin) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (WARN_ON(!reg)) return -EINVAL; list_del(®->list); __dpll_pin_put(pin, ®->tracker); kfree(reg); if (refcount_dec_and_test(&ref->refcount)) { xa_erase(xa_pins, i); WARN_ON(!list_empty(&ref->registration_list)); kfree(ref); } return 0; } return -EINVAL; } static int dpll_xa_ref_dpll_add(struct xarray *xa_dplls, struct dpll_device *dpll, const struct dpll_pin_ops *ops, void *priv, void *cookie) { struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; bool ref_exists = false; unsigned long i; int ret; xa_for_each(xa_dplls, i, ref) { if (ref->dpll != dpll) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (reg) return -EEXIST; ref_exists = true; break; } if (!ref_exists) { ref = kzalloc_obj(*ref); if (!ref) return -ENOMEM; ref->dpll = dpll; INIT_LIST_HEAD(&ref->registration_list); ret = xa_insert(xa_dplls, dpll->id, ref, GFP_KERNEL); if (ret) { kfree(ref); return ret; } refcount_set(&ref->refcount, 1); } reg = kzalloc_obj(*reg); if (!reg) { if (!ref_exists) { xa_erase(xa_dplls, dpll->id); kfree(ref); } return -ENOMEM; } reg->ops = ops; reg->priv = priv; reg->cookie = cookie; __dpll_device_hold(dpll, ®->tracker); if (ref_exists) refcount_inc(&ref->refcount); list_add_tail(®->list, &ref->registration_list); return 0; } static void dpll_xa_ref_dpll_del(struct xarray *xa_dplls, struct dpll_device *dpll, const struct dpll_pin_ops *ops, void *priv, void *cookie) { struct dpll_pin_registration *reg; struct dpll_pin_ref *ref; unsigned long i; xa_for_each(xa_dplls, i, ref) { if (ref->dpll != dpll) continue; reg = dpll_pin_registration_find(ref, ops, priv, cookie); if (WARN_ON(!reg)) return; list_del(®->list); __dpll_device_put(dpll, ®->tracker); kfree(reg); if (refcount_dec_and_test(&ref->refcount)) { xa_erase(xa_dplls, i); WARN_ON(!list_empty(&ref->registration_list)); kfree(ref); } return; } } struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs) { struct dpll_pin_ref *ref; unsigned long i = 0; ref = xa_find(xa_refs, &i, ULONG_MAX, XA_PRESENT); WARN_ON(!ref); return ref; } static struct dpll_device * dpll_device_alloc(const u64 clock_id, u32 device_idx, struct module *module) { struct dpll_device *dpll; int ret; dpll = kzalloc_obj(*dpll); if (!dpll) return ERR_PTR(-ENOMEM); refcount_set(&dpll->refcount, 1); INIT_LIST_HEAD(&dpll->registration_list); dpll->device_idx = device_idx; dpll->clock_id = clock_id; dpll->module = module; ret = xa_alloc_cyclic(&dpll_device_xa, &dpll->id, dpll, xa_limit_32b, &dpll_device_xa_id, GFP_KERNEL); if (ret < 0) { kfree(dpll); return ERR_PTR(ret); } xa_init_flags(&dpll->pin_refs, XA_FLAGS_ALLOC); ref_tracker_dir_init(&dpll->refcnt_tracker, 128, "dpll_device"); return dpll; } /** * dpll_device_get - find existing or create new dpll device * @clock_id: clock_id of creator * @device_idx: idx given by device driver * @module: reference to registering module * @tracker: tracking object for the acquired reference * * Get existing object of a dpll device, unique for given arguments. * Create new if doesn't exist yet. * * Context: Acquires a lock (dpll_lock) * Return: * * valid dpll_device struct pointer if succeeded * * ERR_PTR(X) - error */ struct dpll_device * dpll_device_get(u64 clock_id, u32 device_idx, struct module *module, dpll_tracker *tracker) { struct dpll_device *dpll, *ret = NULL; unsigned long index; mutex_lock(&dpll_lock); xa_for_each(&dpll_device_xa, index, dpll) { if (dpll->clock_id == clock_id && dpll->device_idx == device_idx && dpll->module == module) { __dpll_device_hold(dpll, tracker); ret = dpll; break; } } if (!ret) { ret = dpll_device_alloc(clock_id, device_idx, module); if (!IS_ERR(ret)) dpll_device_tracker_alloc(ret, tracker); } mutex_unlock(&dpll_lock); return ret; } EXPORT_SYMBOL_GPL(dpll_device_get); /** * dpll_device_put - decrease the refcount and free memory if possible * @dpll: dpll_device struct pointer * @tracker: tracking object for the acquired reference * * Context: Acquires a lock (dpll_lock) * Drop reference for a dpll device, if all references are gone, delete * dpll device object. */ void dpll_device_put(struct dpll_device *dpll, dpll_tracker *tracker) { mutex_lock(&dpll_lock); __dpll_device_put(dpll, tracker); mutex_unlock(&dpll_lock); } EXPORT_SYMBOL_GPL(dpll_device_put); static struct dpll_device_registration * dpll_device_registration_find(struct dpll_device *dpll, const struct dpll_device_ops *ops, void *priv) { struct dpll_device_registration *reg; list_for_each_entry(reg, &dpll->registration_list, list) { if (reg->ops == ops && reg->priv == priv) return reg; } return NULL; } /** * dpll_device_register - register the dpll device in the subsystem * @dpll: pointer to a dpll * @type: type of a dpll * @ops: ops for a dpll device * @priv: pointer to private information of owner * * Make dpll device available for user space. * * Context: Acquires a lock (dpll_lock) * Return: * * 0 on success * * negative - error value */ int dpll_device_register(struct dpll_device *dpll, enum dpll_type type, const struct dpll_device_ops *ops, void *priv) { struct dpll_device_registration *reg; bool first_registration = false; if (WARN_ON(!ops)) return -EINVAL; if (WARN_ON(!ops->mode_get)) return -EINVAL; if (WARN_ON(!ops->lock_status_get)) return -EINVAL; if (WARN_ON(type < DPLL_TYPE_PPS || type > DPLL_TYPE_MAX)) return -EINVAL; mutex_lock(&dpll_lock); reg = dpll_device_registration_find(dpll, ops, priv); if (reg) { mutex_unlock(&dpll_lock); return -EEXIST; } reg = kzalloc_obj(*reg); if (!reg) { mutex_unlock(&dpll_lock); return -ENOMEM; }