1212 lines · 29.6 KB · C
RawDownload
// 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, &reg->tracker);	if (ref_exists)		refcount_inc(&ref->refcount);	list_add_tail(&reg->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(&reg->list);		__dpll_pin_put(pin, &reg->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, &reg->tracker);	if (ref_exists)		refcount_inc(&ref->refcount);	list_add_tail(&reg->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(&reg->list);		__dpll_device_put(dpll, &reg->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.

Keyboard shortcuts

General

Show keyboard shortcuts
?
Focus the current search
/

Issues and pull requests

Open filters
f

Issues, pull requests and Inbox

Select next item
j↓
Select previous item
k↑
Open selected item
Enter
Dismiss filters or clear selection
Esc

Repository code

Go to file
t