[RFC PATCH v1 1/3] dpll: Add DPLL framework base functions

Kubalewski, Arkadiusz arkadiusz.kubalewski at intel.com
Fri Jun 24 10:36:21 PDT 2022


-----Original Message-----
From: Vadim Fedorenko <vfedorenko at novek.ru> 
Sent: Friday, June 24, 2022 12:48 AM

>Hi Arkadiusz!
>
>On 23.06.2022 16:33, Kubalewski, Arkadiusz wrote:
>> Hi Vadim,
>> 
>> Great work!
>> 
>> Although, I've been thinking that you already forget about it, so I have
>> started development of something similar.
>>
>
>Sorry for making you wait for so long. I'm happy to merge your work into these 
>series and to continue collaboration to further improve subsystem.

Not a problem, sounds great!

>
>>> +# SPDX-License-Identifier: GPL-2.0-only
>>> +#
>>> +# Generic DPLL drivers configuration
>>> +#
>>> +
>>> +config DPLL
>>> +  bool
>> 
>> for RFC help and default were ommited?
>> 
>
>In private conversation with Jakub we decided to hide this subsystem from user
>facing menu and enable via in-kernel customer's dependency. If you think it's 
>better to let users enable or disable it, we can bring this discussion back to 
>wider audience.

Well, I won't insist on it. Seems fair.

