Files
linux-stable-mirror/drivers/dpll/dpll_core.h
T
Ivan VeceraandJakub Kicinski 2c1bd78dc8 dpll: use pin owner's dpll ref for pin-level attribute reporting
Commit c191b319f2 ("dpll: allow registering FW-identified pin with a
different DPLL") relaxed dpll_pin_register() to let fwnode-identified pins
register with DPLLs from a different driver. This allows, for example, the
ICE driver to register a zl3073x-created pin with its TXC DPLL using
ice_dpll_txclk_ops, which lack frequency_get and phase_adjust_get
callbacks.

After such cross-driver registration, the pin's dpll_refs xarray contains
refs from both drivers. dpll_cmd_pin_get_one() calls
dpll_xa_ref_dpll_first() which returns the ref with the lowest DPLL id.
When the foreign DPLL (e.g. ICE TXC) has a lower id than the owner DPLL
(e.g. zl3073x), the foreign ops are used for reporting. Since those ops
lack callbacks like frequency_get, pin-level attributes are silently
omitted from the netlink response.

For example, a zl3073x output pin that should report frequency and
phase-adjust shows neither:

Before:
  # dpll pin show id 45
  pin id 45:
    module-name: zl3073x
    clock-id: 3427468959636104019
    board-label: 156M25_NAC0_CLKREF_SYNC
    package-label: OUT3
    type: synce-eth-port
    capabilities: 0x0
    phase-adjust-min: -2147483648
    phase-adjust-max: 2147483647
    phase-adjust-gran: 800
    parent-device:
      ...

After:
  # dpll pin show id 19
  pin id 19:
    module-name: zl3073x
    clock-id: 15964355450360090479
    board-label: 156M25_NAC0_CLKREF_SYNC
    package-label: OUT3
    type: synce-eth-port
    frequency: 156250000 Hz
    frequency-supported:
      156250000 Hz
    capabilities: 0x0
    phase-adjust-min: -2147483648
    phase-adjust-max: 2147483647
    phase-adjust-gran: 800
    phase-adjust: 0
    parent-device:
      ...

Fix this by:

1. Adding dpll_pin_own_dpll_ref_first() helper that returns the first ref
   whose DPLL matches the pin's (module, clock_id) tuple -- i.e. the DPLL
   from the driver that created the pin and has the complete set of ops.
   Return NULL if no owner ref is found.

2. Using dpll_pin_own_dpll_ref_first() in dpll_cmd_pin_get_one() with a
   fallback to dpll_xa_ref_dpll_first() for pin-on-pin child pins whose
   dpll_refs all point to a different driver's DPLLs.

3. Using dpll_pin_own_dpll_ref_first() in SET operations
   (dpll_pin_freq_set, dpll_pin_esync_set, dpll_pin_ref_sync_state_set,
   dpll_pin_phase_adj_set) returning -ENODEV if no owner ref exists.
   Replacing the validation loops that rejected the entire operation when
   any ref's ops lacked the required callback -- instead validate only the
   owner refs so that foreign DPLLs with incomplete ops no longer block
   SET operations.

4. Guarding all SET and rollback xa_for_each loops against NULL set
   callbacks so that foreign refs without the operation are safely skipped
   instead of causing a NULL pointer dereference.

Fixes: c191b319f2 ("dpll: allow registering FW-identified pin with a different DPLL")
Signed-off-by: Ivan Vecera <ivecera@redhat.com>
Acked-by: Vadim Fedorenko <vadim.fedorenko@linux.dev>
Link: https://patch.msgid.link/20260714125945.1823269-1-ivecera@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
2026-07-24 16:15:19 -07:00

108 lines
3.3 KiB
C

/* SPDX-License-Identifier: GPL-2.0 */
/*
* Copyright (c) 2023 Meta Platforms, Inc. and affiliates
* Copyright (c) 2023 Intel and affiliates
*/
#ifndef __DPLL_CORE_H__
#define __DPLL_CORE_H__
#include <linux/dpll.h>
#include <linux/list.h>
#include <linux/refcount.h>
#include <linux/ref_tracker.h>
#include "dpll_nl.h"
#define DPLL_REGISTERED XA_MARK_1
/**
* struct dpll_device - stores DPLL device internal data
* @id: unique id number for device given by dpll subsystem
* @device_idx: id given by dev driver
* @clock_id: unique identifier (clock_id) of a dpll
* @module: module of creator
* @type: type of a dpll
* @pin_refs: stores pins registered within a dpll
* @refcount: refcount
* @refcnt_tracker: ref_tracker directory for debugging reference leaks
* @registration_list: list of registered ops and priv data of dpll owners
**/
struct dpll_device {
u32 id;
u32 device_idx;
u64 clock_id;
struct module *module;
enum dpll_type type;
struct xarray pin_refs;
refcount_t refcount;
struct ref_tracker_dir refcnt_tracker;
struct list_head registration_list;
};
/**
* struct dpll_pin - structure for a dpll pin
* @id: unique id number for pin given by dpll subsystem
* @pin_idx: index of a pin given by dev driver
* @clock_id: clock_id of creator
* @module: module of creator
* @module_name: module name of creator
* @fwnode: optional reference to firmware node
* @dpll_refs: hold referencees to dplls pin was registered with
* @parent_refs: hold references to parent pins pin was registered with
* @ref_sync_pins: hold references to pins for Reference SYNC feature
* @prop: pin properties copied from the registerer
* @refcount: refcount
* @refcnt_tracker: ref_tracker directory for debugging reference leaks
* @rcu: rcu_head for kfree_rcu()
**/
struct dpll_pin {
u32 id;
u32 pin_idx;
u64 clock_id;
struct module *module;
char module_name[MODULE_NAME_LEN];
struct fwnode_handle *fwnode;
struct xarray dpll_refs;
struct xarray parent_refs;
struct xarray ref_sync_pins;
struct dpll_pin_properties prop;
refcount_t refcount;
struct ref_tracker_dir refcnt_tracker;
struct rcu_head rcu;
};
/**
* struct dpll_pin_ref - structure for referencing either dpll or pins
* @dpll: pointer to a dpll
* @pin: pointer to a pin
* @registration_list: list of ops and priv data registered with the ref
* @refcount: refcount
**/
struct dpll_pin_ref {
union {
struct dpll_device *dpll;
struct dpll_pin *pin;
};
struct list_head registration_list;
refcount_t refcount;
};
void *dpll_priv(struct dpll_device *dpll);
void *dpll_pin_on_dpll_priv(struct dpll_device *dpll, struct dpll_pin *pin);
void *dpll_pin_on_pin_priv(struct dpll_pin *parent, struct dpll_pin *pin);
const struct dpll_device_ops *dpll_device_ops(struct dpll_device *dpll);
struct dpll_device *dpll_device_get_by_id(int id);
struct dpll_pin_ref *dpll_pin_own_dpll_ref_first(struct dpll_pin *pin);
const struct dpll_pin_ops *dpll_pin_ops(struct dpll_pin_ref *ref);
struct dpll_pin_ref *dpll_xa_ref_dpll_first(struct xarray *xa_refs);
extern struct xarray dpll_device_xa;
extern struct xarray dpll_pin_xa;
extern struct mutex dpll_lock;
void dpll_device_notify(struct dpll_device *dpll, unsigned long action);
void dpll_pin_notify(struct dpll_pin *pin, u64 src_clock_id,
unsigned long action);
#endif