From 0abdb92f66600976b3e0f1c37ae9b4a6107055ea Mon Sep 17 00:00:00 2001 From: Arkadiusz Kubalewski Date: Thu, 2 Feb 2023 00:24:47 +0100 Subject: [PATCH 1/2] dpll: spec: Add Netlink spec in YAML Add a protocol spec for DPLL. Add code generated from the spec. Signed-off-by: Jakub Kicinski Signed-off-by: Arkadiusz Kubalewski --- Documentation/netlink/specs/dpll.yaml | 420 ++++++++++++++++++++++++++ drivers/dpll/dpll_nl.c | 128 ++++++++ drivers/dpll/dpll_nl.h | 33 ++ include/uapi/linux/dpll.h | 193 ++++++++++++ 4 files changed, 774 insertions(+) create mode 100644 Documentation/netlink/specs/dpll.yaml create mode 100644 drivers/dpll/dpll_nl.c create mode 100644 drivers/dpll/dpll_nl.h create mode 100644 include/uapi/linux/dpll.h diff --git a/Documentation/netlink/specs/dpll.yaml b/Documentation/netlink/specs/dpll.yaml new file mode 100644 index 000000000000..61fb7e8b4d43 --- /dev/null +++ b/Documentation/netlink/specs/dpll.yaml @@ -0,0 +1,420 @@ +name: dpll + +doc: DPLL subsystem. + +definitions: + - + type: const + name: temp-divider + value: 10 + - + type: const + name: pin-freq-1-hz + value: 1 + - + type: const + name: pin-freq-10-mhz + value: 10000000 + - + type: enum + name: lock-status + doc: | + Provides information of dpll device lock status, valid values for + DPLL_A_LOCK_STATUS attribute + entries: + - + name: unspec + doc: unspecified value + - + name: unlocked + doc: | + dpll was not yet locked to any valid (or is in one of modes: + DPLL_MODE_FREERUN, DPLL_MODE_NCO) + - + name: calibrating + doc: dpll is trying to lock to a valid signal + - + name: locked + doc: dpll is locked + - + name: holdover + doc: | + dpll is in holdover state - lost a valid lock or was forced by + selecting DPLL_MODE_HOLDOVER mode + render-max: true + - + type: enum + name: pin-type + doc: Enumerates types of a pin, valid values for DPLL_A_PIN_TYPE attribute + entries: + - + name: unspec + doc: unspecified value + - + name: mux + doc: aggregates another layer of selectable pins + - + name: ext + doc: external source + - + name: synce-eth-port + doc: ethernet port PHY's recovered clock + - + name: int-oscillator + doc: device internal oscillator + - + name: gnss + doc: GNSS recovered clock + render-max: true + - + type: enum + name: pin-state + doc: available pin modes + entries: + - + name: unspec + doc: unspecified value + - + name: connected + doc: pin connected + - + name: disconnected + doc: pin disconnected + render-max: true + - + type: enum + name: pin-direction + doc: available pin direction + entries: + - + name: unspec + doc: unspecified value + - + name: source + doc: pin used as a source of a signal + - + name: output + doc: pin used to output the signal + render-max: true + - + type: enum + name: mode + doc: | + working-modes a dpll can support, differentiate if and how dpll selects + one of its sources to syntonize with it + entries: + - + name: unspec + doc: unspecified value + - + name: manual + doc: source can be only selected by sending a request to dpll + - + name: automatic + doc: highest prio, valid source, auto selected by dpll + - + name: holdover + doc: dpll forced into holdover mode + - + name: freerun + doc: dpll driven on system clk, no holdover available + - + name: nco + doc: dpll driven by Numerically Controlled Oscillator + render-max: true + - + type: enum + name: type + doc: type of dpll, valid values for DPLL_A_TYPE attribute + entries: + - + name: unspec + doc: unspecified value + - + name: pps + doc: dpll produces Pulse-Per-Second signal + - + name: eec + doc: dpll drives the Ethernet Equipment Clock + - + type: enum + name: event + doc: events of dpll generic netlink family + entries: + - + name: unspec + doc: invalid event type + - + name: device-create + doc: dpll device created + - + name: device-delete + doc: dpll device deleted + - + name: device-change + doc: | + attribute of dpll device or pin changed, reason is to be found with + an attribute type (DPLL_A_*) received with the event + - + type: flags + name: pin-caps + doc: define capabilities of a pin + entries: + - + name: direction-can-change + - + name: priority-can-change + - + name: state-can-change + + +attribute-sets: + - + name: dpll + enum-name: dplla + attributes: + - + name: dpll + type: nest + value: 1 + - + name: id + type: u32 + - + name: dev-name + type: string + - + name: bus-name + type: string + - + name: mode + type: s32 + enum: mode + - + name: mode-supported + type: s32 + - + name: source-pin-idx + type: u32 + - + name: lock-status + type: s32 + enum: lock-status + - + name: temp + type: s32 + - + name: clock-id + type: u64 + - + name: type + type: s32 + enum: type + - + name: pin + type: nest + nested-attributes: pin + - + name: pin-idx + type: u32 + - + name: pin-description + type: string + - + name: pin-type + type: s32 + enum: pin-type + - + name: pin-direction + type: u8 + enum: pin-direction + - + name: pin-frequency + type: u32 + - + name: pin-frequency-supported + type: u32 + - + name: pin-any-frequency-min + type: u32 + - + name: pin-any-frequency-max + type: u32 + - + name: pin-prio + type: u32 + - + name: pin-state + type: u32 + enum: pin-state + - + name: pin-parent + type: nest + - + name: pin-parent-idx + type: u32 + - + name: pin-rclk-device + type: u32 + - + name: pin-dpll-caps + type: u32 + enum: pin-dpll-caps + enum-as-flags: true + - + name: pin + subset-of: dpll + attributes: + - + name: pin-idx + type: u32 + - + name: pin-description + type: string + - + name: pin-type + type: s32 + enum: pin-type + - + name: pin-direction + type: u8 + enum: pin-direction + - + name: pin-frequency + type: u32 + - + name: pin-frequency-supported + type: u32 + - + name: pin-any-frequency-min + type: u32 + - + name: pin-any-frequency-max + type: u32 + - + name: pin-prio + type: u32 + - + name: pin-state + type: u32 + enum: pin-state + - + name: pin-parent + type: nest + - + name: pin-parent-idx + type: u32 + - + name: pin-rclk-device + type: string + - + name: pin-dpll-caps + type: u32 + enum: pin-dpll-caps + enum-as-flags: true + +operations: + list: + - + name: unspec + doc: unused + + - + name: device-get + doc: | + Get list of DPLL devices (dump) or attributes of a single dpll device + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pre-doit + request: + attributes: + - id + - bus-name + - dev-name + reply: &all_attrs + attributes: + - id + + dump: + pre: dpll-cmd-device-get-start + request: + attributes: + - id + - bus-name + - dev-name + reply: *all_attrs + + - + name: device-set + doc: Set attributes for a DPLL device + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pre-doit + request: + attributes: + - id + - bus-name + - dev-name + - mode + + - + name: pin-get + doc: | + Get list of pins and its attributes. + - dump request without any attributes given - list all the pins in the system + - dump request with target dpll - list all the pins registered with a given dpll device + - do request with target dpll and target pin - single pin attributes + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pin-pre-doit + request: + attributes: + - id + - bus-name + - dev-name + - pin-idx + + dump: + request: + attributes: + - id + - bus-name + - dev-name + reply: + attributes: + - id + - bus-name + - dev-name + - pin-idx + + - + name: pin-set + doc: Set attributes of a target pin + attribute-set: dpll + flags: [ admin-perm ] + + do: + pre: dpll-pin-pre-doit + request: + attributes: + - id + - bus-name + - dev-name + - pin-idx + - pin-frequency + - pin-direction + - pin-prio + - pin-parent-idx + - pin-state + + +mcast-groups: + list: + - + name: monitor diff --git a/drivers/dpll/dpll_nl.c b/drivers/dpll/dpll_nl.c new file mode 100644 index 000000000000..5ce792f325fb --- /dev/null +++ b/drivers/dpll/dpll_nl.c @@ -0,0 +1,128 @@ +// SPDX-License-Identifier: BSD-3-Clause +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN kernel source */ + +#include +#include + +#include "dpll_nl.h" + +#include + +/* DPLL_CMD_DEVICE_GET - do */ +static const struct nla_policy dpll_device_get_do_nl_policy[DPLL_A_BUS_NAME + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + +/* DPLL_CMD_DEVICE_GET - dump */ +static const struct nla_policy dpll_device_get_dump_nl_policy[DPLL_A_BUS_NAME + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + +/* DPLL_CMD_DEVICE_SET - do */ +static const struct nla_policy dpll_device_set_nl_policy[DPLL_A_MODE + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_MODE] = NLA_POLICY_MAX(NLA_S32, 5), +}; + +/* DPLL_CMD_PIN_GET - do */ +static const struct nla_policy dpll_pin_get_do_nl_policy[DPLL_A_PIN_IDX + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_PIN_IDX] = { .type = NLA_U32, }, +}; + +/* DPLL_CMD_PIN_GET - dump */ +static const struct nla_policy dpll_pin_get_dump_nl_policy[DPLL_A_BUS_NAME + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + +/* DPLL_CMD_PIN_SET - do */ +static const struct nla_policy dpll_pin_set_nl_policy[DPLL_A_PIN_PARENT_IDX + 1] = { + [DPLL_A_ID] = { .type = NLA_U32, }, + [DPLL_A_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DPLL_A_PIN_IDX] = { .type = NLA_U32, }, + [DPLL_A_PIN_FREQUENCY] = { .type = NLA_U32, }, + [DPLL_A_PIN_DIRECTION] = NLA_POLICY_MAX(NLA_U8, 2), + [DPLL_A_PIN_PRIO] = { .type = NLA_U32, }, + [DPLL_A_PIN_PARENT_IDX] = { .type = NLA_U32, }, + [DPLL_A_PIN_STATE] = NLA_POLICY_MAX(NLA_U32, 2), +}; + +/* Ops table for dpll */ +static const struct genl_split_ops dpll_nl_ops[6] = { + { + .cmd = DPLL_CMD_DEVICE_GET, + .pre_doit = dpll_pre_doit, + .doit = dpll_nl_device_get_doit, + .policy = dpll_device_get_do_nl_policy, + .maxattr = DPLL_A_BUS_NAME, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_DEVICE_GET, + .start = dpll_cmd_device_get_start, + .dumpit = dpll_nl_device_get_dumpit, + .policy = dpll_device_get_dump_nl_policy, + .maxattr = DPLL_A_BUS_NAME, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = DPLL_CMD_DEVICE_SET, + .pre_doit = dpll_pre_doit, + .doit = dpll_nl_device_set_doit, + .policy = dpll_device_set_nl_policy, + .maxattr = DPLL_A_MODE, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_PIN_GET, + .pre_doit = dpll_pin_pre_doit, + .doit = dpll_nl_pin_get_doit, + .policy = dpll_pin_get_do_nl_policy, + .maxattr = DPLL_A_PIN_IDX, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, + { + .cmd = DPLL_CMD_PIN_GET, + .dumpit = dpll_nl_pin_get_dumpit, + .policy = dpll_pin_get_dump_nl_policy, + .maxattr = DPLL_A_BUS_NAME, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DUMP, + }, + { + .cmd = DPLL_CMD_PIN_SET, + .pre_doit = dpll_pin_pre_doit, + .doit = dpll_nl_pin_set_doit, + .policy = dpll_pin_set_nl_policy, + .maxattr = DPLL_A_PIN_PARENT_IDX, + .flags = GENL_ADMIN_PERM | GENL_CMD_CAP_DO, + }, +}; + +static const struct genl_multicast_group dpll_nl_mcgrps[] = { + [DPLL_NLGRP_MONITOR] = { "monitor", }, +}; + +struct genl_family dpll_nl_family __ro_after_init = { + .name = DPLL_FAMILY_NAME, + .version = DPLL_FAMILY_VERSION, + .netnsok = true, + .parallel_ops = true, + .module = THIS_MODULE, + .split_ops = dpll_nl_ops, + .n_split_ops = ARRAY_SIZE(dpll_nl_ops), + .mcgrps = dpll_nl_mcgrps, + .n_mcgrps = ARRAY_SIZE(dpll_nl_mcgrps), +}; diff --git a/drivers/dpll/dpll_nl.h b/drivers/dpll/dpll_nl.h new file mode 100644 index 000000000000..e7d2d977aaf4 --- /dev/null +++ b/drivers/dpll/dpll_nl.h @@ -0,0 +1,33 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN kernel header */ + +#ifndef _LINUX_DPLL_GEN_H +#define _LINUX_DPLL_GEN_H + +#include +#include + +#include + +int dpll_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +int dpll_pin_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info); +int dpll_cmd_device_get_start(struct netlink_callback *cb); + +int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); +int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_pin_get_doit(struct sk_buff *skb, struct genl_info *info); +int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb); +int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info); + +enum { + DPLL_NLGRP_MONITOR, +}; + +extern struct genl_family dpll_nl_family; + +#endif /* _LINUX_DPLL_GEN_H */ diff --git a/include/uapi/linux/dpll.h b/include/uapi/linux/dpll.h new file mode 100644 index 000000000000..b2cf3cf25853 --- /dev/null +++ b/include/uapi/linux/dpll.h @@ -0,0 +1,193 @@ +/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */ +/* Do not edit directly, auto-generated from: */ +/* Documentation/netlink/specs/dpll.yaml */ +/* YNL-GEN uapi header */ + +#ifndef _UAPI_LINUX_DPLL_H +#define _UAPI_LINUX_DPLL_H + +#define DPLL_FAMILY_NAME "dpll" +#define DPLL_FAMILY_VERSION 1 + +#define DPLL_TEMP_DIVIDER 10 +#define DPLL_PIN_FREQ_1_HZ 1 +#define DPLL_PIN_FREQ_10_MHZ 10000000 + +/** + * enum dpll_lock_status - Provides information of dpll device lock status, + * valid values for DPLL_A_LOCK_STATUS attribute + * @DPLL_LOCK_STATUS_UNSPEC: unspecified value + * @DPLL_LOCK_STATUS_UNLOCKED: dpll was not yet locked to any valid (or is in + * one of modes: DPLL_MODE_FREERUN, DPLL_MODE_NCO) + * @DPLL_LOCK_STATUS_CALIBRATING: dpll is trying to lock to a valid signal + * @DPLL_LOCK_STATUS_LOCKED: dpll is locked + * @DPLL_LOCK_STATUS_HOLDOVER: dpll is in holdover state - lost a valid lock or + * was forced by selecting DPLL_MODE_HOLDOVER mode + */ +enum dpll_lock_status { + DPLL_LOCK_STATUS_UNSPEC, + DPLL_LOCK_STATUS_UNLOCKED, + DPLL_LOCK_STATUS_CALIBRATING, + DPLL_LOCK_STATUS_LOCKED, + DPLL_LOCK_STATUS_HOLDOVER, + + __DPLL_LOCK_STATUS_MAX, + DPLL_LOCK_STATUS_MAX = (__DPLL_LOCK_STATUS_MAX - 1) +}; + +/** + * enum dpll_pin_type - Enumerates types of a pin, valid values for + * DPLL_A_PIN_TYPE attribute + * @DPLL_PIN_TYPE_UNSPEC: unspecified value + * @DPLL_PIN_TYPE_MUX: aggregates another layer of selectable pins + * @DPLL_PIN_TYPE_EXT: external source + * @DPLL_PIN_TYPE_SYNCE_ETH_PORT: ethernet port PHY's recovered clock + * @DPLL_PIN_TYPE_INT_OSCILLATOR: device internal oscillator + * @DPLL_PIN_TYPE_GNSS: GNSS recovered clock + */ +enum dpll_pin_type { + DPLL_PIN_TYPE_UNSPEC, + DPLL_PIN_TYPE_MUX, + DPLL_PIN_TYPE_EXT, + DPLL_PIN_TYPE_SYNCE_ETH_PORT, + DPLL_PIN_TYPE_INT_OSCILLATOR, + DPLL_PIN_TYPE_GNSS, + + __DPLL_PIN_TYPE_MAX, + DPLL_PIN_TYPE_MAX = (__DPLL_PIN_TYPE_MAX - 1) +}; + +/** + * enum dpll_pin_state - available pin modes + * @DPLL_PIN_STATE_UNSPEC: unspecified value + * @DPLL_PIN_STATE_CONNECTED: pin connected + * @DPLL_PIN_STATE_DISCONNECTED: pin disconnected + */ +enum dpll_pin_state { + DPLL_PIN_STATE_UNSPEC, + DPLL_PIN_STATE_CONNECTED, + DPLL_PIN_STATE_DISCONNECTED, + + __DPLL_PIN_STATE_MAX, + DPLL_PIN_STATE_MAX = (__DPLL_PIN_STATE_MAX - 1) +}; + +/** + * enum dpll_pin_direction - available pin direction + * @DPLL_PIN_DIRECTION_UNSPEC: unspecified value + * @DPLL_PIN_DIRECTION_SOURCE: pin used as a source of a signal + * @DPLL_PIN_DIRECTION_OUTPUT: pin used to output the signal + */ +enum dpll_pin_direction { + DPLL_PIN_DIRECTION_UNSPEC, + DPLL_PIN_DIRECTION_SOURCE, + DPLL_PIN_DIRECTION_OUTPUT, + + __DPLL_PIN_DIRECTION_MAX, + DPLL_PIN_DIRECTION_MAX = (__DPLL_PIN_DIRECTION_MAX - 1) +}; + +/** + * enum dpll_mode - working-modes a dpll can support, differentiate if and how + * dpll selects one of its sources to syntonize with it + * @DPLL_MODE_UNSPEC: unspecified value + * @DPLL_MODE_MANUAL: source can be only selected by sending a request to dpll + * @DPLL_MODE_AUTOMATIC: highest prio, valid source, auto selected by dpll + * @DPLL_MODE_HOLDOVER: dpll forced into holdover mode + * @DPLL_MODE_FREERUN: dpll driven on system clk, no holdover available + * @DPLL_MODE_NCO: dpll driven by Numerically Controlled Oscillator + */ +enum dpll_mode { + DPLL_MODE_UNSPEC, + DPLL_MODE_MANUAL, + DPLL_MODE_AUTOMATIC, + DPLL_MODE_HOLDOVER, + DPLL_MODE_FREERUN, + DPLL_MODE_NCO, + + __DPLL_MODE_MAX, + DPLL_MODE_MAX = (__DPLL_MODE_MAX - 1) +}; + +/** + * enum dpll_type - type of dpll, valid values for DPLL_A_TYPE attribute + * @DPLL_TYPE_UNSPEC: unspecified value + * @DPLL_TYPE_PPS: dpll produces Pulse-Per-Second signal + * @DPLL_TYPE_EEC: dpll drives the Ethernet Equipment Clock + */ +enum dpll_type { + DPLL_TYPE_UNSPEC, + DPLL_TYPE_PPS, + DPLL_TYPE_EEC, +}; + +/** + * enum dpll_event - events of dpll generic netlink family + * @DPLL_EVENT_UNSPEC: invalid event type + * @DPLL_EVENT_DEVICE_CREATE: dpll device created + * @DPLL_EVENT_DEVICE_DELETE: dpll device deleted + * @DPLL_EVENT_DEVICE_CHANGE: attribute of dpll device or pin changed, reason + * is to be found with an attribute type (DPLL_A_*) received with the event + */ +enum dpll_event { + DPLL_EVENT_UNSPEC, + DPLL_EVENT_DEVICE_CREATE, + DPLL_EVENT_DEVICE_DELETE, + DPLL_EVENT_DEVICE_CHANGE, +}; + +/** + * enum dpll_pin_caps - define capabilities of a pin + */ +enum dpll_pin_caps { + DPLL_PIN_CAPS_DIRECTION_CAN_CHANGE = 1, + DPLL_PIN_CAPS_PRIORITY_CAN_CHANGE = 2, + DPLL_PIN_CAPS_STATE_CAN_CHANGE = 4, +}; + +enum dplla { + DPLL_A_DPLL = 1, + DPLL_A_ID, + DPLL_A_DEV_NAME, + DPLL_A_BUS_NAME, + DPLL_A_MODE, + DPLL_A_MODE_SUPPORTED, + DPLL_A_SOURCE_PIN_IDX, + DPLL_A_LOCK_STATUS, + DPLL_A_TEMP, + DPLL_A_CLOCK_ID, + DPLL_A_TYPE, + DPLL_A_PIN, + DPLL_A_PIN_IDX, + DPLL_A_PIN_DESCRIPTION, + DPLL_A_PIN_TYPE, + DPLL_A_PIN_DIRECTION, + DPLL_A_PIN_FREQUENCY, + DPLL_A_PIN_FREQUENCY_SUPPORTED, + DPLL_A_PIN_ANY_FREQUENCY_MIN, + DPLL_A_PIN_ANY_FREQUENCY_MAX, + DPLL_A_PIN_PRIO, + DPLL_A_PIN_STATE, + DPLL_A_PIN_PARENT, + DPLL_A_PIN_PARENT_IDX, + DPLL_A_PIN_RCLK_DEVICE, + DPLL_A_PIN_DPLL_CAPS, + + __DPLL_A_MAX, + DPLL_A_MAX = (__DPLL_A_MAX - 1) +}; + +enum { + DPLL_CMD_UNSPEC, + DPLL_CMD_DEVICE_GET, + DPLL_CMD_DEVICE_SET, + DPLL_CMD_PIN_GET, + DPLL_CMD_PIN_SET, + + __DPLL_CMD_MAX, + DPLL_CMD_MAX = (__DPLL_CMD_MAX - 1) +}; + +#define DPLL_MCGRP_MONITOR "monitor" + +#endif /* _UAPI_LINUX_DPLL_H */ From 943547e68aa5d09ba1cb361089392995af25561d Mon Sep 17 00:00:00 2001 From: Arkadiusz Kubalewski Date: Sat, 19 Nov 2022 00:25:15 +0100 Subject: [PATCH 2/2] dpll: Add DPLL framework base functions DPLL framework is used to represent and configure DPLL devices in systems. Each device that has DPLL and can configure sources and outputs can use this framework. Netlink interface is used to provide configuration data and to receive notification messages about changes in the configuration or status of DPLL device. Inputs and outputs of the DPLL device are represented as special objects which could be dynamically added to and removed from DPLL device. Changes: dpll: redesign after ynl and review comments dpll: replace cookie with clock id dpll: add clock class Provide userspace with clock class value of DPLL with dpll device dump netlink request. Clock class is assigned by driver allocating a dpll device. Clock class values are defined as specified in: ITU-T G.8273.2/Y.1368.2 recommendation. dpll: follow one naming schema in dpll subsys dpll: fix dpll device naming scheme Fix dpll device naming scheme by use of new pattern. "dpll_%s_%d_%d", where: - %s - dev_name(parent) of parent device, - %d (1) - enum value of dpll type, - %d (2) - device index provided by parent device. dpll: remove description length parameter dpll: fix muxed/shared pin registration Let the kernel module to register a shared or muxed pin without finding it or its parent. Instead use a parent/shared pin description to find correct pin internally in dpll_core, simplifing a dpll API. dpll: move function comments to dpll_core.c, fix exports dpll: remove single-use helper functions dpll: merge device register with alloc dpll: lock and unlock mutex on dpll device release dpll: move dpll_type to uapi header dpll: rename DPLLA_DUMP_FILTER to DPLLA_FILTER dpll: rename dpll_pin_state to dpll_pin_mode dpll: rename DPLL_MODE_FORCED to DPLL_MODE_MANUAL dpll: remove DPLL_CHANGE_PIN_TYPE enum value Co-developed-by: Milena Olech Signed-off-by: Milena Olech Co-developed-by: Michal Michalik Signed-off-by: Michal Michalik Co-developed-by: Arkadiusz Kubalewski Signed-off-by: Arkadiusz Kubalewski Signed-off-by: Vadim Fedorenko --- MAINTAINERS | 8 + drivers/Kconfig | 2 + drivers/Makefile | 1 + drivers/dpll/Kconfig | 7 + drivers/dpll/Makefile | 10 + drivers/dpll/dpll_core.c | 548 ++++++++++++++++++++++ drivers/dpll/dpll_core.h | 100 ++++ drivers/dpll/dpll_netlink.c | 898 ++++++++++++++++++++++++++++++++++++ drivers/dpll/dpll_netlink.h | 31 ++ include/linux/dpll.h | 251 ++++++++++ 10 files changed, 1856 insertions(+) create mode 100644 drivers/dpll/Kconfig create mode 100644 drivers/dpll/Makefile create mode 100644 drivers/dpll/dpll_core.c create mode 100644 drivers/dpll/dpll_core.h create mode 100644 drivers/dpll/dpll_netlink.c create mode 100644 drivers/dpll/dpll_netlink.h create mode 100644 include/linux/dpll.h diff --git a/MAINTAINERS b/MAINTAINERS index f2bd469ffae5..70fb0424bf27 100644 --- a/MAINTAINERS +++ b/MAINTAINERS @@ -6414,6 +6414,14 @@ F: Documentation/networking/device_drivers/ethernet/freescale/dpaa2/switch-drive F: drivers/net/ethernet/freescale/dpaa2/dpaa2-switch* F: drivers/net/ethernet/freescale/dpaa2/dpsw* +DPLL CLOCK SUBSYSTEM +M: Vadim Fedorenko +L: netdev@vger.kernel.org +S: Maintained +F: drivers/dpll/* +F: include/net/dpll.h +F: include/uapi/linux/dpll.h + DRBD DRIVER M: Philipp Reisner M: Lars Ellenberg diff --git a/drivers/Kconfig b/drivers/Kconfig index 968bd0a6fd78..453df9e1210d 100644 --- a/drivers/Kconfig +++ b/drivers/Kconfig @@ -241,4 +241,6 @@ source "drivers/peci/Kconfig" source "drivers/hte/Kconfig" +source "drivers/dpll/Kconfig" + endmenu diff --git a/drivers/Makefile b/drivers/Makefile index bdf1c66141c9..7cbee58bc692 100644 --- a/drivers/Makefile +++ b/drivers/Makefile @@ -189,3 +189,4 @@ obj-$(CONFIG_COUNTER) += counter/ obj-$(CONFIG_MOST) += most/ obj-$(CONFIG_PECI) += peci/ obj-$(CONFIG_HTE) += hte/ +obj-$(CONFIG_DPLL) += dpll/ diff --git a/drivers/dpll/Kconfig b/drivers/dpll/Kconfig new file mode 100644 index 000000000000..a4cae73f20d3 --- /dev/null +++ b/drivers/dpll/Kconfig @@ -0,0 +1,7 @@ +# SPDX-License-Identifier: GPL-2.0-only +# +# Generic DPLL drivers configuration +# + +config DPLL + bool diff --git a/drivers/dpll/Makefile b/drivers/dpll/Makefile new file mode 100644 index 000000000000..d3926f2a733d --- /dev/null +++ b/drivers/dpll/Makefile @@ -0,0 +1,10 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Makefile for DPLL drivers. +# + +obj-$(CONFIG_DPLL) += dpll_sys.o +dpll_sys-y += dpll_core.o +dpll_sys-y += dpll_netlink.o +dpll_sys-y += dpll_nl.o + diff --git a/drivers/dpll/dpll_core.c b/drivers/dpll/dpll_core.c new file mode 100644 index 000000000000..8e013331eecb --- /dev/null +++ b/drivers/dpll/dpll_core.c @@ -0,0 +1,548 @@ +// 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 +#include +#include +#include + +#include "dpll_core.h" + +DEFINE_MUTEX(dpll_device_xa_lock); +DEFINE_MUTEX(dpll_pin_xa_lock); + +DEFINE_XARRAY_FLAGS(dpll_device_xa, XA_FLAGS_ALLOC); +DEFINE_XARRAY_FLAGS(dpll_pin_xa, XA_FLAGS_ALLOC); + +#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)) + +static struct class dpll_class = { + .name = "dpll", +}; + +/** + * dpll_device_get_by_id - find dpll device by it's id + * @id: id of searched dpll + * + * Return: dpll_device struct if found, NULL otherwise. + */ +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; +} + +/** + * dpll_device_get_by_name - find dpll device by it's id + * @bus_name: bus name of searched dpll + * @dev_name: dev name of searched dpll + * + * Return: dpll_device struct if found, NULL otherwise. + */ +struct dpll_device * +dpll_device_get_by_name(const char *bus_name, const char *device_name) +{ + struct dpll_device *dpll, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_device_xa_lock); + xa_for_each_marked(&dpll_device_xa, index, dpll, DPLL_REGISTERED) { + if (!strcmp(dev_bus_name(&dpll->dev), bus_name) && + !strcmp(dev_name(&dpll->dev), device_name)) { + ret = dpll; + break; + } + } + mutex_unlock(&dpll_device_xa_lock); + + return ret; +} + +struct dpll_device +*dpll_device_alloc(const u64 clock_id, u32 dev_driver_id, struct module *module) +{ + struct dpll_device *dpll; + int ret; + + dpll = kzalloc(sizeof(*dpll), GFP_KERNEL); + if (!dpll) + return ERR_PTR(-ENOMEM); + mutex_init(&dpll->lock); + dpll->dev.class = &dpll_class; + dpll->dev_driver_id = dev_driver_id; + dpll->clock_id = clock_id; + ret = xa_alloc(&dpll_device_xa, &dpll->id, dpll, + xa_limit_16b, GFP_KERNEL); + if (ret) { + kfree(dpll); + mutex_unlock(&dpll_device_xa_lock); + return ERR_PTR(ret); + } + xa_init_flags(&dpll->pins, XA_FLAGS_ALLOC); + mutex_unlock(&dpll_device_xa_lock); + + return dpll; +} + +static int dpll_pin_ref_dpll_add(struct dpll_pin *pin, struct dpll_device *dpll, + struct dpll_pin_ops *ops, void *priv) +{ + struct dpll_pin_ref *ref, *pos; + unsigned long index; + u32 idx; + int ret; + + ref = kzalloc(sizeof(struct dpll_pin_ref), GFP_KERNEL); + if (!ref) + return -ENOMEM; + ref->dpll = dpll; + ref->ops = ops; + ref->priv = priv; + if (!xa_empty(&pin->dpll_refs)) { + xa_for_each(&pin->dpll_refs, index, pos) { + if (pos->dpll == ref->dpll) + return -EEXIST; + } + } + + ret = xa_alloc(&pin->dpll_refs, &idx, ref, xa_limit_16b, GFP_KERNEL); + if (!ret) + refcount_inc(&dpll->refcount); + + return ret; +} + +static void +dpll_pin_ref_dpll_del(struct dpll_pin *pin, struct dpll_device *dpll) +{ + struct dpll_pin_ref *pos; + unsigned long index; + + xa_for_each(&pin->dpll_refs, index, pos) { + if (pos->dpll == dpll) { + if (pos == xa_erase(&pin->dpll_refs, index)) { + refcount_dec(&dpll->refcount); + kfree(pos); + break; + } + } + } +} + +struct dpll_device +*dpll_device_get(u64 clock_id, u32 dev_driver_id, struct module *module) +{ + struct dpll_device *dpll, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_device_xa_lock); + xa_for_each(&dpll_device_xa, index, dpll) { + if (dpll->clock_id == clock_id && + dpll->dev_driver_id == dev_driver_id && + dpll->module == module) { + ret = dpll; + break; + } + } + if (!ret) + ret = dpll_device_alloc(clock_id, dev_driver_id, module); + mutex_unlock(&dpll_device_xa_lock); + if (!IS_ERR_OR_NULL(ret)) + refcount_inc(&ret->refcount); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_device_get); + +void dpll_device_free(struct dpll_device *dpll) +{ + WARN_ON_ONCE(!xa_empty(&dpll->pins)); + xa_destroy(&dpll->pins); + mutex_destroy(&dpll->lock); + kfree(dpll); +} + +void dpll_device_put(struct dpll_device *dpll) +{ + if (!dpll) + return; + + if (refcount_dec_and_test(&dpll->refcount) == 0) + dpll_device_free(dpll); + +} +EXPORT_SYMBOL_GPL(dpll_device_put); + +void dpll_device_register(struct dpll_device *dpll, enum dpll_type type, + void *priv, struct device *owner) +{ + mutex_lock(&dpll->lock); + ASSERT_DPLL_NOT_REGISTERED(dpll); + dpll->dev.bus = owner->bus; + dpll->parent = owner; + dpll->type = type; + dev_set_name(&dpll->dev, "%s_%d", dev_name(owner), + dpll->dev_driver_id); + dpll->priv = priv; + xa_set_mark(&dpll_device_xa, dpll->id, DPLL_REGISTERED); + mutex_unlock(&dpll->lock); + dpll_notify_device_create(dpll); +} +EXPORT_SYMBOL_GPL(dpll_device_register); + +/** + * dpll_device_deregister - deregister dpll device + * @dpll: registered dpll pointer + * + * Note: It does not free the memory + */ +void dpll_device_deregister(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); + dpll_notify_device_delete(dpll); +} +EXPORT_SYMBOL_GPL(dpll_device_deregister); + +void dpll_pin_put(struct dpll_pin *pin) +{ + if (refcount_dec_and_test(&pin->refcount) == 0) { + xa_destroy(&pin->dpll_refs); + xa_destroy(&pin->pin_refs); + mutex_destroy(&pin->lock); + kfree(pin->prop.description); + kfree(pin->rclk_dev_name); + kfree(pin); + } +} +EXPORT_SYMBOL_GPL(dpll_pin_put); + +struct dpll_pin +*dpll_pin_alloc(u64 clock_id, u8 device_drv_id, struct module *module, + const struct dpll_pin_properties *prop) +{ + struct dpll_pin *pin; + int ret; + + pin = kzalloc(sizeof(*pin), GFP_KERNEL); + if (!pin) + return ERR_PTR(-ENOMEM); + mutex_init(&pin->lock); + pin->dev_driver_id = device_drv_id; + pin->clock_id = clock_id; + pin->module = module; + refcount_set(&pin->refcount, 0); + if (WARN_ON(pin->prop.description)) + return ERR_PTR(-EINVAL); + pin->prop.description = kstrdup(pin->prop.description, GFP_KERNEL); + if (!pin->prop.description) + return ERR_PTR(-ENOMEM); + if (WARN_ON(pin->prop.type <= DPLL_PIN_TYPE_UNSPEC || + pin->prop.type > DPLL_PIN_TYPE_MAX)) + return ERR_PTR(-EINVAL); + pin->prop.type = pin->prop.type; + pin->prop.caps_supported = pin->prop.caps_supported; + pin->prop.freq_supported = pin->prop.freq_supported; + pin->prop.any_freq_min = pin->prop.any_freq_min; + pin->prop.any_freq_max = pin->prop.any_freq_max; + xa_init_flags(&pin->pin_refs, XA_FLAGS_ALLOC); + xa_init_flags(&pin->dpll_refs, XA_FLAGS_ALLOC); + ret = xa_alloc(&dpll_pin_xa, &pin->idx, pin, + xa_limit_16b, GFP_KERNEL); + if (ret) { + dpll_pin_put(pin); + return ERR_PTR(ret); + } + + return pin; +} + +struct dpll_pin +*dpll_pin_get(u64 clock_id, u32 device_drv_id, struct module *module, + const struct dpll_pin_properties *prop) +{ + struct dpll_pin *pos, *ret = NULL; + unsigned long index; + + mutex_lock(&dpll_pin_xa_lock); + xa_for_each(&dpll_pin_xa, index, pos) { + if (pos->clock_id == clock_id && + pos->dev_driver_id == device_drv_id && + pos->module == module) { + ret = pos; + break; + } + } + if (!ret) + ret = dpll_pin_alloc(clock_id, device_drv_id, module, prop); + mutex_unlock(&dpll_pin_xa_lock); + if (!IS_ERR_OR_NULL(ret)) + refcount_inc(&ret->refcount); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_get); + +static int dpll_xa_pin_add(struct xarray *pins, struct dpll_pin *pin) +{ + struct dpll_pin *pos; + unsigned long index; + u32 idx; + + xa_for_each(pins, index, pos) { + if (WARN_ON(pos == pin || + !strcmp(pos->prop.description, + pin->prop.description)) || + pos->dev_driver_id == pin->dev_driver_id) + return -EEXIST; + } + + return xa_alloc(pins, &idx, pin, xa_limit_16b, GFP_KERNEL); +} + +static int dpll_xa_pin_del(struct xarray *xa_pins, struct dpll_pin *pin) +{ + struct dpll_pin *pos; + unsigned long index; + + xa_for_each(xa_pins, index, pos) { + if (pos == pin) { + WARN_ON_ONCE(pos != xa_erase(xa_pins, index)); + return 0; + } + } + + return -ENXIO; +} + +int +dpll_pin_on_dpll_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device) +{ + int ret; + + if (WARN_ON(!dpll)) + return -ENODEV; + if (WARN_ON(!pin)) + return -EINVAL; + if (rclk_device) { + pin->rclk_dev_name = kstrdup(dev_name(rclk_device), GFP_KERNEL); + if (!pin->rclk_dev_name) + return -ENOMEM; + } + mutex_lock(&dpll->lock); + ret = dpll_pin_ref_dpll_add(pin, dpll, ops, priv); + if (ret) + goto rclk_free; + ret = dpll_xa_pin_add(&dpll->pins, pin); + if (ret) { + dpll_pin_ref_dpll_del(pin, dpll); + goto rclk_free; + } else { + refcount_inc(&pin->refcount); + xa_set_mark(&dpll_pin_xa, pin->idx, DPLL_PIN_REGISTERED); + dpll_pin_notify(dpll, pin, DPLL_A_PIN_IDX); + } + mutex_unlock(&dpll->lock); + + return ret; +rclk_free: + kfree(pin->rclk_dev_name); + mutex_lock(&dpll->lock); + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_on_dpll_register); + +static int dpll_pin_ref_pin_add(struct dpll_pin *pin, struct dpll_pin *parent, + struct dpll_pin_ops *ops, void *priv) +{ + struct dpll_pin_ref *ref, *pos; + unsigned long index; + u32 idx; + int ret; + + ref = kzalloc(sizeof(struct dpll_pin_ref), GFP_KERNEL); + if (!ref) + return -ENOMEM; + ref->pin = parent; + ref->ops = ops; + ref->priv = priv; + if (!xa_empty(&pin->pin_refs)) { + xa_for_each(&pin->pin_refs, index, pos) { + if (pos->pin == ref->pin) + return -EEXIST; + } + } + + ret = xa_alloc(&pin->pin_refs, &idx, ref, xa_limit_16b, GFP_KERNEL); + if (!ret) + refcount_inc(&pin->refcount); + + return ret; +} + +int +dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device) +{ + int ret; + + if (WARN_ON(!pin || !parent)) + return -EINVAL; + if (WARN_ON(parent->prop.type != DPLL_PIN_TYPE_MUX)) + return -EPERM; + + mutex_lock(&pin->lock); + ret = dpll_pin_ref_pin_add(pin, parent, ops, priv); + mutex_unlock(&pin->lock); + if (!ret) { + struct dpll_pin_ref *ref; + unsigned long index; + + xa_for_each(&parent->dpll_refs, index, ref) { + dpll_pin_parent_notify(ref->dpll, pin, parent, + DPLL_A_PIN_IDX); + } + } + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_on_pin_register); + +struct dpll_pin *dpll_pin_get_by_idx_from_xa(struct xarray *xa_pins, u32 idx) +{ + struct dpll_pin *pos; + unsigned long index; + + xa_for_each_marked(xa_pins, index, pos, DPLL_PIN_REGISTERED) { + if (pos->idx == idx) + return pos; + } + + return NULL; +} + +/** + * dpll_pin_get_by_idx - find a pin by its index + * @dpll: dpll device pointer + * @idx: index of pin + * + * Allows multiple driver instances using one physical DPLL to find + * and share pin already registered with existing dpll device. + * + * Return: pointer if pin was found, NULL otherwise. + */ +struct dpll_pin *dpll_pin_get_by_idx(struct dpll_device *dpll, u32 idx) +{ + return dpll_pin_get_by_idx_from_xa(&dpll->pins, idx); +} + +int dpll_pin_deregister(struct dpll_device *dpll, struct dpll_pin *pin) +{ + int ret = 0; + + if (xa_empty(&dpll->pins)) + return -ENOENT; + + mutex_lock(&dpll->lock); + ret = dpll_xa_pin_del(&dpll->pins, pin); + if (!ret) + dpll_pin_ref_dpll_del(pin, dpll); + mutex_unlock(&dpll->lock); + if (!ret) + dpll_pin_notify(dpll, pin, DPLL_A_PIN_IDX); + + return ret; +} +EXPORT_SYMBOL_GPL(dpll_pin_deregister); + +struct dpll_pin_ref +*dpll_pin_find_dpll_ref(const struct dpll_device *dpll, + const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long index; + + xa_for_each((struct xarray *)&pin->dpll_refs, index, ref) { + if (ref->dpll != dpll) + continue; + else + return ref; + } + + return NULL; +} + +struct dpll_pin_ref +*dpll_pin_find_pin_ref(const struct dpll_pin *parent, + const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref; + unsigned long index; + + xa_for_each((struct xarray *)&pin->pin_refs, index, ref) { + if (ref->pin != parent) + continue; + else + return ref; + } + + return NULL; +} + +void *dpll_priv(const struct dpll_device *dpll) +{ + return dpll->priv; +} +EXPORT_SYMBOL_GPL(dpll_priv); + +void *dpll_pin_priv(const struct dpll_device *dpll, const struct dpll_pin *pin) +{ + struct dpll_pin_ref *ref = dpll_pin_find_dpll_ref(dpll, pin); + + if (!ref) + return NULL; + + return ref->priv; +} +EXPORT_SYMBOL_GPL(dpll_pin_priv); + +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..767173fd90bf --- /dev/null +++ b/drivers/dpll/dpll_core.h @@ -0,0 +1,100 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#ifndef __DPLL_CORE_H__ +#define __DPLL_CORE_H__ + +#include +#include +#include "dpll_netlink.h" + +#define DPLL_REGISTERED XA_MARK_1 +#define DPLL_PIN_REGISTERED XA_MARK_1 + +/** + * struct dpll_device - structure for a DPLL device + * @id: unique id number for each device + * @dev: struct device for this dpll device + * @parent: parent device + * @module: module of creator + * @ops: operations this &dpll_device supports + * @lock: mutex to serialize operations + * @type: type of a dpll + * @priv: pointer to private information of owner + * @pins: list of pointers to pins registered with this dpll + * @clock_id: unique identifier (clock_id) of a dpll + * @dev_driver_idx: provided by driver for + * @mode_supported_mask: mask of supported modes + * @refcount: refcount + **/ +struct dpll_device { + u32 id; + u32 dev_driver_id; + struct device dev; + struct device *parent; + struct module *module; + struct dpll_device_ops *ops; + struct mutex lock; + enum dpll_type type; + void *priv; + struct xarray pins; + u64 clock_id; + unsigned long mode_supported_mask; + refcount_t refcount; +}; + +/** + * struct dpll_pin - structure for a dpll pin + * @idx: unique idx given by alloc on global pin's XA + * @dev_driver_id: id given by dev driver + * @clock_id: clock_id of creator + * @type: type of the pin + * @module: module of creator + * @dpll_refs: hold referencees to dplls that pin is registered with + * @pin_refs: hold references to pins that pin is registered with + * @prop: properties given by registerer + * @rclk_dev_name: holds name of device when pin can recover clock from it + * @refcount: refcount + * @lock: access lock + **/ +struct dpll_pin { + u32 idx; + u32 dev_driver_id; + u64 clock_id; + struct module *module; + struct xarray dpll_refs; + struct xarray pin_refs; + struct dpll_pin_properties prop; + char *rclk_dev_name; + refcount_t refcount; + struct mutex lock; +}; + +struct dpll_pin_ref { + union { + struct dpll_device *dpll; + struct dpll_pin *pin; + }; + struct dpll_pin_ops *ops; + void *priv; +}; + +struct dpll_device *dpll_device_get_by_id(int id); +struct dpll_device *dpll_device_get_by_name(const char *bus_name, + const char *dev_name); +void dpll_device_unregister(struct dpll_device *dpll); +struct dpll_pin *dpll_pin_get_by_idx(struct dpll_device *dpll, u32 idx); +struct dpll_pin_ref +*dpll_pin_find_dpll_ref(const struct dpll_device *dpll, + const struct dpll_pin *pin); +struct dpll_pin_ref +*dpll_pin_find_pin_ref(const struct dpll_pin *parent, + const struct dpll_pin *pin); + +extern struct xarray dpll_device_xa; +extern struct xarray dpll_pin_xa; +extern struct mutex dpll_device_xa_lock; +extern struct mutex dpll_pin_xa_lock; +#endif diff --git a/drivers/dpll/dpll_netlink.c b/drivers/dpll/dpll_netlink.c new file mode 100644 index 000000000000..3c88c3c6d584 --- /dev/null +++ b/drivers/dpll/dpll_netlink.c @@ -0,0 +1,898 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Generic netlink for DPLL management framework + * + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + * + */ +#include +#include +#include +#include "dpll_core.h" +#include "dpll_nl.h" +#include + +static int +dpll_msg_add_dev_handle(struct sk_buff *msg, const struct dpll_device *dpll) +{ + if (nla_put_u32(msg, DPLL_A_ID, dpll->id)) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_BUS_NAME, dev_bus_name(&dpll->dev))) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_DEV_NAME, dev_name(&dpll->dev))) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_mode(struct sk_buff *msg, const struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + enum dpll_mode mode; + + if (WARN_ON(!dpll->ops->mode_get)) + return -EOPNOTSUPP; + if (dpll->ops->mode_get(dpll, &mode, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_MODE, mode)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_source_pin_idx(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + u32 source_pin_idx; + + if (WARN_ON(!dpll->ops->source_pin_idx_get)) + return -EOPNOTSUPP; + if (dpll->ops->source_pin_idx_get(dpll, &source_pin_idx, extack)) + return -EFAULT; + if (nla_put_u32(msg, DPLL_A_SOURCE_PIN_IDX, source_pin_idx)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_lock_status(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + enum dpll_lock_status status; + + if (WARN_ON(!dpll->ops->lock_status_get)) + return -EOPNOTSUPP; + if (dpll->ops->lock_status_get(dpll, &status, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_LOCK_STATUS, status)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_temp(struct sk_buff *msg, struct dpll_device *dpll, + struct netlink_ext_ack *extack) +{ + s32 temp; + + if (!dpll->ops->temp_get) + return -EOPNOTSUPP; + if (dpll->ops->temp_get(dpll, &temp, extack)) + return -EFAULT; + if (nla_put_s32(msg, DPLL_A_TEMP, temp)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_msg_add_pin_prio(struct sk_buff *msg, const struct dpll_device *dpll, + const struct dpll_pin *pin, struct dpll_pin_ops *ops, + struct netlink_ext_ack *extack) +{ + u32 prio; + + if (!ops->prio_get) + return -EOPNOTSUPP; + if (ops->prio_get(pin, dpll, &prio, extack)) + return -EFAULT; + if (nla_put_u8(msg, DPLL_A_PIN_PRIO, prio)) + return -EMSGSIZE; + + return 0; +} + +static u32 dpll_pin_freq_value[] = { + [DPLL_PIN_FREQ_SUPP_1_HZ] = DPLL_PIN_FREQ_1_HZ, + [DPLL_PIN_FREQ_SUPP_10_MHZ] = DPLL_PIN_FREQ_10_MHZ, +}; + +static int +dpll_msg_add_pin_freq(struct sk_buff *msg, const struct dpll_pin *pin, + struct netlink_ext_ack *extack, bool dump_any_freq) +{ + enum dpll_pin_freq_supp fs; + struct dpll_pin_ref *ref; + unsigned long i; + u32 freq; + + xa_for_each((struct xarray *)&pin->dpll_refs, i, ref) { + if (ref && ref->ops && ref->dpll) + break; + } + + if (!ref->ops->frequency_get) + return -EOPNOTSUPP; + if (ref->ops->frequency_get(pin, ref->dpll, &freq, extack)) + return -EFAULT; + if (nla_put_u32(msg, DPLL_A_PIN_FREQUENCY, freq)) + return -EMSGSIZE; + if (!dump_any_freq) + return 0; + + for (fs = DPLL_PIN_FREQ_SUPP_UNSPEC; fs < DPLL_PIN_FREQ_SUPP_MAX; fs++) + if (test_bit(fs, &pin->prop.freq_supported)) + if (nla_put_u32(msg, DPLL_A_PIN_FREQUENCY_SUPPORTED, + dpll_pin_freq_value[fs])) + return -EMSGSIZE; + if (pin->prop.any_freq_min && pin->prop.any_freq_max) { + if (nla_put_u32(msg, DPLL_A_PIN_ANY_FREQUENCY_MIN, + pin->prop.any_freq_min)) + return -EMSGSIZE; + if (nla_put_u32(msg, DPLL_A_PIN_ANY_FREQUENCY_MAX, + pin->prop.any_freq_max)) + return -EMSGSIZE; + } + + return 0; +} + +static int +dpll_msg_add_pin_parents(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + struct dpll_pin_ref *ref = NULL; + enum dpll_pin_state state; + struct nlattr *nest; + unsigned long index; + int ret; + + xa_for_each(&pin->pin_refs, index, ref) { + if (WARN_ON(!ref->ops->state_on_pin_get)) + return -EFAULT; + ret = ref->ops->state_on_pin_get(pin, ref->pin, &state, + extack); + if (ret) + return -EFAULT; + nest = nla_nest_start(msg, DPLL_A_PIN_PARENT); + if (!nest) + return -EMSGSIZE; + if (nla_put_u32(msg, DPLL_A_PIN_IDX, ref->pin->dev_driver_id)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + if (nla_put_u8(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) +{ + enum dpll_pin_state state; + struct dpll_pin_ref *ref; + struct nlattr *attr; + unsigned long index; + int ret; + + xa_for_each(&pin->dpll_refs, index, ref) { + struct dpll_device *dpll = ref->dpll; + struct dpll_pin_ops *ops = ref->ops; + + if (WARN_ON(!ref->ops->state_on_dpll_get)) + return -EFAULT; + ret = ops->state_on_dpll_get(pin, dpll, &state, extack); + if (ret) + return -EFAULT; + attr = nla_nest_start(msg, DPLL_A_DPLL); + if (!attr) + return -EMSGSIZE; + ret = dpll_msg_add_dev_handle(msg, dpll); + if (ret) + goto nest_cancel; + if (nla_put_u8(msg, DPLL_A_PIN_STATE, state)) { + ret = -EMSGSIZE; + goto nest_cancel; + } + ret = dpll_msg_add_pin_prio(msg, dpll, pin, ops, extack); + if (ret && ret != -EOPNOTSUPP) + goto nest_cancel; + nla_nest_end(msg, attr); + } + + return 0; + +nest_cancel: + nla_nest_end(msg, attr); + return ret; +} + +static int +__dpll_cmd_pin_dump_one(struct sk_buff *msg, struct dpll_pin *pin, + struct netlink_ext_ack *extack) +{ + int ret; + + if (nla_put_u32(msg, DPLL_A_PIN_IDX, pin->dev_driver_id)) + return -EMSGSIZE; + if (nla_put_string(msg, DPLL_A_PIN_DESCRIPTION, pin->prop.description)) + return -EMSGSIZE; + if (nla_put_s32(msg, DPLL_A_PIN_TYPE, pin->prop.type)) + return -EMSGSIZE; + ret = dpll_msg_add_pin_freq(msg, pin, extack, true); + if (ret) + return ret; + if (!xa_empty(&pin->pin_refs)) { + ret = dpll_msg_add_pin_parents(msg, pin, extack); + if (ret) + return ret; + } + if (!xa_empty(&pin->dpll_refs)) { + ret = dpll_msg_add_pin_dplls(msg, pin, extack); + if (ret) + return ret; + } + if (pin->rclk_dev_name) + if (nla_put_string(msg, DPLL_A_PIN_RCLK_DEVICE, + pin->rclk_dev_name)) + return -EMSGSIZE; + + return 0; +} + +static int +dpll_device_dump_one(struct dpll_device *dpll, struct sk_buff *msg, + struct netlink_ext_ack *extack) +{ + enum dpll_mode mode; + int ret; + + ret = dpll_msg_add_dev_handle(msg, dpll); + return ret; + ret = dpll_msg_add_source_pin_idx(msg, dpll, extack); + if (ret) + return ret; + ret = dpll_msg_add_temp(msg, dpll, extack); + if (ret && ret != -EOPNOTSUPP) + 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; + for (mode = DPLL_MODE_UNSPEC + 1; mode <= DPLL_MODE_MAX; mode++) + if (test_bit(mode, &dpll->mode_supported_mask)) + if (nla_put_s32(msg, DPLL_A_MODE_SUPPORTED, mode)) + return -EMSGSIZE; + if (nla_put_64bit(msg, DPLL_A_CLOCK_ID, sizeof(dpll->clock_id), + &dpll->clock_id, 0)) + return -EMSGSIZE; + if (nla_put_s32(msg, DPLL_A_TYPE, dpll->type)) + return -EMSGSIZE; + + return 0; +} + +static bool dpll_pin_is_freq_supported(struct dpll_pin *pin, u32 freq) +{ + enum dpll_pin_freq_supp fs; + + if (freq >= pin->prop.any_freq_min && freq <= pin->prop.any_freq_max) + return true; + for (fs = DPLL_PIN_FREQ_SUPP_UNSPEC; fs < DPLL_PIN_FREQ_SUPP_MAX; fs++) + if (test_bit(fs, &pin->prop.freq_supported)) + if (freq == dpll_pin_freq_value[fs]) + return true; + return false; +} + +static int +dpll_pin_freq_set(struct dpll_pin *pin, struct nlattr *a, + struct netlink_ext_ack *extack) +{ + u32 freq = nla_get_u32(a); + struct dpll_pin_ref *ref; + unsigned long i; + int ret; + + if (!dpll_pin_is_freq_supported(pin, freq)) + return -EINVAL; + + xa_for_each(&pin->dpll_refs, i, ref) { + ret = ref->ops->frequency_set(pin, ref->dpll, freq, extack); + if (ret) + return -EFAULT; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_FREQUENCY); + } + + return 0; +} + +static int +dpll_pin_parent_state_set(struct dpll_device *dpll, struct dpll_pin *pin, + struct nlattr *nested, struct netlink_ext_ack *extack) +{ + enum dpll_pin_state state; + struct dpll_pin_ref *ref; + struct dpll_pin *parent; + struct nlattr *a; + unsigned long i; + u32 parent_idx; + int ret, rem; + + if (!test_bit(DPLL_PIN_CAPS_STATE_CAN_CHANGE, &pin->prop.capabilities)) + return -EOPNOTSUPP; + nla_for_each_nested(a, nested, rem) { + switch (nla_type(a)) { + case DPLL_A_PIN_STATE: + state = nla_get_u8(a); + break; + case DPLL_A_PIN_PARENT_IDX: + parent_idx = nla_get_u32(a); + break; + default: + break; + } + } + xa_for_each(&pin->pin_refs, i, parent) { + if (parent->dev_driver_id == parent_idx) + break; + } + if (!parent) + return -EINVAL; + ref = dpll_pin_find_pin_ref(parent, pin); + if (!ref) + return -EINVAL; + ret = ref->ops->state_on_pin_set(pin, parent, state, extack); + if (ret) + return -EFAULT; + dpll_pin_parent_notify(dpll, pin, parent, DPLL_A_PIN_STATE); + + return 0; +} + +static int +dpll_pin_dpll_set(struct dpll_pin *pin, struct nlattr *nested, + struct netlink_ext_ack *extack) +{ + struct nlattr *a, *dev_attr = NULL, *bus_attr = NULL; + bool state_change = false, prio_change = false; + enum dpll_pin_state state; + struct dpll_pin_ref *ref; + unsigned long i; + int rem; + u8 prio; + + nla_for_each_nested(a, nested, rem) { + switch (nla_type(a)) { + case DPLL_A_DEV_NAME: + dev_attr = a; + break; + case DPLL_A_BUS_NAME: + bus_attr = a; + break; + case DPLL_A_PIN_STATE: + state = nla_get_u8(a); + state_change = true; + break; + case DPLL_A_PIN_PRIO: + prio = nla_get_u8(a); + prio_change = true; + break; + default: + break; + } + } + xa_for_each(&pin->dpll_refs, i, ref) { + if (!nla_strcmp(bus_attr, dev_bus_name(&ref->dpll->dev)) && + !nla_strcmp(dev_attr, dev_name(&ref->dpll->dev))) + break; + } + if (!ref) + return -EINVAL; + + if (state_change) { + if (!test_bit(DPLL_PIN_CAPS_STATE_CAN_CHANGE, + &pin->prop.capabilities)) + return -EOPNOTSUPP; + if (!ref->ops || !ref->ops->state_on_dpll_set) + return -EOPNOTSUPP; + if (ref->ops->state_on_dpll_set(pin, ref->dpll, state, extack)) + return -EINVAL; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_STATE); + } + + if (prio_change) { + if (!test_bit(DPLL_PIN_CAPS_PRIORITY_CAN_CHANGE, + &pin->prop.capabilities)) + return -EOPNOTSUPP; + if (!ref->ops || !ref->ops->prio_set) + return -EOPNOTSUPP; + if (ref->ops->prio_set(pin, ref->dpll, state, extack)) + return -EINVAL; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_PRIO); + } + + return 0; +} + +static int +dpll_pin_direction_set(struct dpll_pin *pin, struct nlattr *a, + struct netlink_ext_ack *extack) +{ + enum dpll_pin_direction direction = nla_get_u8(a); + struct dpll_pin_ref *ref; + unsigned long i; + + if (!test_bit(DPLL_PIN_CAPS_DIRECTION_CAN_CHANGE, + &pin->prop.capabilities)) + return -EINVAL; + + xa_for_each(&pin->dpll_refs, i, ref) { + if (ref->ops->direction_set(pin, ref->dpll, direction, extack)) + return -EFAULT; + dpll_pin_notify(ref->dpll, pin, DPLL_A_PIN_DIRECTION); + } + + return 0; +} + +static int +dpll_pin_set_from_nlattr(struct dpll_device *dpll, + struct dpll_pin *pin, struct genl_info *info) +{ + int rem, ret = -EINVAL; + struct nlattr *a; + + 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; + case DPLL_A_PIN_PARENT: + ret = dpll_pin_parent_state_set(dpll, pin, a, + info->extack); + if (ret) + return ret; + break; + case DPLL_A_DPLL: + ret = dpll_pin_dpll_set(pin, a, info->extack); + if (ret) + return ret; + break; + case DPLL_A_PIN_DIRECTION: + ret = dpll_pin_direction_set(pin, a, info->extack); + if (ret) + return ret; + break; + default: + break; + } + } + + return ret; +} + +int dpll_nl_pin_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + struct dpll_pin *pin = info->user_ptr[1]; + + return dpll_pin_set_from_nlattr(dpll, pin, info); +} + +int dpll_nl_pin_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_pin *pin = info->user_ptr[1]; + struct nlattr *hdr, *nest; + struct sk_buff *msg; + 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_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + nest = nla_nest_start(msg, DPLL_A_PIN); + if (!nest) + return -EMSGSIZE; + ret = __dpll_cmd_pin_dump_one(msg, pin, info->extack); + if (ret) { + nlmsg_free(msg); + return ret; + } + nla_nest_end(msg, nest); + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); +} + +int dpll_nl_pin_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct nlattr *hdr, *nest; + struct dpll_pin *pin; + unsigned long i; + int ret; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, + &dpll_nl_family, 0, DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + xa_for_each_marked(&dpll_pin_xa, i, pin, DPLL_PIN_REGISTERED) { + nest = nla_nest_start(skb, DPLL_A_PIN); + if (!nest) { + ret = -EMSGSIZE; + break; + } + ret = __dpll_cmd_pin_dump_one(skb, pin, cb->extack); + if (ret) { + nla_nest_cancel(skb, nest); + break; + } + nla_nest_end(skb, nest); + } + + if (ret) + genlmsg_cancel(skb, hdr); + else + genlmsg_end(skb, hdr); + + return ret; +} + +static int +dpll_set_from_nlattr(struct dpll_device *dpll, struct genl_info *info) +{ + struct nlattr *attr; + int rem, ret = 0; + + nla_for_each_attr(attr, genlmsg_data(info->genlhdr), + genlmsg_len(info->genlhdr), rem) { + switch (nla_type(attr)) { + case DPLL_A_MODE: + enum dpll_mode mode = nla_get_u8(attr); + + if (!dpll->ops || !dpll->ops->mode_set) + return -EOPNOTSUPP; + ret = dpll->ops->mode_set(dpll, mode, info->extack); + if (ret) + return ret; + break; + default: + break; + } + } + + return ret; +} + +int dpll_nl_device_set_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + + return dpll_set_from_nlattr(dpll, info); +} + +int dpll_nl_device_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb) +{ + struct nlattr *hdr, *nest; + struct dpll_device *dpll; + unsigned long i; + int ret; + + hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq, + &dpll_nl_family, 0, DPLL_CMD_DEVICE_GET); + if (!hdr) + return -EMSGSIZE; + + mutex_lock(&dpll_device_xa_lock); + xa_for_each(&dpll_device_xa, i, dpll) { + nest = nla_nest_start(skb, DPLL_A_DPLL); + mutex_lock(&dpll->lock); + ret = dpll_device_dump_one(dpll, skb, cb->extack); + mutex_unlock(&dpll->lock); + if (ret) { + nla_nest_cancel(skb, nest); + break; + } + nla_nest_end(skb, nest); + } + mutex_unlock(&dpll_device_xa_lock); + if (ret) + genlmsg_cancel(skb, hdr); + else + genlmsg_end(skb, hdr); + + return ret; +} + +int dpll_nl_device_get_doit(struct sk_buff *skb, struct genl_info *info) +{ + struct dpll_device *dpll = info->user_ptr[0]; + struct nlattr *hdr, *nest; + struct sk_buff *msg; + 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); + if (!hdr) + return -EMSGSIZE; + + nest = nla_nest_start(msg, DPLL_A_DPLL); + mutex_lock(&dpll->lock); + ret = dpll_device_dump_one(dpll, msg, info->extack); + mutex_unlock(&dpll->lock); + if (ret) { + nlmsg_free(msg); + return ret; + } + nla_nest_end(msg, nest); + genlmsg_end(msg, hdr); + + return genlmsg_reply(msg, info); +} + +int dpll_pin_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + int ret = dpll_pre_doit(ops, skb, info); + struct dpll_device *dpll; + struct dpll_pin *pin; + + if (ret) + return ret; + dpll = info->user_ptr[0]; + if (!info->attrs[DPLL_A_PIN_IDX]) + return -EINVAL; + pin = dpll_pin_get_by_idx(dpll, + nla_get_u32(info->attrs[DPLL_A_PIN_IDX])); + if (!pin) + return -EINVAL; + info->user_ptr[1] = pin; + + return 0; +} + +int dpll_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, + struct genl_info *info) +{ + struct dpll_device *dpll_id = NULL, *dpll_name = NULL; + + if (!info->attrs[DPLL_A_ID] && + !(info->attrs[DPLL_A_BUS_NAME] && info->attrs[DPLL_A_DEV_NAME])) + return -EINVAL; + + if (info->attrs[DPLL_A_ID]) { + u32 id = nla_get_u32(info->attrs[DPLL_A_ID]); + + dpll_id = dpll_device_get_by_id(id); + if (!dpll_id) + return -ENODEV; + info->user_ptr[0] = dpll_id; + } + if (info->attrs[DPLL_A_BUS_NAME] && + info->attrs[DPLL_A_DEV_NAME]) { + const char *bus_name = nla_data(info->attrs[DPLL_A_BUS_NAME]); + const char *dev_name = nla_data(info->attrs[DPLL_A_DEV_NAME]); + + dpll_name = dpll_device_get_by_name(bus_name, dev_name); + if (!dpll_name) + return -ENODEV; + + if (dpll_id && dpll_name != dpll_id) + return -EINVAL; + info->user_ptr[0] = dpll_name; + } + + return 0; +} + +static int +dpll_event_device_change(struct sk_buff *msg, struct dpll_device *dpll, + struct dpll_pin *pin, struct dpll_pin *parent, + enum dplla attr) +{ + int ret = dpll_msg_add_dev_handle(msg, dpll); + struct dpll_pin_ref *ref = NULL; + + if (ret) + return ret; + if (pin && nla_put_u32(msg, DPLL_A_PIN_IDX, pin->dev_driver_id)) + return -EMSGSIZE; + + switch (attr) { + case DPLL_A_MODE: + ret = dpll_msg_add_mode(msg, dpll, NULL); + break; + case DPLL_A_SOURCE_PIN_IDX: + ret = dpll_msg_add_source_pin_idx(msg, dpll, NULL); + break; + case DPLL_A_LOCK_STATUS: + ret = dpll_msg_add_lock_status(msg, dpll, NULL); + break; + case DPLL_A_TEMP: + ret = dpll_msg_add_temp(msg, dpll, NULL); + break; + case DPLL_A_PIN_FREQUENCY: + ret = dpll_msg_add_pin_freq(msg, pin, NULL, false); + break; + case DPLL_A_PIN_PRIO: + ref = dpll_pin_find_dpll_ref(dpll, pin); + if (!ref) + return -EFAULT; + ret = dpll_msg_add_pin_prio(msg, dpll, pin, ref->ops, NULL); + break; + case DPLL_A_PIN_STATE: + enum dpll_pin_state state; + + if (parent) { + ref = dpll_pin_find_pin_ref(parent, pin); + if (!ref || !ref->ops || !ref->ops->state_on_pin_get) + return -EOPNOTSUPP; + ret = ref->ops->state_on_pin_get(pin, parent, &state, + NULL); + if (ret) + return ret; + if (nla_put_u32(msg, DPLL_A_PIN_PARENT_IDX, + parent->dev_driver_id)) + return -EMSGSIZE; + } else { + ref = dpll_pin_find_dpll_ref(dpll, pin); + if (!ref || !ref->ops || !ref->ops->state_on_dpll_get) + return -EOPNOTSUPP; + ret = ref->ops->state_on_dpll_get(pin, dpll, &state, + NULL); + if (ret) + return ret; + } + if (nla_put_u8(msg, DPLL_A_PIN_STATE, state)) + return -EMSGSIZE; + break; + default: + break; + } + + return ret; +} + +static int +dpll_send_event_create(enum dpll_event event, struct dpll_device *dpll) +{ + struct sk_buff *msg; + int ret = -EMSGSIZE; + void *hdr; + + 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 out_free_msg; + + ret = dpll_msg_add_dev_handle(msg, dpll); + if (ret) + goto out_cancel_msg; + genlmsg_end(msg, hdr); + genlmsg_multicast(&dpll_nl_family, msg, 0, 0, GFP_KERNEL); + + return 0; + +out_cancel_msg: + genlmsg_cancel(msg, hdr); +out_free_msg: + nlmsg_free(msg); + + return ret; +} + +static int +dpll_send_event_change(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr) +{ + struct sk_buff *msg; + int ret = -EMSGSIZE; + void *hdr; + + msg = genlmsg_new(NLMSG_GOODSIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + hdr = genlmsg_put(msg, 0, 0, &dpll_nl_family, 0, DPLL_EVENT_DEVICE_CHANGE); + if (!hdr) + goto out_free_msg; + + ret = dpll_event_device_change(msg, dpll, pin, parent, attr); + if (ret) + goto out_cancel_msg; + genlmsg_end(msg, hdr); + genlmsg_multicast(&dpll_nl_family, msg, 0, 0, GFP_KERNEL); + + return 0; + +out_cancel_msg: + genlmsg_cancel(msg, hdr); +out_free_msg: + nlmsg_free(msg); + + return ret; +} + +int dpll_notify_device_create(struct dpll_device *dpll) +{ + return dpll_send_event_create(DPLL_EVENT_DEVICE_CREATE, dpll); +} + +int dpll_notify_device_delete(struct dpll_device *dpll) +{ + return dpll_send_event_create(DPLL_EVENT_DEVICE_DELETE, dpll); +} + +int dpll_device_notify(struct dpll_device *dpll, enum dplla attr) +{ + return dpll_send_event_change(dpll, NULL, NULL, attr); +} +EXPORT_SYMBOL_GPL(dpll_device_notify); + +int dpll_pin_notify(struct dpll_device *dpll, struct dpll_pin *pin, + enum dplla attr) +{ + return dpll_send_event_change(dpll, pin, NULL, attr); +} + +int dpll_pin_parent_notify(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr) +{ + return dpll_send_event_change(dpll, pin, parent, attr); +} + +int __init dpll_netlink_init(void) +{ + return genl_register_family(&dpll_nl_family); +} + +void dpll_netlink_finish(void) +{ + genl_unregister_family(&dpll_nl_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..b78a6ba93071 --- /dev/null +++ b/drivers/dpll/dpll_netlink.h @@ -0,0 +1,31 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +/** + * dpll_notify_device_create - notify that the device has been created + * @dpll: registered dpll pointer + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_notify_device_create(struct dpll_device *dpll); + + +/** + * dpll_notify_device_delete - notify that the device has been deleted + * @dpll: registered dpll pointer + * + * Return: 0 if succeeds, error code otherwise. + */ +int dpll_notify_device_delete(struct dpll_device *dpll); + +int dpll_pin_notify(struct dpll_device *dpll, struct dpll_pin *pin, + enum dplla attr); + +int dpll_pin_parent_notify(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin *parent, enum dplla attr); + + +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..fd2b590cc29f --- /dev/null +++ b/include/linux/dpll.h @@ -0,0 +1,251 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2021 Meta Platforms, Inc. and affiliates + */ + +#ifndef __DPLL_H__ +#define __DPLL_H__ + +#include +#include +#include + +struct dpll_device; +struct dpll_pin; + +#define PIN_IDX_INVALID ((u32)ULONG_MAX) + +struct dpll_device_ops { + int (*mode_get)(const struct dpll_device *dpll, enum dpll_mode *mode, + struct netlink_ext_ack *extack); + int (*mode_set)(const struct dpll_device *dpll, + const enum dpll_mode mode, + struct netlink_ext_ack *extack); + bool (*mode_supported)(const struct dpll_device *dpll, + const enum dpll_mode mode, + struct netlink_ext_ack *extack); + int (*source_pin_idx_get)(const struct dpll_device *dpll, + u32 *pin_idx, + struct netlink_ext_ack *extack); + int (*lock_status_get)(const struct dpll_device *dpll, + enum dpll_lock_status *status, + struct netlink_ext_ack *extack); + int (*temp_get)(const struct dpll_device *dpll, s32 *temp, + struct netlink_ext_ack *extack); +}; + +struct dpll_pin_ops { + int (*frequency_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 frequency, + struct netlink_ext_ack *extack); + int (*frequency_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *frequency, struct netlink_ext_ack *extack); + int (*direction_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const enum dpll_pin_direction direction, + struct netlink_ext_ack *extack); + int (*direction_get)(const struct dpll_pin *pin, + enum dpll_pin_direction *direction, + struct netlink_ext_ack *extack); + int (*state_on_pin_get)(const struct dpll_pin *pin, + const struct dpll_pin *parent_pin, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack); + int (*state_on_dpll_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + enum dpll_pin_state *state, + struct netlink_ext_ack *extack); + int (*state_on_pin_set)(const struct dpll_pin *pin, + const struct dpll_pin *parent_pin, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack); + int (*state_on_dpll_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const enum dpll_pin_state state, + struct netlink_ext_ack *extack); + int (*prio_get)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + u32 *prio, struct netlink_ext_ack *extack); + int (*prio_set)(const struct dpll_pin *pin, + const struct dpll_device *dpll, + const u32 prio, struct netlink_ext_ack *extack); +}; + +struct dpll_pin_properties { + const char *description; + enum dpll_pin_type type; + unsigned long caps_supported; + unsigned long freq_supported; + u32 any_freq_min; + u32 any_freq_max; + unsigned long capabilities; +}; + +enum dpll_pin_freq_supp { + DPLL_PIN_FREQ_SUPP_UNSPEC = 0, + DPLL_PIN_FREQ_SUPP_1_HZ, + DPLL_PIN_FREQ_SUPP_10_MHZ, + + __DPLL_PIN_FREQ_SUPP_MAX, + DPLL_PIN_FREQ_SUPP_MAX = (__DPLL_PIN_FREQ_SUPP_MAX - 1) +}; + +/** + * dpll_device_get - find or create dpll_device object + * @clock_id: a system unique number for a device + * @dev_driver_idx: index of dpll device on parent device + * @module: register module + * + * Returns: + * * pointer to initialized dpll - success + * * NULL - memory allocation fail + */ +struct dpll_device +*dpll_device_get(u64 clock_id, u32 dev_driver_id, struct module *module); + +/** + * dpll_device_put - caller drops reference to the device, free resources + * @dpll: dpll device pointer + * + * If all dpll_device_get callers drops their reference, the dpll device + * resources are freed. + */ +void dpll_device_put(struct dpll_device *dpll); + +/** + * dpll_device_register - register device, make it visible in the subsystem. + * @dpll: reference previously allocated with dpll_device_get + * @type: type of dpll + * @priv: private data of registerer + * @owner: device struct of the owner + * + */ +void dpll_device_register(struct dpll_device *dpll, enum dpll_type type, + void *priv, struct device *owner); + +/** + * dpll_device_deregister - deregister registered dpll + * @dpll: pointer to dpll + * + * Unregister the dpll from the subsystem, make it unavailable for netlink + * API users. + */ +void dpll_device_deregister(struct dpll_device *dpll); + +/** + * dpll_priv - get private data + * @dpll: pointer to dpll + * + * Obtain private data pointer passed to dpll subsystem when allocating + * device with ``dpll_device_alloc(..)`` + */ +void *dpll_priv(const struct dpll_device *dpll); + +/** + * dpll_pin_priv - get private data + * @dpll: pointer to dpll + * + * Obtain private pin data pointer passed to dpll subsystem when pin + * was registered with dpll. + */ +void *dpll_pin_priv(const struct dpll_device *dpll, const struct dpll_pin *pin); + +/** + * dpll_pin_get - get reference or create new pin object + * @clock_id: a system unique number of a device + * @dev_driver_idx: index of dpll device on parent device + * @module: register module + * @pin_prop: constant properities of a pin + * + * find existing pin with given clock_id, dev_driver_idx and module, or create new + * and returen its reference. + * + * Returns: + * * pointer to initialized pin - success + * * NULL - memory allocation fail + */ +struct dpll_pin +*dpll_pin_get(u64 clock_id, u32 dev_driver_id, struct module *module, + const struct dpll_pin_properties *pin_prop); + +/** + * dpll_pin_register - register pin with a dpll device + * @dpll: pointer to dpll object to register pin with + * @pin: pointer to allocated pin object being registered with dpll + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops + * @rclk_device: pointer to device struct if pin is used for recovery of a clock + * from that device + * + * Register previously allocated pin object with a dpll device. + * + * Return: + * * 0 - if pin was registered with a parent pin, + * * -ENOMEM - failed to allocate memory, + * * -EEXIST - pin already registered with this dpll, + * * -EBUSY - couldn't allocate id for a pin. + */ +int dpll_pin_on_dpll_register(struct dpll_device *dpll, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device); + +/** + * dpll_pin_deregister - deregister pin from a dpll device + * @dpll: pointer to dpll object to deregister pin from + * @pin: pointer to allocated pin object being deregistered from dpll + * + * Deregister previously registered pin object from a dpll device. + * + * Return: + * * 0 - pin was successfully deregistered from this dpll device, + * * -ENXIO - given pin was not registered with this dpll device, + * * -EINVAL - pin pointer is not valid. + */ +int dpll_pin_deregister(struct dpll_device *dpll, struct dpll_pin *pin); + +/** + * dpll_pin_free - free memory allocated for a pin + * @pin: pointer to allocated pin object being freed + * + * Shared pins must be deregistered from all dpll devices before freeing them, + * otherwise the memory won't be freed. + */ +void dpll_pin_free(struct dpll_pin *pin); + +/** + * dpll_pin_on_pin_register - register a pin to a muxed-type pin + * @parent: parent pin pointer + * @pin: pointer to allocated pin object being registered with a parent pin + * @ops: struct with pin ops callbacks + * @priv: private data pointer passed when calling callback ops + * @rclk_device: pointer to device struct if pin is used for recovery of a clock + * from that device + * + * In case of multiplexed pins, allows registring them under a single + * parent pin. + * + * Return: + * * 0 - if pin was registered with a parent pin, + * * -ENOMEM - failed to allocate memory, + * * -EEXIST - pin already registered with this parent pin, + */ +int dpll_pin_on_pin_register(struct dpll_pin *parent, struct dpll_pin *pin, + struct dpll_pin_ops *ops, void *priv, + struct device *rclk_device); + +/** + * dpll_device_notify - notify on dpll device change + * @dpll: dpll device pointer + * @attr: changed attribute + * + * Broadcast event to the netlink multicast registered listeners. + * + * Return: + * * 0 - success + * * negative - error + */ +int dpll_device_notify(struct dpll_device *dpll, enum dplla attr); + +#endif