>
>>> diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile
>>> new file mode 100644
>>> index 000000000000..0748c80097e4
>>> --- /dev/null
>>> +++ b/drivers/dpll/Makefile
>>> @@ -0,0 +1,7 @@
>>> +# SPDX-License-Identifier: GPL-2.0
>>> +#
>>> +# Makefile for DPLL drivers.
>>> +#
>>> +
>>> +obj-$(CONFIG_DPLL)          += dpll_sys.o
>>> +dpll_sys-y                  += dpll_core.o dpll_netlink.o
>>> diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c
>>> new file mode 100644
>>> index 000000000000..e34767e723cf
>>> --- /dev/null
>>> +++ b/drivers/dpll/dpll_core.c
>>> @@ -0,0 +1,152 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + *  dpll_core.c - Generic DPLL Management class support.
>>> + *
>>> + *  Copyright (c) 2021 Meta Platforms, Inc. and affiliates
>>> + */
>>> +
>>> +#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
>>> +
>>> +#include <linux/device.h>
>>> +#include <linux/err.h>
>>> +#include <linux/slab.h>
>>> +#include <linux/string.h>
>>> +
>>> +#include "dpll_core.h"
>>> +
>>> +static DEFINE_MUTEX(dpll_device_xa_lock);
>>> +static DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC);
>>> +#define DPLL_REGISTERED XA_MARK_1
>>> +
>>> +#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))
>>> +
>>> +
>>> +int for_each_dpll_device(int id, int (*cb)(struct dpll_device *, void *), void *data)
>>> +{
>>> +	struct dpll_device *dpll;
>>> +	unsigned long index;
>>> +	int ret = 0;
>>> +
>>> +	mutex_lock(&dpll_device_xa_lock);
>>> +	xa_for_each_start(&dpll_device_xa, index, dpll, id) {
>>> +		if (!xa_get_mark(&dpll_device_xa, index, DPLL_REGISTERED))
>>> +			continue;
>>> +		ret = cb(dpll, data);
>>> +		if (ret)
>>> +			break;
>>> +	}
>>> +	mutex_unlock(&dpll_device_xa_lock);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +struct dpll_device *dpll_device_get_by_id(int id)
>>> +{
>>> +	struct dpll_device *dpll = NULL;
>>> +
>>> +	if (xa_get_mark(&dpll_device_xa, id, DPLL_REGISTERED))
>>> +		dpll = xa_load(&dpll_device_xa, id);
>>> +	return dpll;
>>> +}
>>> +
>>> +void *dpll_priv(struct dpll_device *dpll)
>>> +{
>>> +	return dpll->priv;
>>> +}
>>> +EXPORT_SYMBOL_GPL(dpll_priv);
>>> +
>>> +static void dpll_device_release(struct device *dev)
>>> +{
>>> +	struct dpll_device *dpll;
>>> +
>>> +	dpll = to_dpll_device(dev);
>>> +
>>> +	dpll_device_unregister(dpll);
>>> +
>>> +	mutex_destroy(&dpll->lock);
>>> +	kfree(dpll);
>>> +}
>>> +
>>> +static struct class dpll_class = {
>>> +	.name = "dpll",
>>> +	.dev_release = dpll_device_release,
>>> +};
>>> +
>>> +struct dpll_device *dpll_device_alloc(struct dpll_device_ops *ops, int sources_count,
>>> +					 int outputs_count, void *priv)
>>> +{
>>> +	struct dpll_device *dpll;
>>> +	int ret;
>>> +
>>> +	dpll = kzalloc(sizeof(*dpll), GFP_KERNEL);
>>> +	if (!dpll)
>>> +		return ERR_PTR(-ENOMEM);
>>> +
>>> +	mutex_init(&dpll->lock);
>>> +	dpll->ops = ops;
>>> +	dpll->dev.class = &dpll_class;
>>> +	dpll->sources_count = sources_count;
>>> +	dpll->outputs_count = outputs_count;
>>> +
>>> +	mutex_lock(&dpll_device_xa_lock);
>>> +	ret = xa_alloc(&dpll_device_xa, &dpll->id, dpll, xa_limit_16b, GFP_KERNEL);
>>> +	if (ret)
>>> +		goto error;
>>> +	dev_set_name(&dpll->dev, "dpll%d", dpll->id);
>>> +	mutex_unlock(&dpll_device_xa_lock);
>>> +	dpll->priv = priv;
>>> +
>>> +	return dpll;
>>> +
>>> +error:
>>> +	mutex_unlock(&dpll_device_xa_lock);
>>> +	kfree(dpll);
>>> +	return ERR_PTR(ret);
>>> +}
>>> +EXPORT_SYMBOL_GPL(dpll_device_alloc);
>>> +
>>> +void dpll_device_register(struct dpll_device *dpll)
>>> +{
>>> +	ASSERT_DPLL_NOT_REGISTERED(dpll);
>>> +
>>> +	mutex_lock(&dpll_device_xa_lock);
>>> +	xa_set_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED);
>>> +	dpll_notify_device_create(dpll->id, dev_name(&dpll->dev));
>>> +	mutex_unlock(&dpll_device_xa_lock);
>>> +}
>>> +EXPORT_SYMBOL_GPL(dpll_device_register);
>>> +
>>> +void dpll_device_unregister(struct dpll_device *dpll)
>>> +{
>>> +	ASSERT_DPLL_REGISTERED(dpll);
>>> +
>>> +	mutex_lock(&dpll_device_xa_lock);
>>> +	xa_erase(&dpll_device_xa, dpll->id);
>>> +	mutex_unlock(&dpll_device_xa_lock);
>>> +}
>>> +EXPORT_SYMBOL_GPL(dpll_device_unregister);
>>> +
>>> +static int __init dpll_init(void)
>>> +{
>>> +	int ret;
>>> +
>>> +	ret = dpll_netlink_init();
>>> +	if (ret)
>>> +		goto error;
>>> +
>>> +	ret = class_register(&dpll_class);
>>> +	if (ret)
>>> +		goto unregister_netlink;
>>> +
>>> +	return 0;
>>> +
>>> +unregister_netlink:
>>> +	dpll_netlink_finish();
>>> +error:
>>> +	mutex_destroy(&dpll_device_xa_lock);
>>> +	return ret;
>>> +}
>>> +subsys_initcall(dpll_init);
>>> diff --git a/drivers/dpll/dpll_core.h b/drivers/dpll/dpll_core.h
>>> new file mode 100644
>>> index 000000000000..5ad3224d5caf
>>> --- /dev/null
>>> +++ b/drivers/dpll/dpll_core.h
>>> @@ -0,0 +1,40 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + *  Copyright (c) 2021 Meta Platforms, Inc. and affiliates
>>> + */
>>> +
>>> +#ifndef __DPLL_CORE_H__
>>> +#define __DPLL_CORE_H__
>>> +
>>> +#include <linux/dpll.h>
>>> +
>>> +#include "dpll_netlink.h"
>>> +
>>> +/**
>>> + * struct dpll_device - structure for a DPLL device
>>> + * @id:		unique id number for each edvice
>>> + * @dev:	&struct device for this dpll device
>>> + * @sources_count:	amount of input sources this dpll_device supports
>>> + * @outputs_count:	amount of outputs this dpll_device supports
>>> + * @ops:	operations this &dpll_device supports
>>> + * @lock:	mutex to serialize operations
>>> + * @priv:	pointer to private information of owner
>>> + */
>>> +struct dpll_device {
>>> +	int id;
>>> +	struct device dev;
>>> +	int sources_count;
>>> +	int outputs_count;
>>> +	struct dpll_device_ops *ops;
>>> +	struct mutex lock;
>>> +	void *priv;
>>> +};
>>> +
>>> +#define to_dpll_device(_dev) \
>>> +	container_of(_dev, struct dpll_device, dev)
>>> +
>>> +int for_each_dpll_device(int id, int (*cb)(struct dpll_device *, void *),
>>> +			  void *data);
>>> +struct dpll_device *dpll_device_get_by_id(int id);
>>> +void dpll_device_unregister(struct dpll_device *dpll);
>>> +#endif
>>> diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c
>>> new file mode 100644
>>> index 000000000000..0bbdaa6dde8e
>>> --- /dev/null
>>> +++ b/drivers/dpll/dpll_netlink.c
>>> @@ -0,0 +1,437 @@
>>> +// SPDX-License-Identifier: GPL-2.0
>>> +/*
>>> + * Generic netlink for DPLL management framework
>>> + *
>>> + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates
>>> + *
>>> + */
>>> +#include <linux/module.h>
>>> +#include <linux/kernel.h>
>>> +#include <net/genetlink.h>
>>> +#include "dpll_core.h"
>>> +
>>> +#include <uapi/linux/dpll.h>
>>> +
>>> +static const struct genl_multicast_group dpll_genl_mcgrps[] = {
>>> +	{ .name = DPLL_CONFIG_DEVICE_GROUP_NAME, },
>>> +	{ .name = DPLL_CONFIG_SOURCE_GROUP_NAME, },
>>> +	{ .name = DPLL_CONFIG_OUTPUT_GROUP_NAME, },
>>> +	{ .name = DPLL_MONITOR_GROUP_NAME,  },
>>> +};
>>> +
>>> +static const struct nla_policy dpll_genl_get_policy[] = {
>>> +	[DPLLA_DEVICE_ID]	= { .type = NLA_U32 },
>>> +	[DPLLA_DEVICE_NAME]	= { .type = NLA_STRING,
>>> +				    .len = DPLL_NAME_LENGTH },
>>> +	[DPLLA_FLAGS]		= { .type = NLA_U32 },
>>> +};
>>> +
>>> +static const struct nla_policy dpll_genl_set_source_policy[] = {
>>> +	[DPLLA_DEVICE_ID]	= { .type = NLA_U32 },
>>> +	[DPLLA_SOURCE_ID]	= { .type = NLA_U32 },
>>> +	[DPLLA_SOURCE_TYPE]	= { .type = NLA_U32 },
>>> +};
>>> +
>>> +static const struct nla_policy dpll_genl_set_output_policy[] = {
>>> +	[DPLLA_DEVICE_ID]	= { .type = NLA_U32 },
>>> +	[DPLLA_OUTPUT_ID]	= { .type = NLA_U32 },
>>> +	[DPLLA_OUTPUT_TYPE]	= { .type = NLA_U32 },
>>> +};
>>> +
>>> +struct param {
>>> +	struct netlink_callback *cb;
>>> +	struct dpll_device *dpll;
>>> +	struct nlattr **attrs;
>>> +	struct sk_buff *msg;
>>> +	int dpll_id;
>>> +	int dpll_source_id;
>>> +	int dpll_source_type;
>>> +	int dpll_output_id;
>>> +	int dpll_output_type;
>>> +};
>>> +
>>> +struct dpll_dump_ctx {
>>> +	struct dpll_device *dev;
>>> +	int flags;
>>> +	int pos_idx;
>>> +	int pos_src_idx;
>>> +	int pos_out_idx;
>>> +};
>>> +
>>> +typedef int (*cb_t)(struct param *);
>>> +
>>> +static struct genl_family dpll_gnl_family;
>>> +
>>> +static struct dpll_dump_ctx *dpll_dump_context(struct netlink_callback *cb)
>>> +{
>>> +	return (struct dpll_dump_ctx *)cb->ctx;
>>> +}
>>> +
>>> +static int __dpll_cmd_device_dump_one(struct dpll_device *dpll,
>>> +					   struct sk_buff *msg)
>>> +{
>>> +	if (nla_put_u32(msg, DPLLA_DEVICE_ID, dpll->id))
>>> +		return -EMSGSIZE;
>>> +
>>> +	if (nla_put_string(msg, DPLLA_DEVICE_NAME, dev_name(&dpll->dev)))
>>> +		return -EMSGSIZE;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int __dpll_cmd_dump_sources(struct dpll_device *dpll,
>>> +					   struct sk_buff *msg)
>>> +{
>>> +	struct nlattr *src_attr;
>>> +	int i, ret = 0, type;
>>> +
>>> +	for (i = 0; i < dpll->sources_count; i++) {
>>> +		src_attr = nla_nest_start(msg, DPLLA_SOURCE);
>>> +		if (!src_attr) {
>>> +			ret = -EMSGSIZE;
>>> +			break;
>>> +		}
>>> +		type = dpll->ops->get_source_type(dpll, i);
>>> +		if (nla_put_u32(msg, DPLLA_SOURCE_ID, i) ||
>>> +		    nla_put_u32(msg, DPLLA_SOURCE_TYPE, type)) {
>>> +			nla_nest_cancel(msg, src_attr);
>>> +			ret = -EMSGSIZE;
>>> +			break;
>>> +		}
>>> +		nla_nest_end(msg, src_attr);
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int __dpll_cmd_dump_outputs(struct dpll_device *dpll,
>>> +					   struct sk_buff *msg)
>>> +{
>>> +	struct nlattr *out_attr;
>>> +	int i, ret = 0, type;
>>> +
>>> +	for (i = 0; i < dpll->outputs_count; i++) {
>>> +		out_attr = nla_nest_start(msg, DPLLA_OUTPUT);
>>> +		if (!out_attr) {
>>> +			ret = -EMSGSIZE;
>>> +			break;
>>> +		}
>>> +		type = dpll->ops->get_source_type(dpll, i);
>>> +		if (nla_put_u32(msg, DPLLA_OUTPUT_ID, i) ||
>>> +		    nla_put_u32(msg, DPLLA_OUTPUT_TYPE, type)) {
>>> +			nla_nest_cancel(msg, out_attr);
>>> +			ret = -EMSGSIZE;
>>> +			break;
>>> +		}
>>> +		nla_nest_end(msg, out_attr);
>>> +	}
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int __dpll_cmd_dump_status(struct dpll_device *dpll,
>>> +					   struct sk_buff *msg)
>>> +{
>>> +	int ret;
>>> +
>>> +	if (!dpll->ops->get_status && !dpll->ops->get_temp && !dpll->ops->get_lock_status)
>>> +		return 0;
>> 
>> what if dpll doesn't support one of those commands?
>> 
>
>then only supported attributes will be messaged back to user

Hmm, isn't that redundat if we need to check those again below?

>
>>> +
>>> +	if (dpll->ops->get_status) {
>>> +		ret = dpll->ops->get_status(dpll);
>>> +		if (nla_put_u32(msg, DPLLA_STATUS, ret))
>>> +			return -EMSGSIZE;
>>> +	}
>>> +
>>> +	if (dpll->ops->get_temp) {
>>> +		ret = dpll->ops->get_status(dpll);
>>> +		if (nla_put_u32(msg, DPLLA_TEMP, ret))
>>> +			return -EMSGSIZE;
>>> +	}
>> 
>> shouldn't be get_temp(dpll)?
>
>good catch, copy-paste error
>
>>> +
>>> +	if (dpll->ops->get_lock_status) {
>>> +		ret = dpll->ops->get_lock_status(dpll);
>>> +		if (nla_put_u32(msg, DPLLA_LOCK_STATUS, ret))
>>> +			return -EMSGSIZE;
>>> +	}
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static int dpll_device_dump_one(struct dpll_device *dev, struct sk_buff *msg, int flags)
>>> +{
>>> +	struct nlattr *hdr;
>>> +	int ret;
>>> +
>>> +	hdr = nla_nest_start(msg, DPLLA_DEVICE);
>>> +	if (!hdr)
>>> +		return -EMSGSIZE;
>>> +
>>> +	mutex_lock(&dev->lock);
>>> +	ret = __dpll_cmd_device_dump_one(dev, msg);
>>> +	if (ret)
>>> +		goto out_cancel_nest;
>>> +
>>> +	if (flags & DPLL_FLAG_SOURCES && dev->ops->get_source_type) {
>>> +		ret = __dpll_cmd_dump_sources(dev, msg);
>>> +		if (ret)
>>> +			goto out_cancel_nest;
>>> +	}
>>> +
>>> +	if (flags & DPLL_FLAG_OUTPUTS && dev->ops->get_output_type) {
>>> +		ret = __dpll_cmd_dump_outputs(dev, msg);
>>> +		if (ret)
>>> +			goto out_cancel_nest;
>>> +	}
>>> +
>>> +	if (flags & DPLL_FLAG_STATUS) {
>>> +		ret = __dpll_cmd_dump_status(dev, msg);
>>> +		if (ret)
>>> +			goto out_cancel_nest;
>>> +	}
>>> +
>>> +	mutex_unlock(&dev->lock);
>>> +	nla_nest_end(msg, hdr);
>>> +
>>> +	return 0;
>>> +
>>> +out_cancel_nest:
>>> +	mutex_unlock(&dev->lock);
>>> +	nla_nest_cancel(msg, hdr);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int dpll_genl_cmd_set_source(struct param *p)
>>> +{
>>> +	const struct genl_dumpit_info *info = genl_dumpit_info(p->cb);
>>> +	struct dpll_device *dpll = p->dpll;
>>> +	int ret = 0, src_id, type;
>>> +
>>> +	if (!info->attrs[DPLLA_SOURCE_ID] ||
>>> +	    !info->attrs[DPLLA_SOURCE_TYPE])
>>> +		return -EINVAL;
>>> +
>>> +	if (!dpll->ops->set_source_type)
>>> +		return -EOPNOTSUPP;
>>> +
>>> +	src_id = nla_get_u32(info->attrs[DPLLA_SOURCE_ID]);
>>> +	type = nla_get_u32(info->attrs[DPLLA_SOURCE_TYPE]);
>>> +
>>> +	mutex_lock(&dpll->lock);
>>> +	ret = dpll->ops->set_source_type(dpll, src_id, type);
>>> +	mutex_unlock(&dpll->lock);

I wonder if shouldn't the dpll ptr be validated here, and in similar cases.
I mean, between calling dpll_pre_doit and actually doing something on a 'dpll',
it is possible that device gets removed?

Or maybe pre_doit/post_doit shall lock and unlock some other mutex?
Altough, I am not an expert in the netlink stuff, thus just raising a concern.

>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int dpll_genl_cmd_set_output(struct param *p)
>>> +{
>>> +	const struct genl_dumpit_info *info = genl_dumpit_info(p->cb);
>>> +	struct dpll_device *dpll = p->dpll;
>>> +	int ret = 0, out_id, type;
>>> +
>>> +	if (!info->attrs[DPLLA_OUTPUT_ID] ||
>>> +	    !info->attrs[DPLLA_OUTPUT_TYPE])
>>> +		return -EINVAL;
>>> +
>>> +	if (!dpll->ops->set_output_type)
>>> +		return -EOPNOTSUPP;
>>> +
>>> +	out_id = nla_get_u32(info->attrs[DPLLA_OUTPUT_ID]);
>>> +	type = nla_get_u32(info->attrs[DPLLA_OUTPUT_TYPE]);
>>> +
>>> +	mutex_lock(&dpll->lock);
>>> +	ret = dpll->ops->set_source_type(dpll, out_id, type);
>>> +	mutex_unlock(&dpll->lock);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int dpll_device_loop_cb(struct dpll_device *dpll, void *data)
>>> +{
>>> +	struct dpll_dump_ctx *ctx;
>>> +	struct param *p = (struct param *)data;
>>> +
>>> +	ctx = dpll_dump_context(p->cb);
>>> +
>>> +	ctx->pos_idx = dpll->id;
>>> +
>>> +	return dpll_device_dump_one(dpll, p->msg, ctx->flags);
>>> +}
>>> +
>>> +static int dpll_cmd_device_dump(struct param *p)
>>> +{
>>> +	struct dpll_dump_ctx *ctx = dpll_dump_context(p->cb);
>>> +
>>> +	return for_each_dpll_device(ctx->pos_idx, dpll_device_loop_cb, p);
>>> +}
>>> +
>>> +static int dpll_genl_cmd_device_get_id(struct param *p)
>>> +{
>>> +	struct dpll_device *dpll = p->dpll;
>>> +	int flags = 0;
>>> +
>>> +	if (p->attrs[DPLLA_FLAGS])
>>> +		flags = nla_get_u32(p->attrs[DPLLA_FLAGS]);
>>> +
>>> +	return dpll_device_dump_one(dpll, p->msg, flags);
>>> +}
>>> +
>>> +static cb_t cmd_doit_cb[] = {
>>> +	[DPLL_CMD_DEVICE_GET]		= dpll_genl_cmd_device_get_id,
>>> +	[DPLL_CMD_SET_SOURCE_TYPE]	= dpll_genl_cmd_set_source,
>>> +	[DPLL_CMD_SET_OUTPUT_TYPE]	= dpll_genl_cmd_set_output,
>>> +};
>>> +
>>> +static cb_t cmd_dump_cb[] = {
>>> +	[DPLL_CMD_DEVICE_GET]		= dpll_cmd_device_dump,
>>> +};
>>> +
>>> +static int dpll_genl_cmd_start(struct netlink_callback *cb)
>>> +{
>>> +	const struct genl_dumpit_info *info = genl_dumpit_info(cb);
>>> +	struct dpll_dump_ctx *ctx = dpll_dump_context(cb);
>>> +
>>> +	ctx->dev = NULL;
>>> +	if (info->attrs[DPLLA_FLAGS])
>>> +		ctx->flags = nla_get_u32(info->attrs[DPLLA_FLAGS]);
>>> +	else
>>> +		ctx->flags = 0;
>>> +	ctx->pos_idx = 0;
>>> +	ctx->pos_src_idx = 0;
>>> +	ctx->pos_out_idx = 0;
>>> +	return 0;
>>> +}
>>> +
>>> +static int dpll_genl_cmd_dumpit(struct sk_buff *skb,
>>> +				   struct netlink_callback *cb)
>>> +{
>>> +	struct param p = { .cb = cb, .msg = skb };
>>> +	const struct genl_dumpit_info *info = genl_dumpit_info(cb);
>>> +	int cmd = info->op.cmd;
>>> +	int ret;
>>> +	void *hdr;
>>> +
>>> +	hdr = genlmsg_put(skb, 0, 0, &dpll_gnl_family, 0, cmd);
>>> +	if (!hdr)
>>> +		return -EMSGSIZE;
>>> +
>>> +	ret = cmd_dump_cb[cmd](&p);
>>> +	if (ret)
>>> +		goto out_cancel_msg;
>>> +
>>> +	genlmsg_end(skb, hdr);
>>> +
>>> +	return 0;
>>> +
>>> +out_cancel_msg:
>>> +	genlmsg_cancel(skb, hdr);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int dpll_genl_cmd_doit(struct sk_buff *skb,
>>> +				 struct genl_info *info)
>>> +{
>>> +	struct param p = { .attrs = info->attrs, .dpll = info->user_ptr[0] };
>>> +	int cmd = info->genlhdr->cmd;
>>> +	struct sk_buff *msg;
>>> +	void *hdr;
>>> +	int ret;
>>> +
>>> +	msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL);
>>> +	if (!msg)
>>> +		return -ENOMEM;
>>> +	p.msg = msg;
>>> +
>>> +	hdr = genlmsg_put_reply(msg, info, &dpll_gnl_family, 0, cmd);
>>> +	if (!hdr) {
>>> +		ret = -EMSGSIZE;
>>> +		goto out_free_msg;
>>> +	}
>>> +
>>> +	ret = cmd_doit_cb[cmd](&p);
>>> +	if (ret)
>>> +		goto out_cancel_msg;
>>> +
>>> +	genlmsg_end(msg, hdr);
>>> +
>>> +	return genlmsg_reply(msg, info);
>>> +
>>> +out_cancel_msg:
>>> +	genlmsg_cancel(msg, hdr);
>>> +out_free_msg:
>>> +	nlmsg_free(msg);
>>> +
>>> +	return ret;
>>> +}
>>> +
>>> +static int dpll_pre_doit(const struct genl_ops *ops, struct sk_buff *skb,
>>> +						 struct genl_info *info)
>>> +{
>>> +	struct dpll_device *dpll;
>>> +	int id;
>>> +
>>> +	if (!info->attrs[DPLLA_DEVICE_ID])
>>> +		return -EINVAL;
>>> +	id = nla_get_u32(info->attrs[DPLLA_DEVICE_ID]);
>>> +
>>> +	dpll = dpll_device_get_by_id(id);
>>> +	if (!dpll)
>>> +		return -ENODEV;
>>> +	info->user_ptr[0] = dpll;
>>> +
>>> +	return 0;
>>> +}
>>> +
>>> +static const struct genl_ops dpll_genl_ops[] = {
>>> +	{
>>> +		.cmd	= DPLL_CMD_DEVICE_GET,
>>> +		.start	= dpll_genl_cmd_start,
>>> +		.dumpit	= dpll_genl_cmd_dumpit,
>>> +		.doit	= dpll_genl_cmd_doit,
>>> +		.policy	= dpll_genl_get_policy,
>>> +		.maxattr = ARRAY_SIZE(dpll_genl_get_policy) - 1,
>>> +	},
>>> +	{
>>> +		.cmd	= DPLL_CMD_SET_SOURCE_TYPE,
>>> +		.flags	= GENL_UNS_ADMIN_PERM,
>>> +		.doit	= dpll_genl_cmd_doit,
>>> +		.policy	= dpll_genl_set_source_policy,
>>> +		.maxattr = ARRAY_SIZE(dpll_genl_set_source_policy) - 1,
>>> +	},
>>> +	{
>>> +		.cmd	= DPLL_CMD_SET_OUTPUT_TYPE,
>>> +		.flags	= GENL_UNS_ADMIN_PERM,
>>> +		.doit	= dpll_genl_cmd_doit,
>>> +		.policy	= dpll_genl_set_output_policy,
>>> +		.maxattr = ARRAY_SIZE(dpll_genl_set_output_policy) - 1,
>>> +	},
>>> +};
>>> +
>>> +static struct genl_family dpll_gnl_family __ro_after_init = {
>>> +	.hdrsize	= 0,
>>> +	.name		= DPLL_FAMILY_NAME,
>>> +	.version	= DPLL_VERSION,
>>> +	.ops		= dpll_genl_ops,
>>> +	.n_ops		= ARRAY_SIZE(dpll_genl_ops),
>>> +	.mcgrps		= dpll_genl_mcgrps,
>>> +	.n_mcgrps	= ARRAY_SIZE(dpll_genl_mcgrps),
>>> +	.pre_doit	= dpll_pre_doit,
>>> +};
>>> +
>>> +int __init dpll_netlink_init(void)
>>> +{
>>> +	return genl_register_family(&dpll_gnl_family);
>>> +}
>>> +
>>> +void dpll_netlink_finish(void)
>>> +{
>>> +	genl_unregister_family(&dpll_gnl_family);
>>> +}
>>> +
>>> +void __exit dpll_netlink_fini(void)
>>> +{
>>> +	dpll_netlink_finish();
>>> +}
>>> diff --git a/drivers/dpll/dpll_netlink.h b/drivers/dpll/dpll_netlink.h
>>> new file mode 100644
>>> index 000000000000..e2d100f59dd6
>>> --- /dev/null
>>> +++ b/drivers/dpll/dpll_netlink.h
>>> @@ -0,0 +1,7 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + *  Copyright (c) 2021 Meta Platforms, Inc. and affiliates
>>> + */
>>> +
>>> +int __init dpll_netlink_init(void);
>>> +void dpll_netlink_finish(void);
>>> diff --git a/include/linux/dpll.h b/include/linux/dpll.h
>>> new file mode 100644
>>> index 000000000000..9051337bcf9e
>>> --- /dev/null
>>> +++ b/include/linux/dpll.h
>>> @@ -0,0 +1,25 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 */
>>> +/*
>>> + *  Copyright (c) 2021 Meta Platforms, Inc. and affiliates
>>> + */
>>> +
>>> +#ifndef __DPLL_H__
>>> +#define __DPLL_H__
>>> +
>>> +struct dpll_device;
>>> +
>>> +struct dpll_device_ops {
>>> +	int (*get_status)(struct dpll_device *dpll);
>>> +	int (*get_temp)(struct dpll_device *dpll);
>>> +	int (*get_lock_status)(struct dpll_device *dpll);
>>> +	int (*get_source_type)(struct dpll_device *dpll, int id);
>>> +	int (*get_output_type)(struct dpll_device *dpll, int id);
>>> +	int (*set_source_type)(struct dpll_device *dpll, int id, int val);
>>> +	int (*set_output_type)(struct dpll_device *dpll, int id, int val);
>>> +};
>>> +
>>> +struct dpll_device *dpll_device_alloc(struct dpll_device_ops *ops, int sources_count,
>>> +					 int outputs_count, void *priv);
>>> +void dpll_device_register(struct dpll_device *dpll);
>>> +void *dpll_priv(struct dpll_device *dpll);
>>> +#endif
>>> diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h
>>> new file mode 100644
>>> index 000000000000..8c00f52736ee
>>> --- /dev/null
>>> +++ b/include/uapi/linux/dpll.h
>>> @@ -0,0 +1,77 @@
>>> +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
>>> +#ifndef _UAPI_LINUX_DPLL_H
>>> +#define _UAPI_LINUX_DPLL_H
>>> +
>>> +#define DPLL_NAME_LENGTH	20
>>> +
>>> +/* Adding event notification support elements */
>>> +#define DPLL_FAMILY_NAME		"dpll"
>>> +#define DPLL_VERSION			0x01
>>> +#define DPLL_CONFIG_DEVICE_GROUP_NAME  "config"
>>> +#define DPLL_CONFIG_SOURCE_GROUP_NAME  "source"
>>> +#define DPLL_CONFIG_OUTPUT_GROUP_NAME  "output"
>>> +#define DPLL_MONITOR_GROUP_NAME        "monitor"
>>> +
>>> +#define DPLL_FLAG_SOURCES	1
>>> +#define DPLL_FLAG_OUTPUTS	2
>>> +#define DPLL_FLAG_STATUS	4
>>> +
>>> +/* Attributes of dpll_genl_family */
>>> +enum dpll_genl_get_attr {
>>> +	DPLLA_UNSPEC,
>>> +	DPLLA_DEVICE,
>>> +	DPLLA_DEVICE_ID,
>>> +	DPLLA_DEVICE_NAME,
>>> +	DPLLA_SOURCE,
>>> +	DPLLA_SOURCE_ID,
>>> +	DPLLA_SOURCE_TYPE,
>>> +	DPLLA_OUTPUT,
>>> +	DPLLA_OUTPUT_ID,
>>> +	DPLLA_OUTPUT_TYPE,
>>> +	DPLLA_STATUS,
>>> +	DPLLA_TEMP,
>>> +	DPLLA_LOCK_STATUS,
>>> +	DPLLA_FLAGS,
>>> +
>>> +	__DPLLA_MAX,
>>> +};
>>> +#define DPLLA_GET_MAX (__DPLLA_MAX - 1)
>> 
>> I think "_get_/_GET_" in above names is outdated?
>> 
>
>Yes, you are right. The earliest revision had these "GET/SET" in attributes but 
>later we decided to unite them into common attributes. I will remove these 
>artifacts in the next revision.
>
>>> +
>>> +/* DPLL signal types used as source or as output */
>>> +enum dpll_genl_signal_type {
>>> +	DPLL_TYPE_EXT_1PPS,
>>> +	DPLL_TYPE_EXT_10MHZ,
>>> +	DPLL_TYPE_SYNCE_ETH_PORT,
>>> +	DPLL_TYPE_INT_OSCILLATOR,
>>> +	DPLL_TYPE_GNSS,
>>> +
>>> +	__DPLL_TYPE_MAX,
>>> +};
>>> +#define DPLL_TYPE_MAX (__DPLL_TYPE_MAX - 1)
>>> +
>>> +/* Events of dpll_genl_family */
>>> +enum dpll_genl_event {
>>> +	DPLL_EVENT_UNSPEC,
>>> +	DPLL_EVENT_DEVICE_CREATE,		/* DPLL device creation */
>>> +	DPLL_EVENT_DEVICE_DELETE,		/* DPLL device deletion */
>>> +	DPLL_EVENT_STATUS_LOCKED,		/* DPLL device locked to source */
>>> +	DPLL_EVENT_STATUS_UNLOCKED,	/* DPLL device freerun */
>>> +	DPLL_EVENT_SOURCE_CHANGE,		/* DPLL device source changed */
>>> +	DPLL_EVENT_OUTPUT_CHANGE,		/* DPLL device output changed */
>>> +
>>> +	__DPLL_EVENT_MAX,
>>> +};
>>> +#define DPLL_EVENT_MAX (__DPLL_EVENT_MAX - 1)
>>> +
>>> +/* Commands supported by the dpll_genl_family */
>>> +enum dpll_genl_cmd {
>>> +	DPLL_CMD_UNSPEC,
>>> +	DPLL_CMD_DEVICE_GET,	/* List of DPLL devices id */
>>> +	DPLL_CMD_SET_SOURCE_TYPE,	/* Set the DPLL device source type */
>>> +	DPLL_CMD_SET_OUTPUT_TYPE,	/* Get the DPLL device output type */
>> 
>> "Get" in comment description looks like a typo.
>> I am getting bit confused with the name and comments.
>> For me, first look says: it is selection of a type of a source.
>> But in the code I can see it selects a source id and a type.
>> Type of source originates in HW design, why would the one want to "set" it?
>> I can imagine a HW design where a single source or output would allow to choose
>> where the signal originates/goes, some kind of extra selector layer for a
>> source/output, but was that the intention?
>
>In general - yes, we have experimented with our time card providing different 
>types of source synchronisation signal on different input pins, i.e. 1PPS, 
>10MHz, IRIG-B, etc. Any of these signals could be connected to any of 4 external 
>pins, that's why I source id is treated as input pin identifier and source type 
>is the signal type it receives.
>
>> If so, shouldn't the user get some bitmap/list of modes available for each
>> source/output?
>
>Good idea. We have list of available modes exposed via sysfs file, and I agree 
>that it's worth to expose them via netlink interface. I will try to address this 
>in the next version.
>
>> 
>> The user shall get some extra information about the source/output. Right now
>> there can be multiple sources/outputs of the same type, but not really possible
>> to find out their purpose. I.e. a dpll equipped with four source of
>> DPLL_TYPE_EXT_1PPS type.
>>  > This implementation looks like designed for a "forced reference lock" mode
>> where the user must explicitly select one source. But a multi source/output
>> DPLL could be running in different modes. I believe most important is automatic
>> mode, where it tries to lock to a user-configured source priority list.
>> However, there is also freerun mode, where dpll isn't even trying to lock to
>> anything, or NCO - Numerically Controlled Oscillator mode.
>
>Yes, you are right, my focus was on "forced reference lock" mode as currently 
>this is the only mode supported by our hardware. But I'm happy to extend it to 
>other usecases.
>
>> It would be great to have ability to select DPLL modes, but also to be able to
>> configure priorities, read failure status, configure extra "features" (i.e.
>> Embedded Sync, EEC modes, Fast Lock)
>I absolutely agree on this way of improvement, and I already have some ongoing 
>work about failures/events/status change messages. I can see no stoppers for 
>creating priorities (if supported by HW) and other extra "features", but we have 
>to agree on the interface with flexibility in mind.

Great and makes perfect sense!

>
>> The sources and outputs can also have some extra features or capabilities, like:
>> - enable Embedded Sync
>
>Does it mean we want to enable or disable Embedded Sync within one protocol? Is 
>it like Time-Sensitive Networking (TSN) for Ethernet?

Well, from what I know, Embedded PPS (ePPS), Embedded Pulse Per 2 Seconds
(ePP2S) and Embedded Sync (eSync) can be either 25/75 or 75/25, which describes
a ratio of how the 'embedded pulse' is divided into HIGH and LOW states on a
pulse of higher frequency signal in which EPPS/EPP2S/ESync is embedded.

EPPS and EPP2S are rather straightforward, once an EPPS enabled input is
selected as a source, then output configured as PPS(PP2S) shall tick in the
same periods as signal "embedded" in input.
Embedded Sync (eSync) is similar but it allows for configuration of frequency
of a 'sync' signal, i.e. source is 10MHz with eSync configured as 100 HZ, where
the output configured for 100HZ could use it.

I cannot say how exactly Embedded Sync/PPS will be used, as from my perspective
this is user specific, and I am not very familiar with any standard describing
its usage.
I am working on SyncE, where either Embedded Sync or PPS is not a part of SyncE
standard, but I strongly believe that users would need to run a DPLL with
Embedded Sync/PPS enabled for some other things. And probably would love to
have SW control over the dpll.

Lets assume following simplified example:
input1 +-------------+ output1 
-------|             |---------
       |  DPLL 1     |         
input2 |             | output2 
-------|             |---------
       +-------------+         
where:
- input1 is external 10MHz with 25/75 Embedded PPS enabled,
- input2 is a fallback PPS from GNSS
user expects:
- output1 as a 10MHz with embedded sync
- output2 as a PPS
As long as input1 is selected source, output1 is synchonized with it and
output2 ticks are synchronized with ePPS.
Now the user selects input2 as a source, where outputs are unchanged,
both output2 and output1-ePPS are synchronized with input2, and output1
10MHz must be generated by DPLL.

I am trying to show example of what DPLL user might want to configure, this
would be a separated configuration of ePPS/ePP2S/eSync per source as well as
per output.
Also a DPLL itself can have explicitly disabled embedded signal processing on
its sources.

>
>> - add phase delay
>> - configure frequency (user might need to use source/output with different
>>    frequency then 1 PPS or 10MHz)
>
>Providing these modes I was thinking about the most common and widely used 
>signals in measurement equipment. So my point here is that both 1PPS and 10MHz 
>should stay as is, but another type of signal should be added, let's say 
>CUSTOM_FREQ, which will also consider special attribute in netlink terms. is it ok?

Sure, makes sense.

>
>> Generally, for simple DPLL designs this interface could do the job (although,
>> I still think user needs more information about the sources/outputs), but for
>> more complex ones, there should be something different, which takes care of my
>> comments regarding extra configuration needed.
>> 
>
>As I already mentioned earlier, I'm open for improvements and happy to 
>collaborate to cover other use cases of this subsystem from the very beginning 
>of development. We can even create an open github repo to share implementation 
>details together with comments if it works better for you.
>

Sure, great! I am happy to help.
I could start working on a part for extra DPLL modes and source-priorities as
automatic mode doesn't make sense without them.

Thank you,
Arkadiusz  

>> Thanks,
>> Arkadiusz
>> 
>>> +
>>> +	__DPLL_CMD_MAX,
>>> +};
>>> +#define DPLL_CMD_MAX (__DPLL_CMD_MAX - 1)
>>> +
>>> +#endif /* _UAPI_LINUX_DPLL_H */
>>> -- 
>>> 2.27.0
>>>
>>>
>
>


More information about the linux-arm-kernel mailing list