2023-09-13 21:49:38 +01:00
// 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>
2024-03-04 17:35:32 -08:00
# include <linux/netdevice.h>
2023-09-13 21:49:38 +01:00
# 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
*/
2024-03-04 17:35:32 -08:00
static int dpll_msg_add_pin_handle ( struct sk_buff * msg , struct dpll_pin * pin )
2023-09-13 21:49:38 +01:00
{
if ( ! pin )
return 0 ;
if ( nla_put_u32 ( msg , DPLL_A_PIN_ID , pin - > id ) )
return - EMSGSIZE ;
return 0 ;
}
2024-03-04 17:35:32 -08:00
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 ) ) ;
}
2023-09-13 21:49:38 +01:00
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 ;
2023-12-07 16:12:04 +01:00
int ret ;
2023-09-13 21:49:38 +01:00
2023-12-07 16:12:04 +01:00
/* 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 ;
2023-09-13 21:49:38 +01:00
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 ) ;
2024-01-30 13:08:30 +01:00
enum dpll_lock_status_error status_error = 0 ;
2023-09-13 21:49:38 +01:00
enum dpll_lock_status status ;
int ret ;
2024-01-30 13:08:30 +01:00
ret = ops - > lock_status_get ( dpll , dpll_priv ( dpll ) , & status ,
& status_error , extack ) ;
2023-09-13 21:49:38 +01:00
if ( ret )
return ret ;
if ( nla_put_u32 ( msg , DPLL_A_LOCK_STATUS , status ) )
return - EMSGSIZE ;
2024-01-30 13:08:30 +01:00
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 ;
2023-09-13 21:49:38 +01:00
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 ( ! ops - > prio_get )
return 0 ;
ret = ops - > prio_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) , dpll ,
dpll_priv ( dpll ) , & prio , extack ) ;
if ( ret )
return ret ;
if ( nla_put_u32 ( msg , DPLL_A_PIN_PRIO , prio ) )
return - EMSGSIZE ;
return 0 ;
}
static int
dpll_msg_add_pin_on_dpll_state ( 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 ;
enum dpll_pin_state state ;
int ret ;
if ( ! ops - > state_on_dpll_get )
return 0 ;
ret = ops - > state_on_dpll_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , & state , extack ) ;
if ( ret )
return ret ;
if ( nla_put_u32 ( msg , DPLL_A_PIN_STATE , state ) )
return - EMSGSIZE ;
return 0 ;
}
static int
dpll_msg_add_pin_direction ( 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 ;
enum dpll_pin_direction direction ;
int ret ;
ret = ops - > direction_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) , dpll ,
dpll_priv ( dpll ) , & direction , extack ) ;
if ( ret )
return ret ;
if ( nla_put_u32 ( msg , DPLL_A_PIN_DIRECTION , direction ) )
return - EMSGSIZE ;
return 0 ;
}
2023-10-11 12:12:34 +02:00
static int
dpll_msg_add_pin_phase_adjust ( 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 ;
s32 phase_adjust ;
int ret ;
if ( ! ops - > phase_adjust_get )
return 0 ;
ret = ops - > phase_adjust_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) ,
& phase_adjust , extack ) ;
if ( ret )
return ret ;
if ( nla_put_s32 ( msg , DPLL_A_PIN_PHASE_ADJUST , phase_adjust ) )
return - EMSGSIZE ;
return 0 ;
}
static int
dpll_msg_add_phase_offset ( 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 ;
s64 phase_offset ;
int ret ;
if ( ! ops - > phase_offset_get )
return 0 ;
ret = ops - > phase_offset_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , & phase_offset ,
extack ) ;
if ( ret )
return ret ;
if ( nla_put_64bit ( msg , DPLL_A_PIN_PHASE_OFFSET , sizeof ( phase_offset ) ,
& phase_offset , DPLL_A_PIN_PAD ) )
return - EMSGSIZE ;
return 0 ;
}
2024-01-03 14:28:36 +01:00
static int dpll_msg_add_ffo ( 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 ;
s64 ffo ;
int ret ;
if ( ! ops - > ffo_get )
return 0 ;
ret = ops - > ffo_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , & ffo , extack ) ;
if ( ret ) {
if ( ret = = - ENODATA )
return 0 ;
return ret ;
}
return nla_put_sint ( msg , DPLL_A_PIN_FRACTIONAL_FREQUENCY_OFFSET , ffo ) ;
}
2023-09-13 21:49:38 +01:00
static int
dpll_msg_add_pin_freq ( 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 ;
struct nlattr * nest ;
int fs , ret ;
u64 freq ;
if ( ! ops - > frequency_get )
return 0 ;
ret = ops - > frequency_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) , dpll ,
dpll_priv ( dpll ) , & freq , extack ) ;
if ( ret )
return ret ;
if ( nla_put_64bit ( msg , DPLL_A_PIN_FREQUENCY , sizeof ( freq ) , & freq ,
DPLL_A_PIN_PAD ) )
return - EMSGSIZE ;
2024-01-19 14:43:02 +01:00
for ( fs = 0 ; fs < pin - > prop . freq_supported_num ; fs + + ) {
2023-09-13 21:49:38 +01:00
nest = nla_nest_start ( msg , DPLL_A_PIN_FREQUENCY_SUPPORTED ) ;
if ( ! nest )
return - EMSGSIZE ;
2024-01-19 14:43:02 +01:00
freq = pin - > prop . freq_supported [ fs ] . min ;
2023-09-13 21:49:38 +01:00
if ( nla_put_64bit ( msg , DPLL_A_PIN_FREQUENCY_MIN , sizeof ( freq ) ,
& freq , DPLL_A_PIN_PAD ) ) {
nla_nest_cancel ( msg , nest ) ;
return - EMSGSIZE ;
}
2024-01-19 14:43:02 +01:00
freq = pin - > prop . freq_supported [ fs ] . max ;
2023-09-13 21:49:38 +01:00
if ( nla_put_64bit ( msg , DPLL_A_PIN_FREQUENCY_MAX , sizeof ( freq ) ,
& freq , DPLL_A_PIN_PAD ) ) {
nla_nest_cancel ( msg , nest ) ;
return - EMSGSIZE ;
}
nla_nest_end ( msg , nest ) ;
}
return 0 ;
}
static bool dpll_pin_is_freq_supported ( struct dpll_pin * pin , u32 freq )
{
int fs ;
2024-01-19 14:43:02 +01:00
for ( fs = 0 ; fs < pin - > prop . freq_supported_num ; fs + + )
if ( freq > = pin - > prop . freq_supported [ fs ] . min & &
freq < = pin - > prop . freq_supported [ fs ] . max )
2023-09-13 21:49:38 +01:00
return true ;
return false ;
}
static int
dpll_msg_add_pin_parents ( struct sk_buff * msg , struct dpll_pin * pin ,
struct dpll_pin_ref * dpll_ref ,
struct netlink_ext_ack * extack )
{
enum dpll_pin_state state ;
struct dpll_pin_ref * ref ;
struct dpll_pin * ppin ;
struct nlattr * nest ;
unsigned long index ;
int ret ;
xa_for_each ( & pin - > parent_refs , index , ref ) {
const struct dpll_pin_ops * ops = dpll_pin_ops ( ref ) ;
void * parent_priv ;
ppin = ref - > pin ;
parent_priv = dpll_pin_on_dpll_priv ( dpll_ref - > dpll , ppin ) ;
ret = ops - > state_on_pin_get ( pin ,
dpll_pin_on_pin_priv ( ppin , pin ) ,
ppin , parent_priv , & state , extack ) ;
if ( ret )
return ret ;
nest = nla_nest_start ( msg , DPLL_A_PIN_PARENT_PIN ) ;
if ( ! nest )
return - EMSGSIZE ;
ret = dpll_msg_add_dev_parent_handle ( msg , ppin - > id ) ;
if ( ret )
goto nest_cancel ;
if ( nla_put_u32 ( msg , DPLL_A_PIN_STATE , state ) ) {
ret = - EMSGSIZE ;
goto nest_cancel ;
}
nla_nest_end ( msg , nest ) ;
}
return 0 ;
nest_cancel :
nla_nest_cancel ( msg , nest ) ;
return ret ;
}
static int
dpll_msg_add_pin_dplls ( struct sk_buff * msg , struct dpll_pin * pin ,
struct netlink_ext_ack * extack )
{
struct dpll_pin_ref * ref ;
struct nlattr * attr ;
unsigned long index ;
int ret ;
xa_for_each ( & pin - > dpll_refs , index , ref ) {
attr = nla_nest_start ( msg , DPLL_A_PIN_PARENT_DEVICE ) ;
if ( ! attr )
return - EMSGSIZE ;
ret = dpll_msg_add_dev_parent_handle ( msg , ref - > dpll - > id ) ;
if ( ret )
goto nest_cancel ;
ret = dpll_msg_add_pin_on_dpll_state ( msg , pin , ref , extack ) ;
if ( ret )
goto nest_cancel ;
ret = dpll_msg_add_pin_prio ( msg , pin , ref , extack ) ;
if ( ret )
goto nest_cancel ;
ret = dpll_msg_add_pin_direction ( msg , pin , ref , extack ) ;
2023-10-11 12:12:34 +02:00
if ( ret )
goto nest_cancel ;
ret = dpll_msg_add_phase_offset ( msg , pin , ref , extack ) ;
2023-09-13 21:49:38 +01:00
if ( ret )
goto nest_cancel ;
nla_nest_end ( msg , attr ) ;
}
return 0 ;
nest_cancel :
nla_nest_end ( msg , attr ) ;
return ret ;
}
static int
dpll_cmd_pin_get_one ( struct sk_buff * msg , struct dpll_pin * pin ,
struct netlink_ext_ack * extack )
{
2024-01-19 14:43:02 +01:00
const struct dpll_pin_properties * prop = & pin - > prop ;
2023-09-13 21:49:38 +01:00
struct dpll_pin_ref * ref ;
int ret ;
ref = dpll_xa_ref_dpll_first ( & pin - > dpll_refs ) ;
ASSERT_NOT_NULL ( ref ) ;
ret = dpll_msg_add_pin_handle ( msg , pin ) ;
if ( ret )
return ret ;
if ( nla_put_string ( msg , DPLL_A_PIN_MODULE_NAME ,
module_name ( pin - > module ) ) )
return - EMSGSIZE ;
if ( nla_put_64bit ( msg , DPLL_A_PIN_CLOCK_ID , sizeof ( pin - > clock_id ) ,
& pin - > clock_id , DPLL_A_PIN_PAD ) )
return - EMSGSIZE ;
if ( prop - > board_label & &
nla_put_string ( msg , DPLL_A_PIN_BOARD_LABEL , prop - > board_label ) )
return - EMSGSIZE ;
if ( prop - > panel_label & &
nla_put_string ( msg , DPLL_A_PIN_PANEL_LABEL , prop - > panel_label ) )
return - EMSGSIZE ;
if ( prop - > package_label & &
nla_put_string ( msg , DPLL_A_PIN_PACKAGE_LABEL ,
prop - > package_label ) )
return - EMSGSIZE ;
if ( nla_put_u32 ( msg , DPLL_A_PIN_TYPE , prop - > type ) )
return - EMSGSIZE ;
if ( nla_put_u32 ( msg , DPLL_A_PIN_CAPABILITIES , prop - > capabilities ) )
return - EMSGSIZE ;
ret = dpll_msg_add_pin_freq ( msg , pin , ref , extack ) ;
2023-10-11 12:12:34 +02:00
if ( ret )
return ret ;
if ( nla_put_s32 ( msg , DPLL_A_PIN_PHASE_ADJUST_MIN ,
prop - > phase_range . min ) )
return - EMSGSIZE ;
if ( nla_put_s32 ( msg , DPLL_A_PIN_PHASE_ADJUST_MAX ,
prop - > phase_range . max ) )
return - EMSGSIZE ;
ret = dpll_msg_add_pin_phase_adjust ( msg , pin , ref , extack ) ;
2024-01-03 14:28:36 +01:00
if ( ret )
return ret ;
ret = dpll_msg_add_ffo ( msg , pin , ref , extack ) ;
2023-09-13 21:49:38 +01:00
if ( ret )
return ret ;
if ( xa_empty ( & pin - > parent_refs ) )
ret = dpll_msg_add_pin_dplls ( msg , pin , extack ) ;
else
ret = dpll_msg_add_pin_parents ( msg , pin , ref , extack ) ;
return ret ;
}
static int
dpll_device_get_one ( struct dpll_device * dpll , struct sk_buff * msg ,
struct netlink_ext_ack * extack )
{
int ret ;
ret = dpll_msg_add_dev_handle ( msg , dpll ) ;
if ( ret )
return ret ;
if ( nla_put_string ( msg , DPLL_A_MODULE_NAME , module_name ( dpll - > module ) ) )
return - EMSGSIZE ;
if ( nla_put_64bit ( msg , DPLL_A_CLOCK_ID , sizeof ( dpll - > clock_id ) ,
& dpll - > clock_id , DPLL_A_PAD ) )
return - EMSGSIZE ;
ret = dpll_msg_add_temp ( msg , dpll , extack ) ;
if ( ret )
return ret ;
ret = dpll_msg_add_lock_status ( msg , dpll , extack ) ;
if ( ret )
return ret ;
ret = dpll_msg_add_mode ( msg , dpll , extack ) ;
if ( ret )
return ret ;
ret = dpll_msg_add_mode_supported ( msg , dpll , extack ) ;
if ( ret )
return ret ;
if ( nla_put_u32 ( msg , DPLL_A_TYPE , dpll - > type ) )
return - EMSGSIZE ;
2023-10-11 12:12:34 +02:00
return 0 ;
2023-09-13 21:49:38 +01:00
}
static int
dpll_device_event_send ( enum dpll_cmd event , struct dpll_device * dpll )
{
struct sk_buff * msg ;
int ret = - ENOMEM ;
void * hdr ;
if ( WARN_ON ( ! xa_get_mark ( & dpll_device_xa , dpll - > id , DPLL_REGISTERED ) ) )
return - ENODEV ;
msg = genlmsg_new ( NLMSG_GOODSIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
hdr = genlmsg_put ( msg , 0 , 0 , & dpll_nl_family , 0 , event ) ;
if ( ! hdr )
goto err_free_msg ;
ret = dpll_device_get_one ( dpll , msg , NULL ) ;
if ( ret )
goto err_cancel_msg ;
genlmsg_end ( msg , hdr ) ;
genlmsg_multicast ( & dpll_nl_family , msg , 0 , 0 , GFP_KERNEL ) ;
return 0 ;
err_cancel_msg :
genlmsg_cancel ( msg , hdr ) ;
err_free_msg :
nlmsg_free ( msg ) ;
return ret ;
}
int dpll_device_create_ntf ( struct dpll_device * dpll )
{
return dpll_device_event_send ( DPLL_CMD_DEVICE_CREATE_NTF , dpll ) ;
}
int dpll_device_delete_ntf ( struct dpll_device * dpll )
{
return dpll_device_event_send ( DPLL_CMD_DEVICE_DELETE_NTF , dpll ) ;
}
static int
__dpll_device_change_ntf ( struct dpll_device * dpll )
{
return dpll_device_event_send ( DPLL_CMD_DEVICE_CHANGE_NTF , dpll ) ;
}
2024-01-19 14:43:03 +01:00
static bool dpll_pin_available ( struct dpll_pin * pin )
{
struct dpll_pin_ref * par_ref ;
unsigned long i ;
if ( ! xa_get_mark ( & dpll_pin_xa , pin - > id , DPLL_REGISTERED ) )
return false ;
xa_for_each ( & pin - > parent_refs , i , par_ref )
if ( xa_get_mark ( & dpll_pin_xa , par_ref - > pin - > id ,
DPLL_REGISTERED ) )
return true ;
xa_for_each ( & pin - > dpll_refs , i , par_ref )
if ( xa_get_mark ( & dpll_device_xa , par_ref - > dpll - > id ,
DPLL_REGISTERED ) )
return true ;
return false ;
}
2023-09-13 21:49:38 +01:00
/**
* dpll_device_change_ntf - notify that the dpll device has been changed
* @ dpll : registered dpll pointer
*
* Context : acquires and holds a dpll_lock .
* Return : 0 if succeeds , error code otherwise .
*/
int dpll_device_change_ntf ( struct dpll_device * dpll )
{
int ret ;
mutex_lock ( & dpll_lock ) ;
ret = __dpll_device_change_ntf ( dpll ) ;
mutex_unlock ( & dpll_lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( dpll_device_change_ntf ) ;
static int
dpll_pin_event_send ( enum dpll_cmd event , struct dpll_pin * pin )
{
struct sk_buff * msg ;
int ret = - ENOMEM ;
void * hdr ;
2024-01-19 14:43:03 +01:00
if ( ! dpll_pin_available ( pin ) )
2023-09-13 21:49:38 +01:00
return - ENODEV ;
msg = genlmsg_new ( NLMSG_GOODSIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
hdr = genlmsg_put ( msg , 0 , 0 , & dpll_nl_family , 0 , event ) ;
if ( ! hdr )
goto err_free_msg ;
ret = dpll_cmd_pin_get_one ( msg , pin , NULL ) ;
if ( ret )
goto err_cancel_msg ;
genlmsg_end ( msg , hdr ) ;
genlmsg_multicast ( & dpll_nl_family , msg , 0 , 0 , GFP_KERNEL ) ;
return 0 ;
err_cancel_msg :
genlmsg_cancel ( msg , hdr ) ;
err_free_msg :
nlmsg_free ( msg ) ;
return ret ;
}
int dpll_pin_create_ntf ( struct dpll_pin * pin )
{
return dpll_pin_event_send ( DPLL_CMD_PIN_CREATE_NTF , pin ) ;
}
int dpll_pin_delete_ntf ( struct dpll_pin * pin )
{
return dpll_pin_event_send ( DPLL_CMD_PIN_DELETE_NTF , pin ) ;
}
static int __dpll_pin_change_ntf ( struct dpll_pin * pin )
{
return dpll_pin_event_send ( DPLL_CMD_PIN_CHANGE_NTF , pin ) ;
}
/**
* dpll_pin_change_ntf - notify that the pin has been changed
* @ pin : registered pin pointer
*
* Context : acquires and holds a dpll_lock .
* Return : 0 if succeeds , error code otherwise .
*/
int dpll_pin_change_ntf ( struct dpll_pin * pin )
{
int ret ;
mutex_lock ( & dpll_lock ) ;
ret = __dpll_pin_change_ntf ( pin ) ;
mutex_unlock ( & dpll_lock ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( dpll_pin_change_ntf ) ;
static int
dpll_pin_freq_set ( struct dpll_pin * pin , struct nlattr * a ,
struct netlink_ext_ack * extack )
{
2023-10-11 12:12:36 +02:00
u64 freq = nla_get_u64 ( a ) , old_freq ;
struct dpll_pin_ref * ref , * failed ;
const struct dpll_pin_ops * ops ;
struct dpll_device * dpll ;
2023-09-13 21:49:38 +01:00
unsigned long i ;
int ret ;
if ( ! dpll_pin_is_freq_supported ( pin , freq ) ) {
NL_SET_ERR_MSG_ATTR ( extack , a , " frequency is not supported by the device " ) ;
return - EINVAL ;
}
xa_for_each ( & pin - > dpll_refs , i , ref ) {
2023-10-11 12:12:36 +02:00
ops = dpll_pin_ops ( ref ) ;
if ( ! ops - > frequency_set | | ! ops - > frequency_get ) {
NL_SET_ERR_MSG ( extack , " frequency set not supported by the device " ) ;
2023-09-13 21:49:38 +01:00
return - EOPNOTSUPP ;
2023-10-11 12:12:36 +02:00
}
}
ref = dpll_xa_ref_dpll_first ( & pin - > dpll_refs ) ;
ops = dpll_pin_ops ( ref ) ;
dpll = ref - > dpll ;
ret = ops - > frequency_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) , dpll ,
dpll_priv ( dpll ) , & old_freq , extack ) ;
if ( ret ) {
NL_SET_ERR_MSG ( extack , " unable to get old frequency value " ) ;
return ret ;
}
if ( freq = = old_freq )
return 0 ;
xa_for_each ( & pin - > dpll_refs , i , ref ) {
ops = dpll_pin_ops ( ref ) ;
dpll = ref - > dpll ;
2023-09-13 21:49:38 +01:00
ret = ops - > frequency_set ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , freq , extack ) ;
2023-10-11 12:12:36 +02:00
if ( ret ) {
failed = ref ;
NL_SET_ERR_MSG_FMT ( extack , " frequency set failed for dpll_id:%u " ,
dpll - > id ) ;
goto rollback ;
}
2023-09-13 21:49:38 +01:00
}
__dpll_pin_change_ntf ( pin ) ;
return 0 ;
2023-10-11 12:12:36 +02:00
rollback :
xa_for_each ( & pin - > dpll_refs , i , ref ) {
if ( ref = = failed )
break ;
ops = dpll_pin_ops ( ref ) ;
dpll = ref - > dpll ;
if ( ops - > frequency_set ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , old_freq , extack ) )
NL_SET_ERR_MSG ( extack , " set frequency rollback failed " ) ;
}
return ret ;
2023-09-13 21:49:38 +01:00
}
static int
dpll_pin_on_pin_state_set ( struct dpll_pin * pin , u32 parent_idx ,
enum dpll_pin_state state ,
struct netlink_ext_ack * extack )
{
struct dpll_pin_ref * parent_ref ;
const struct dpll_pin_ops * ops ;
struct dpll_pin_ref * dpll_ref ;
void * pin_priv , * parent_priv ;
struct dpll_pin * parent ;
unsigned long i ;
int ret ;
if ( ! ( DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE &
2024-01-19 14:43:02 +01:00
pin - > prop . capabilities ) ) {
2023-09-13 21:49:38 +01:00
NL_SET_ERR_MSG ( extack , " state changing is not allowed " ) ;
return - EOPNOTSUPP ;
}
parent = xa_load ( & dpll_pin_xa , parent_idx ) ;
if ( ! parent )
return - EINVAL ;
parent_ref = xa_load ( & pin - > parent_refs , parent - > pin_idx ) ;
if ( ! parent_ref )
return - EINVAL ;
xa_for_each ( & parent - > dpll_refs , i , dpll_ref ) {
ops = dpll_pin_ops ( parent_ref ) ;
if ( ! ops - > state_on_pin_set )
return - EOPNOTSUPP ;
pin_priv = dpll_pin_on_pin_priv ( parent , pin ) ;
parent_priv = dpll_pin_on_dpll_priv ( dpll_ref - > dpll , parent ) ;
ret = ops - > state_on_pin_set ( pin , pin_priv , parent , parent_priv ,
state , extack ) ;
if ( ret )
return ret ;
}
__dpll_pin_change_ntf ( pin ) ;
return 0 ;
}
static int
dpll_pin_state_set ( struct dpll_device * dpll , struct dpll_pin * pin ,
enum dpll_pin_state state ,
struct netlink_ext_ack * extack )
{
const struct dpll_pin_ops * ops ;
struct dpll_pin_ref * ref ;
int ret ;
if ( ! ( DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE &
2024-01-19 14:43:02 +01:00
pin - > prop . capabilities ) ) {
2023-09-13 21:49:38 +01:00
NL_SET_ERR_MSG ( extack , " state changing is not allowed " ) ;
return - EOPNOTSUPP ;
}
ref = xa_load ( & pin - > dpll_refs , dpll - > id ) ;
ASSERT_NOT_NULL ( ref ) ;
ops = dpll_pin_ops ( ref ) ;
if ( ! ops - > state_on_dpll_set )
return - EOPNOTSUPP ;
ret = ops - > state_on_dpll_set ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , state , extack ) ;
if ( ret )
return ret ;
__dpll_pin_change_ntf ( pin ) ;
return 0 ;
}
static int
dpll_pin_prio_set ( struct dpll_device * dpll , struct dpll_pin * pin ,
u32 prio , struct netlink_ext_ack * extack )
{
const struct dpll_pin_ops * ops ;
struct dpll_pin_ref * ref ;
int ret ;
if ( ! ( DPLL_PIN_CAPABILITIES_PRIORITY_CAN_CHANGE &
2024-01-19 14:43:02 +01:00
pin - > prop . capabilities ) ) {
2023-09-13 21:49:38 +01:00
NL_SET_ERR_MSG ( extack , " prio changing is not allowed " ) ;
return - EOPNOTSUPP ;
}
ref = xa_load ( & pin - > dpll_refs , dpll - > id ) ;
ASSERT_NOT_NULL ( ref ) ;
ops = dpll_pin_ops ( ref ) ;
if ( ! ops - > prio_set )
return - EOPNOTSUPP ;
ret = ops - > prio_set ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) , dpll ,
dpll_priv ( dpll ) , prio , extack ) ;
if ( ret )
return ret ;
__dpll_pin_change_ntf ( pin ) ;
return 0 ;
}
static int
dpll_pin_direction_set ( struct dpll_pin * pin , struct dpll_device * dpll ,
enum dpll_pin_direction direction ,
struct netlink_ext_ack * extack )
{
const struct dpll_pin_ops * ops ;
struct dpll_pin_ref * ref ;
int ret ;
if ( ! ( DPLL_PIN_CAPABILITIES_DIRECTION_CAN_CHANGE &
2024-01-19 14:43:02 +01:00
pin - > prop . capabilities ) ) {
2023-09-13 21:49:38 +01:00
NL_SET_ERR_MSG ( extack , " direction changing is not allowed " ) ;
return - EOPNOTSUPP ;
}
ref = xa_load ( & pin - > dpll_refs , dpll - > id ) ;
ASSERT_NOT_NULL ( ref ) ;
ops = dpll_pin_ops ( ref ) ;
if ( ! ops - > direction_set )
return - EOPNOTSUPP ;
ret = ops - > direction_set ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , direction , extack ) ;
if ( ret )
return ret ;
__dpll_pin_change_ntf ( pin ) ;
return 0 ;
}
2023-10-11 12:12:34 +02:00
static int
dpll_pin_phase_adj_set ( struct dpll_pin * pin , struct nlattr * phase_adj_attr ,
struct netlink_ext_ack * extack )
{
struct dpll_pin_ref * ref , * failed ;
const struct dpll_pin_ops * ops ;
s32 phase_adj , old_phase_adj ;
struct dpll_device * dpll ;
unsigned long i ;
int ret ;
phase_adj = nla_get_s32 ( phase_adj_attr ) ;
2024-01-19 14:43:02 +01:00
if ( phase_adj > pin - > prop . phase_range . max | |
phase_adj < pin - > prop . phase_range . min ) {
2023-10-11 12:12:34 +02:00
NL_SET_ERR_MSG_ATTR ( extack , phase_adj_attr ,
" phase adjust value not supported " ) ;
return - EINVAL ;
}
xa_for_each ( & pin - > dpll_refs , i , ref ) {
ops = dpll_pin_ops ( ref ) ;
if ( ! ops - > phase_adjust_set | | ! ops - > phase_adjust_get ) {
NL_SET_ERR_MSG ( extack , " phase adjust not supported " ) ;
return - EOPNOTSUPP ;
}
}
ref = dpll_xa_ref_dpll_first ( & pin - > dpll_refs ) ;
ops = dpll_pin_ops ( ref ) ;
dpll = ref - > dpll ;
ret = ops - > phase_adjust_get ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , & old_phase_adj ,
extack ) ;
if ( ret ) {
NL_SET_ERR_MSG ( extack , " unable to get old phase adjust value " ) ;
return ret ;
}
if ( phase_adj = = old_phase_adj )
return 0 ;
xa_for_each ( & pin - > dpll_refs , i , ref ) {
ops = dpll_pin_ops ( ref ) ;
dpll = ref - > dpll ;
ret = ops - > phase_adjust_set ( pin ,
dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , phase_adj ,
extack ) ;
if ( ret ) {
failed = ref ;
NL_SET_ERR_MSG_FMT ( extack ,
" phase adjust set failed for dpll_id:%u " ,
dpll - > id ) ;
goto rollback ;
}
}
__dpll_pin_change_ntf ( pin ) ;
return 0 ;
rollback :
xa_for_each ( & pin - > dpll_refs , i , ref ) {
if ( ref = = failed )
break ;
ops = dpll_pin_ops ( ref ) ;
dpll = ref - > dpll ;
if ( ops - > phase_adjust_set ( pin , dpll_pin_on_dpll_priv ( dpll , pin ) ,
dpll , dpll_priv ( dpll ) , old_phase_adj ,
extack ) )
NL_SET_ERR_MSG ( extack , " set phase adjust rollback failed " ) ;
}
return ret ;
}
2023-09-13 21:49:38 +01:00
static int
dpll_pin_parent_device_set ( struct dpll_pin * pin , struct nlattr * parent_nest ,
struct netlink_ext_ack * extack )
{
struct nlattr * tb [ DPLL_A_PIN_MAX + 1 ] ;
enum dpll_pin_direction direction ;
enum dpll_pin_state state ;
struct dpll_pin_ref * ref ;
struct dpll_device * dpll ;
u32 pdpll_idx , prio ;
int ret ;
nla_parse_nested ( tb , DPLL_A_PIN_MAX , parent_nest ,
dpll_pin_parent_device_nl_policy , extack ) ;
if ( ! tb [ DPLL_A_PIN_PARENT_ID ] ) {
NL_SET_ERR_MSG ( extack , " device parent id expected " ) ;
return - EINVAL ;
}
pdpll_idx = nla_get_u32 ( tb [ DPLL_A_PIN_PARENT_ID ] ) ;
dpll = xa_load ( & dpll_device_xa , pdpll_idx ) ;
if ( ! dpll ) {
NL_SET_ERR_MSG ( extack , " parent device not found " ) ;
return - EINVAL ;
}
ref = xa_load ( & pin - > dpll_refs , dpll - > id ) ;
if ( ! ref ) {
NL_SET_ERR_MSG ( extack , " pin not connected to given parent device " ) ;
return - EINVAL ;
}
if ( tb [ DPLL_A_PIN_STATE ] ) {
state = nla_get_u32 ( tb [ DPLL_A_PIN_STATE ] ) ;
ret = dpll_pin_state_set ( dpll , pin , state , extack ) ;
if ( ret )
return ret ;
}
if ( tb [ DPLL_A_PIN_PRIO ] ) {
prio = nla_get_u32 ( tb [ DPLL_A_PIN_PRIO ] ) ;
ret = dpll_pin_prio_set ( dpll , pin , prio , extack ) ;
if ( ret )
return ret ;
}
if ( tb [ DPLL_A_PIN_DIRECTION ] ) {
direction = nla_get_u32 ( tb [ DPLL_A_PIN_DIRECTION ] ) ;
ret = dpll_pin_direction_set ( pin , dpll , direction , extack ) ;
if ( ret )
return ret ;
}
return 0 ;
}
static int
dpll_pin_parent_pin_set ( struct dpll_pin * pin , struct nlattr * parent_nest ,
struct netlink_ext_ack * extack )
{
struct nlattr * tb [ DPLL_A_PIN_MAX + 1 ] ;
u32 ppin_idx ;
int ret ;
nla_parse_nested ( tb , DPLL_A_PIN_MAX , parent_nest ,
dpll_pin_parent_pin_nl_policy , extack ) ;
if ( ! tb [ DPLL_A_PIN_PARENT_ID ] ) {
NL_SET_ERR_MSG ( extack , " device parent id expected " ) ;
return - EINVAL ;
}
ppin_idx = nla_get_u32 ( tb [ DPLL_A_PIN_PARENT_ID ] ) ;
2023-12-11 09:37:58 +01:00
if ( tb [ DPLL_A_PIN_STATE ] ) {
enum dpll_pin_state state = nla_get_u32 ( tb [ DPLL_A_PIN_STATE ] ) ;
ret = dpll_pin_on_pin_state_set ( pin , ppin_idx , state , extack ) ;
if ( ret )
return ret ;
}
2023-09-13 21:49:38 +01:00
return 0 ;
}
static int
dpll_pin_set_from_nlattr ( struct dpll_pin * pin , struct genl_info * info )
{
struct nlattr * a ;
int rem , ret ;
nla_for_each_attr ( a , genlmsg_data ( info - > genlhdr ) ,
genlmsg_len ( info - > genlhdr ) , rem ) {
switch ( nla_type ( a ) ) {
case DPLL_A_PIN_FREQUENCY :
ret = dpll_pin_freq_set ( pin , a , info - > extack ) ;
if ( ret )
return ret ;
break ;
2023-10-11 12:12:34 +02:00
case DPLL_A_PIN_PHASE_ADJUST :
ret = dpll_pin_phase_adj_set ( pin , a , info - > extack ) ;
if ( ret )
return ret ;
break ;
2023-09-13 21:49:38 +01:00
case DPLL_A_PIN_PARENT_DEVICE :
ret = dpll_pin_parent_device_set ( pin , a , info - > extack ) ;
if ( ret )
return ret ;
break ;
case DPLL_A_PIN_PARENT_PIN :
ret = dpll_pin_parent_pin_set ( pin , a , info - > extack ) ;
if ( ret )
return ret ;
break ;
}
}
return 0 ;
}
static struct dpll_pin *
dpll_pin_find ( u64 clock_id , struct nlattr * mod_name_attr ,
enum dpll_pin_type type , struct nlattr * board_label ,
struct nlattr * panel_label , struct nlattr * package_label ,
struct netlink_ext_ack * extack )
{
bool board_match , panel_match , package_match ;
struct dpll_pin * pin_match = NULL , * pin ;
const struct dpll_pin_properties * prop ;
bool cid_match , mod_match , type_match ;
unsigned long i ;
xa_for_each_marked ( & dpll_pin_xa , i , pin , DPLL_REGISTERED ) {
2024-01-19 14:43:02 +01:00
prop = & pin - > prop ;
2023-09-13 21:49:38 +01:00
cid_match = clock_id ? pin - > clock_id = = clock_id : true ;
mod_match = mod_name_attr & & module_name ( pin - > module ) ?
! nla_strcmp ( mod_name_attr ,
module_name ( pin - > module ) ) : true ;
type_match = type ? prop - > type = = type : true ;
board_match = board_label ? ( prop - > board_label ?
! nla_strcmp ( board_label , prop - > board_label ) : false ) :
true ;
panel_match = panel_label ? ( prop - > panel_label ?
! nla_strcmp ( panel_label , prop - > panel_label ) : false ) :
true ;
package_match = package_label ? ( prop - > package_label ?
! nla_strcmp ( package_label , prop - > package_label ) :
false ) : true ;
if ( cid_match & & mod_match & & type_match & & board_match & &
panel_match & & package_match ) {
if ( pin_match ) {
NL_SET_ERR_MSG ( extack , " multiple matches " ) ;
return ERR_PTR ( - EINVAL ) ;
}
pin_match = pin ;
2023-09-19 09:03:05 +08:00
}
2023-09-13 21:49:38 +01:00
}
if ( ! pin_match ) {
NL_SET_ERR_MSG ( extack , " not found " ) ;
return ERR_PTR ( - ENODEV ) ;
}
return pin_match ;
}
static struct dpll_pin * dpll_pin_find_from_nlattr ( struct genl_info * info )
{
struct nlattr * attr , * mod_name_attr = NULL , * board_label_attr = NULL ,
* panel_label_attr = NULL , * package_label_attr = NULL ;
enum dpll_pin_type type = 0 ;
u64 clock_id = 0 ;
int rem = 0 ;
nla_for_each_attr ( attr , genlmsg_data ( info - > genlhdr ) ,
genlmsg_len ( info - > genlhdr ) , rem ) {
switch ( nla_type ( attr ) ) {
case DPLL_A_PIN_CLOCK_ID :
if ( clock_id )
goto duplicated_attr ;
clock_id = nla_get_u64 ( attr ) ;
break ;
case DPLL_A_PIN_MODULE_NAME :
if ( mod_name_attr )
goto duplicated_attr ;
mod_name_attr = attr ;
break ;
case DPLL_A_PIN_TYPE :
if ( type )
goto duplicated_attr ;
type = nla_get_u32 ( attr ) ;
break ;
case DPLL_A_PIN_BOARD_LABEL :
if ( board_label_attr )
goto duplicated_attr ;
board_label_attr = attr ;
break ;
case DPLL_A_PIN_PANEL_LABEL :
if ( panel_label_attr )
goto duplicated_attr ;
panel_label_attr = attr ;
break ;
case DPLL_A_PIN_PACKAGE_LABEL :
if ( package_label_attr )
goto duplicated_attr ;
package_label_attr = attr ;
break ;
default :
break ;
}
}
if ( ! ( clock_id | | mod_name_attr | | board_label_attr | |
panel_label_attr | | package_label_attr ) ) {
NL_SET_ERR_MSG ( info - > extack , " missing attributes " ) ;
return ERR_PTR ( - EINVAL ) ;
}
return dpll_pin_find ( clock_id , mod_name_attr , type , board_label_attr ,
panel_label_attr , package_label_attr ,
info - > extack ) ;
duplicated_attr :
NL_SET_ERR_MSG ( info - > extack , " duplicated attribute " ) ;
return ERR_PTR ( - EINVAL ) ;
}
int dpll_nl_pin_id_get_doit ( struct sk_buff * skb , struct genl_info * info )
{
struct dpll_pin * pin ;
struct sk_buff * msg ;
struct nlattr * hdr ;
int ret ;
msg = genlmsg_new ( NLMSG_GOODSIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
hdr = genlmsg_put_reply ( msg , info , & dpll_nl_family , 0 ,
DPLL_CMD_PIN_ID_GET ) ;
2023-11-21 09:37:09 +08:00
if ( ! hdr ) {
nlmsg_free ( msg ) ;
2023-09-13 21:49:38 +01:00
return - EMSGSIZE ;
2023-11-21 09:37:09 +08:00
}
2023-09-13 21:49:38 +01:00
pin = dpll_pin_find_from_nlattr ( info ) ;
if ( ! IS_ERR ( pin ) ) {
2024-01-19 14:43:03 +01:00
if ( ! dpll_pin_available ( pin ) ) {
nlmsg_free ( msg ) ;
return - ENODEV ;
}
2023-09-13 21:49:38 +01:00
ret = dpll_msg_add_pin_handle ( msg , pin ) ;
if ( ret ) {
nlmsg_free ( msg ) ;
return ret ;
}
}
genlmsg_end ( msg , hdr ) ;
return genlmsg_reply ( msg , info ) ;
}
int dpll_nl_pin_get_doit ( struct sk_buff * skb , struct genl_info * info )
{
struct dpll_pin * pin = info - > user_ptr [ 0 ] ;
struct sk_buff * msg ;
struct nlattr * hdr ;
int ret ;
if ( ! pin )
return - ENODEV ;
msg = genlmsg_new ( NLMSG_GOODSIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
hdr = genlmsg_put_reply ( msg , info , & dpll_nl_family , 0 ,
DPLL_CMD_PIN_GET ) ;
2023-11-21 09:37:09 +08:00
if ( ! hdr ) {
nlmsg_free ( msg ) ;
2023-09-13 21:49:38 +01:00
return - EMSGSIZE ;
2023-11-21 09:37:09 +08:00
}
2023-09-13 21:49:38 +01:00
ret = dpll_cmd_pin_get_one ( msg , pin , info - > extack ) ;
if ( ret ) {
nlmsg_free ( msg ) ;
return ret ;
}
genlmsg_end ( msg , hdr ) ;
return genlmsg_reply ( msg , info ) ;
}
int dpll_nl_pin_get_dumpit ( struct sk_buff * skb , struct netlink_callback * cb )
{
struct dpll_dump_ctx * ctx = dpll_dump_context ( cb ) ;
struct dpll_pin * pin ;
struct nlattr * hdr ;
unsigned long i ;
int ret = 0 ;
2024-02-07 12:59:02 +01:00
mutex_lock ( & dpll_lock ) ;
2023-09-13 21:49:38 +01:00
xa_for_each_marked_start ( & dpll_pin_xa , i , pin , DPLL_REGISTERED ,
ctx - > idx ) {
2024-01-19 14:43:03 +01:00
if ( ! dpll_pin_available ( pin ) )
continue ;
2023-09-13 21:49:38 +01:00
hdr = genlmsg_put ( skb , NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq ,
& dpll_nl_family , NLM_F_MULTI ,
DPLL_CMD_PIN_GET ) ;
if ( ! hdr ) {
ret = - EMSGSIZE ;
break ;
}
ret = dpll_cmd_pin_get_one ( skb , pin , cb - > extack ) ;
if ( ret ) {
genlmsg_cancel ( skb , hdr ) ;
break ;
}
genlmsg_end ( skb , hdr ) ;
}
2024-02-07 12:59:02 +01:00
mutex_unlock ( & dpll_lock ) ;
2023-09-13 21:49:38 +01:00
if ( ret = = - EMSGSIZE ) {
ctx - > idx = i ;
return skb - > len ;
}
return ret ;
}
int dpll_nl_pin_set_doit ( struct sk_buff * skb , struct genl_info * info )
{
struct dpll_pin * pin = info - > user_ptr [ 0 ] ;
return dpll_pin_set_from_nlattr ( pin , info ) ;
}
static struct dpll_device *
dpll_device_find ( u64 clock_id , struct nlattr * mod_name_attr ,
enum dpll_type type , struct netlink_ext_ack * extack )
{
struct dpll_device * dpll_match = NULL , * dpll ;
bool cid_match , mod_match , type_match ;
unsigned long i ;
xa_for_each_marked ( & dpll_device_xa , i , dpll , DPLL_REGISTERED ) {
cid_match = clock_id ? dpll - > clock_id = = clock_id : true ;
mod_match = mod_name_attr ? ( module_name ( dpll - > module ) ?
! nla_strcmp ( mod_name_attr ,
module_name ( dpll - > module ) ) : false ) : true ;
type_match = type ? dpll - > type = = type : true ;
if ( cid_match & & mod_match & & type_match ) {
if ( dpll_match ) {
NL_SET_ERR_MSG ( extack , " multiple matches " ) ;
return ERR_PTR ( - EINVAL ) ;
}
dpll_match = dpll ;
}
}
if ( ! dpll_match ) {
NL_SET_ERR_MSG ( extack , " not found " ) ;
return ERR_PTR ( - ENODEV ) ;
}
return dpll_match ;
}
static struct dpll_device *
dpll_device_find_from_nlattr ( struct genl_info * info )
{
struct nlattr * attr , * mod_name_attr = NULL ;
enum dpll_type type = 0 ;
u64 clock_id = 0 ;
int rem = 0 ;
nla_for_each_attr ( attr , genlmsg_data ( info - > genlhdr ) ,
genlmsg_len ( info - > genlhdr ) , rem ) {
switch ( nla_type ( attr ) ) {
case DPLL_A_CLOCK_ID :
if ( clock_id )
goto duplicated_attr ;
clock_id = nla_get_u64 ( attr ) ;
break ;
case DPLL_A_MODULE_NAME :
if ( mod_name_attr )
goto duplicated_attr ;
mod_name_attr = attr ;
break ;
case DPLL_A_TYPE :
if ( type )
goto duplicated_attr ;
type = nla_get_u32 ( attr ) ;
break ;
default :
break ;
}
}
if ( ! clock_id & & ! mod_name_attr & & ! type ) {
NL_SET_ERR_MSG ( info - > extack , " missing attributes " ) ;
return ERR_PTR ( - EINVAL ) ;
}
return dpll_device_find ( clock_id , mod_name_attr , type , info - > extack ) ;
duplicated_attr :
NL_SET_ERR_MSG ( info - > extack , " duplicated attribute " ) ;
return ERR_PTR ( - EINVAL ) ;
}
int dpll_nl_device_id_get_doit ( struct sk_buff * skb , struct genl_info * info )
{
struct dpll_device * dpll ;
struct sk_buff * msg ;
struct nlattr * hdr ;
int ret ;
msg = genlmsg_new ( NLMSG_GOODSIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
hdr = genlmsg_put_reply ( msg , info , & dpll_nl_family , 0 ,
DPLL_CMD_DEVICE_ID_GET ) ;
2023-11-21 09:37:09 +08:00
if ( ! hdr ) {
nlmsg_free ( msg ) ;
2023-09-13 21:49:38 +01:00
return - EMSGSIZE ;
2023-11-21 09:37:09 +08:00
}
2023-09-13 21:49:38 +01:00
dpll = dpll_device_find_from_nlattr ( info ) ;
if ( ! IS_ERR ( dpll ) ) {
ret = dpll_msg_add_dev_handle ( msg , dpll ) ;
if ( ret ) {
nlmsg_free ( msg ) ;
return ret ;
}
}
genlmsg_end ( msg , hdr ) ;
return genlmsg_reply ( msg , info ) ;
}
int dpll_nl_device_get_doit ( struct sk_buff * skb , struct genl_info * info )
{
struct dpll_device * dpll = info - > user_ptr [ 0 ] ;
struct sk_buff * msg ;
struct nlattr * hdr ;
int ret ;
msg = genlmsg_new ( NLMSG_GOODSIZE , GFP_KERNEL ) ;
if ( ! msg )
return - ENOMEM ;
hdr = genlmsg_put_reply ( msg , info , & dpll_nl_family , 0 ,
DPLL_CMD_DEVICE_GET ) ;
2023-11-21 09:37:09 +08:00
if ( ! hdr ) {
nlmsg_free ( msg ) ;
2023-09-13 21:49:38 +01:00
return - EMSGSIZE ;
2023-11-21 09:37:09 +08:00
}
2023-09-13 21:49:38 +01:00
ret = dpll_device_get_one ( dpll , msg , info - > extack ) ;
if ( ret ) {
nlmsg_free ( msg ) ;
return ret ;
}
genlmsg_end ( msg , hdr ) ;
return genlmsg_reply ( msg , info ) ;
}
int dpll_nl_device_set_doit ( struct sk_buff * skb , struct genl_info * info )
{
/* placeholder for set command */
return 0 ;
}
int dpll_nl_device_get_dumpit ( struct sk_buff * skb , struct netlink_callback * cb )
{
struct dpll_dump_ctx * ctx = dpll_dump_context ( cb ) ;
struct dpll_device * dpll ;
struct nlattr * hdr ;
unsigned long i ;
int ret = 0 ;
2024-02-07 12:59:02 +01:00
mutex_lock ( & dpll_lock ) ;
2023-09-13 21:49:38 +01:00
xa_for_each_marked_start ( & dpll_device_xa , i , dpll , DPLL_REGISTERED ,
ctx - > idx ) {
hdr = genlmsg_put ( skb , NETLINK_CB ( cb - > skb ) . portid ,
cb - > nlh - > nlmsg_seq , & dpll_nl_family ,
NLM_F_MULTI , DPLL_CMD_DEVICE_GET ) ;
if ( ! hdr ) {
ret = - EMSGSIZE ;
break ;
}
ret = dpll_device_get_one ( dpll , skb , cb - > extack ) ;
if ( ret ) {
genlmsg_cancel ( skb , hdr ) ;
break ;
}
genlmsg_end ( skb , hdr ) ;
}
2024-02-07 12:59:02 +01:00
mutex_unlock ( & dpll_lock ) ;
2023-09-13 21:49:38 +01:00
if ( ret = = - EMSGSIZE ) {
ctx - > idx = i ;
return skb - > len ;
}
return ret ;
}
int dpll_pre_doit ( const struct genl_split_ops * ops , struct sk_buff * skb ,
struct genl_info * info )
{
u32 id ;
if ( GENL_REQ_ATTR_CHECK ( info , DPLL_A_ID ) )
return - EINVAL ;
mutex_lock ( & dpll_lock ) ;
id = nla_get_u32 ( info - > attrs [ DPLL_A_ID ] ) ;
info - > user_ptr [ 0 ] = dpll_device_get_by_id ( id ) ;
if ( ! info - > user_ptr [ 0 ] ) {
NL_SET_ERR_MSG ( info - > extack , " device not found " ) ;
goto unlock ;
}
return 0 ;
unlock :
mutex_unlock ( & dpll_lock ) ;
return - ENODEV ;
}
void dpll_post_doit ( const struct genl_split_ops * ops , struct sk_buff * skb ,
struct genl_info * info )
{
mutex_unlock ( & dpll_lock ) ;
}
int
dpll_lock_doit ( const struct genl_split_ops * ops , struct sk_buff * skb ,
struct genl_info * info )
{
mutex_lock ( & dpll_lock ) ;
return 0 ;
}
void
dpll_unlock_doit ( const struct genl_split_ops * ops , struct sk_buff * skb ,
struct genl_info * info )
{
mutex_unlock ( & dpll_lock ) ;
}
int dpll_pin_pre_doit ( const struct genl_split_ops * ops , struct sk_buff * skb ,
struct genl_info * info )
{
int ret ;
mutex_lock ( & dpll_lock ) ;
if ( GENL_REQ_ATTR_CHECK ( info , DPLL_A_PIN_ID ) ) {
ret = - EINVAL ;
goto unlock_dev ;
}
info - > user_ptr [ 0 ] = xa_load ( & dpll_pin_xa ,
nla_get_u32 ( info - > attrs [ DPLL_A_PIN_ID ] ) ) ;
2024-01-19 14:43:03 +01:00
if ( ! info - > user_ptr [ 0 ] | |
! dpll_pin_available ( info - > user_ptr [ 0 ] ) ) {
2023-09-13 21:49:38 +01:00
NL_SET_ERR_MSG ( info - > extack , " pin not found " ) ;
ret = - ENODEV ;
goto unlock_dev ;
}
return 0 ;
unlock_dev :
mutex_unlock ( & dpll_lock ) ;
return ret ;
}
void dpll_pin_post_doit ( const struct genl_split_ops * ops , struct sk_buff * skb ,
struct genl_info * info )
{
mutex_unlock ( & dpll_lock ) ;
}