// SPDX-License-Identifier: GPL-2.0
/*
* Generic netlink for DPLL management framework
*
* Copyright (c) 2023 Meta Platforms, Inc. and affiliates
* Copyright (c) 2023 Intel and affiliates
*
*/
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/netdevice.h>
#include <net/genetlink.h>
#include "dpll_core.h"
#include "dpll_netlink.h"
#include "dpll_nl.h"
#include <uapi/linux/dpll.h>
#define ASSERT_NOT_NULL(ptr) (WARN_ON(!ptr))
#define xa_for_each_marked_start(xa, index, entry, filter, start) \
for (index = start, entry = xa_find(xa, &index, ULONG_MAX, filter); \
entry; entry = xa_find_after(xa, &index, ULONG_MAX, filter))
struct dpll_dump_ctx {
unsigned long idx;
};
static struct dpll_dump_ctx *dpll_dump_context(struct netlink_callback *cb)
{
return (struct dpll_dump_ctx *)cb->ctx;
}
static int
dpll_msg_add_dev_handle(struct sk_buff *msg, struct dpll_device *dpll)
{
if (nla_put_u32(msg, DPLL_A_ID, dpll->id))
return -EMSGSIZE;
return 0;
}
static int
dpll_msg_add_dev_parent_handle(struct sk_buff *msg, u32 id)
{
if (nla_put_u32(msg, DPLL_A_PIN_PARENT_ID, id))
return -EMSGSIZE;
return 0;
}
/**
* dpll_msg_add_pin_handle - attach pin handle attribute to a given message
* @msg: pointer to sk_buff message to attach a pin handle
* @pin: pin pointer
*
* Return:
* * 0 - success
* * -EMSGSIZE - no space in message to attach pin handle
*/
static int dpll_msg_add_pin_handle(struct sk_buff *msg, struct dpll_pin *pin)
{
if (!pin)
return 0;
if (nla_put_u32(msg, DPLL_A_PIN_ID, pin->id))
return -EMSGSIZE;
return 0;
}
static struct dpll_pin *dpll_netdev_pin(const struct net_device *dev)
{
return rcu_dereference_rtnl(dev->dpll_pin);
}
/**
* dpll_netdev_pin_handle_size - get size of pin handle attribute of a netdev
* @dev: netdev from which to get the pin
*
* Return: byte size of pin handle attribute, or 0 if @dev has no pin.
*/
size_t dpll_netdev_pin_handle_size(const struct net_device *dev)
{
return dpll_netdev_pin(dev) ? nla_total_size(4) : 0; /* DPLL_A_PIN_ID */
}
int dpll_netdev_add_pin_handle(struct sk_buff *msg,
const struct net_device *dev)
{
return dpll_msg_add_pin_handle(msg, dpll_netdev_pin(dev));
}
static int
dpll_msg_add_mode(struct sk_buff *msg, struct dpll_device *dpll,
struct netlink_ext_ack *extack)
{
const struct dpll_device_ops *ops = dpll_device_ops(dpll);
enum dpll_mode mode;
int ret;
ret = ops->mode_get(dpll, dpll_priv(dpll), &mode, extack);
if (ret)
return ret;
if (nla_put_u32(msg, DPLL_A_MODE, mode))
return -EMSGSIZE;
return 0;
}
static int
dpll_msg_add_mode_supported(struct sk_buff *msg, struct dpll_device *dpll,
struct netlink_ext_ack *extack)
{
const struct dpll_device_ops *ops = dpll_device_ops(dpll);
enum dpll_mode mode;
int ret;
/* No mode change is supported now, so the only supported mode is the
* one obtained by mode_get().
*/
ret = ops->mode_get(dpll, dpll_priv(dpll), &mode, extack);
if (ret)
return ret;
if (nla_put_u32(msg, DPLL_A_MODE_SUPPORTED, mode))
return -EMSGSIZE;
return 0;
}
static int
dpll_msg_add_lock_status(struct sk_buff *msg, struct dpll_device *dpll,
struct netlink_ext_ack *extack)
{
const struct dpll_device_ops *ops = dpll_device_ops(dpll);
enum dpll_lock_status_error status_error = 0;
enum dpll_lock_status status;
int ret;
ret = ops->lock_status_get(dpll, dpll_priv(dpll), &status,
&status_error, extack);
if (ret)
return ret;
if (nla_put_u32(msg, DPLL_A_LOCK_STATUS, status))
return -EMSGSIZE;
if (status_error &&
(status == DPLL_LOCK_STATUS_UNLOCKED ||
status == DPLL_LOCK_STATUS_HOLDOVER) &&
nla_put_u32(msg, DPLL_A_LOCK_STATUS_ERROR, status_error))
return -EMSGSIZE;
return 0;
}
static int
dpll_msg_add_temp(struct sk_buff *msg, struct dpll_device *dpll,
struct netlink_ext_ack *extack)
{
const struct dpll_device_ops *ops = dpll_device_ops(dpll);
s32 temp;
int ret;
if (!ops->temp_get)
return 0;
ret = ops->temp_get(dpll, dpll_priv(dpll), &temp, extack);
if (ret)
return ret;
if (nla_put_s32(msg, DPLL_A_TEMP, temp))
return -EMSGSIZE;
return 0;
}
static int
dpll_msg_add_pin_prio(struct sk_buff *msg, struct dpll_pin *pin,
struct dpll_pin_ref *ref,
struct netlink_ext_ack *extack)
{
const struct dpll_pin_ops *ops = dpll_pin_ops(ref);
struct dpll_device *dpll = ref->dpll;
u32 prio;
int ret;
if
|