Commit 496fd0a2 authored by Jiri Pirko's avatar Jiri Pirko Committed by David S. Miller
Browse files

mlx5: Implement SyncE support using DPLL infrastructure



Implement SyncE support using newly introduced DPLL support.
Make sure that each PFs/VFs/SFs probed with appropriate capability
will spawn a dpll auxiliary device and register appropriate dpll device
and pin instances.

Signed-off-by: default avatarJiri Pirko <jiri@nvidia.com>
Signed-off-by: default avatarArkadiusz Kubalewski <arkadiusz.kubalewski@intel.com>
Signed-off-by: default avatarVadim Fedorenko <vadim.fedorenko@linux.dev>
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parent 09eeb3ae
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -189,3 +189,11 @@ config MLX5_SF_MANAGER
	port is managed through devlink.  A subfunction supports RDMA, netdevice
	and vdpa device. It is similar to a SRIOV VF but it doesn't require
	SRIOV support.

config MLX5_DPLL
	tristate "Mellanox 5th generation network adapters (ConnectX series) DPLL support"
	depends on NETDEVICES && ETHERNET && PCI && MLX5_CORE
	select DPLL
	help
	  DPLL support in Mellanox Technologies ConnectX NICs.
+3 −0
Original line number Diff line number Diff line
@@ -128,3 +128,6 @@ mlx5_core-$(CONFIG_MLX5_SF) += sf/vhca_event.o sf/dev/dev.o sf/dev/driver.o irq_
# SF manager
#
mlx5_core-$(CONFIG_MLX5_SF_MANAGER) += sf/cmd.o sf/hw_table.o sf/devlink.o

obj-$(CONFIG_MLX5_DPLL) += mlx5_dpll.o
mlx5_dpll-y :=	dpll.o
+17 −0
Original line number Diff line number Diff line
@@ -206,6 +206,19 @@ static bool is_ib_enabled(struct mlx5_core_dev *dev)
	return err ? false : val.vbool;
}

static bool is_dpll_supported(struct mlx5_core_dev *dev)
{
	if (!IS_ENABLED(CONFIG_MLX5_DPLL))
		return false;

	if (!MLX5_CAP_MCAM_REG2(dev, synce_registers)) {
		mlx5_core_warn(dev, "Missing SyncE capability\n");
		return false;
	}

	return true;
}

enum {
	MLX5_INTERFACE_PROTOCOL_ETH,
	MLX5_INTERFACE_PROTOCOL_ETH_REP,
@@ -215,6 +228,8 @@ enum {
	MLX5_INTERFACE_PROTOCOL_MPIB,

	MLX5_INTERFACE_PROTOCOL_VNET,

	MLX5_INTERFACE_PROTOCOL_DPLL,
};

static const struct mlx5_adev_device {
@@ -237,6 +252,8 @@ static const struct mlx5_adev_device {
					   .is_supported = &is_ib_rep_supported },
	[MLX5_INTERFACE_PROTOCOL_MPIB] = { .suffix = "multiport",
					   .is_supported = &is_mp_supported },
	[MLX5_INTERFACE_PROTOCOL_DPLL] = { .suffix = "dpll",
					   .is_supported = &is_dpll_supported },
};

int mlx5_adev_idx_alloc(void)
+432 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0 OR Linux-OpenIB
/* Copyright (c) 2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. */

#include <linux/dpll.h>
#include <linux/mlx5/driver.h>

/* This structure represents a reference to DPLL, one is created
 * per mdev instance.
 */
struct mlx5_dpll {
	struct dpll_device *dpll;
	struct dpll_pin *dpll_pin;
	struct mlx5_core_dev *mdev;
	struct workqueue_struct *wq;
	struct delayed_work work;
	struct {
		bool valid;
		enum dpll_lock_status lock_status;
		enum dpll_pin_state pin_state;
	} last;
	struct notifier_block mdev_nb;
	struct net_device *tracking_netdev;
};

static int mlx5_dpll_clock_id_get(struct mlx5_core_dev *mdev, u64 *clock_id)
{
	u32 out[MLX5_ST_SZ_DW(msecq_reg)] = {};
	u32 in[MLX5_ST_SZ_DW(msecq_reg)] = {};
	int err;

	err = mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out),
				   MLX5_REG_MSECQ, 0, 0);
	if (err)
		return err;
	*clock_id = MLX5_GET64(msecq_reg, out, local_clock_identity);
	return 0;
}

static int
mlx5_dpll_synce_status_get(struct mlx5_core_dev *mdev,
			   enum mlx5_msees_admin_status *admin_status,
			   enum mlx5_msees_oper_status *oper_status,
			   bool *ho_acq)
{
	u32 out[MLX5_ST_SZ_DW(msees_reg)] = {};
	u32 in[MLX5_ST_SZ_DW(msees_reg)] = {};
	int err;

	err = mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out),
				   MLX5_REG_MSEES, 0, 0);
	if (err)
		return err;
	if (admin_status)
		*admin_status = MLX5_GET(msees_reg, out, admin_status);
	*oper_status = MLX5_GET(msees_reg, out, oper_status);
	if (ho_acq)
		*ho_acq = MLX5_GET(msees_reg, out, ho_acq);
	return 0;
}

static int
mlx5_dpll_synce_status_set(struct mlx5_core_dev *mdev,
			   enum mlx5_msees_admin_status admin_status)
{
	u32 out[MLX5_ST_SZ_DW(msees_reg)] = {};
	u32 in[MLX5_ST_SZ_DW(msees_reg)] = {};

	MLX5_SET(msees_reg, in, field_select,
		 MLX5_MSEES_FIELD_SELECT_ENABLE |
		 MLX5_MSEES_FIELD_SELECT_ADMIN_STATUS);
	MLX5_SET(msees_reg, in, admin_status, admin_status);
	return mlx5_core_access_reg(mdev, in, sizeof(in), out, sizeof(out),
				    MLX5_REG_MSEES, 0, 1);
}

static enum dpll_lock_status
mlx5_dpll_lock_status_get(enum mlx5_msees_oper_status oper_status, bool ho_acq)
{
	switch (oper_status) {
	case MLX5_MSEES_OPER_STATUS_SELF_TRACK:
		fallthrough;
	case MLX5_MSEES_OPER_STATUS_OTHER_TRACK:
		return ho_acq ? DPLL_LOCK_STATUS_LOCKED_HO_ACQ :
				DPLL_LOCK_STATUS_LOCKED;
	case MLX5_MSEES_OPER_STATUS_HOLDOVER:
		fallthrough;
	case MLX5_MSEES_OPER_STATUS_FAIL_HOLDOVER:
		return DPLL_LOCK_STATUS_HOLDOVER;
	default:
		return DPLL_LOCK_STATUS_UNLOCKED;
	}
}

static enum dpll_pin_state
mlx5_dpll_pin_state_get(enum mlx5_msees_admin_status admin_status,
			enum mlx5_msees_oper_status oper_status)
{
	return (admin_status == MLX5_MSEES_ADMIN_STATUS_TRACK &&
		(oper_status == MLX5_MSEES_OPER_STATUS_SELF_TRACK ||
		 oper_status == MLX5_MSEES_OPER_STATUS_OTHER_TRACK)) ?
	       DPLL_PIN_STATE_CONNECTED : DPLL_PIN_STATE_DISCONNECTED;
}

static int mlx5_dpll_device_lock_status_get(const struct dpll_device *dpll,
					    void *priv,
					    enum dpll_lock_status *status,
					    struct netlink_ext_ack *extack)
{
	enum mlx5_msees_oper_status oper_status;
	struct mlx5_dpll *mdpll = priv;
	bool ho_acq;
	int err;

	err = mlx5_dpll_synce_status_get(mdpll->mdev, NULL,
					 &oper_status, &ho_acq);
	if (err)
		return err;

	*status = mlx5_dpll_lock_status_get(oper_status, ho_acq);
	return 0;
}

static int mlx5_dpll_device_mode_get(const struct dpll_device *dpll,
				     void *priv,
				     u32 *mode, struct netlink_ext_ack *extack)
{
	*mode = DPLL_MODE_MANUAL;
	return 0;
}

static bool mlx5_dpll_device_mode_supported(const struct dpll_device *dpll,
					    void *priv,
					    enum dpll_mode mode,
					    struct netlink_ext_ack *extack)
{
	return mode == DPLL_MODE_MANUAL;
}

static const struct dpll_device_ops mlx5_dpll_device_ops = {
	.lock_status_get = mlx5_dpll_device_lock_status_get,
	.mode_get = mlx5_dpll_device_mode_get,
	.mode_supported = mlx5_dpll_device_mode_supported,
};

static int mlx5_dpll_pin_direction_get(const struct dpll_pin *pin,
				       void *pin_priv,
				       const struct dpll_device *dpll,
				       void *dpll_priv,
				       enum dpll_pin_direction *direction,
				       struct netlink_ext_ack *extack)
{
	*direction = DPLL_PIN_DIRECTION_INPUT;
	return 0;
}

static int mlx5_dpll_state_on_dpll_get(const struct dpll_pin *pin,
				       void *pin_priv,
				       const struct dpll_device *dpll,
				       void *dpll_priv,
				       enum dpll_pin_state *state,
				       struct netlink_ext_ack *extack)
{
	enum mlx5_msees_admin_status admin_status;
	enum mlx5_msees_oper_status oper_status;
	struct mlx5_dpll *mdpll = pin_priv;
	int err;

	err = mlx5_dpll_synce_status_get(mdpll->mdev, &admin_status,
					 &oper_status, NULL);
	if (err)
		return err;
	*state = mlx5_dpll_pin_state_get(admin_status, oper_status);
	return 0;
}

static int mlx5_dpll_state_on_dpll_set(const struct dpll_pin *pin,
				       void *pin_priv,
				       const struct dpll_device *dpll,
				       void *dpll_priv,
				       enum dpll_pin_state state,
				       struct netlink_ext_ack *extack)
{
	struct mlx5_dpll *mdpll = pin_priv;

	return mlx5_dpll_synce_status_set(mdpll->mdev,
					  state == DPLL_PIN_STATE_CONNECTED ?
					  MLX5_MSEES_ADMIN_STATUS_TRACK :
					  MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
}

static const struct dpll_pin_ops mlx5_dpll_pins_ops = {
	.direction_get = mlx5_dpll_pin_direction_get,
	.state_on_dpll_get = mlx5_dpll_state_on_dpll_get,
	.state_on_dpll_set = mlx5_dpll_state_on_dpll_set,
};

static const struct dpll_pin_properties mlx5_dpll_pin_properties = {
	.type = DPLL_PIN_TYPE_SYNCE_ETH_PORT,
	.capabilities = DPLL_PIN_CAPABILITIES_STATE_CAN_CHANGE,
};

#define MLX5_DPLL_PERIODIC_WORK_INTERVAL 500 /* ms */

static void mlx5_dpll_periodic_work_queue(struct mlx5_dpll *mdpll)
{
	queue_delayed_work(mdpll->wq, &mdpll->work,
			   msecs_to_jiffies(MLX5_DPLL_PERIODIC_WORK_INTERVAL));
}

static void mlx5_dpll_periodic_work(struct work_struct *work)
{
	struct mlx5_dpll *mdpll = container_of(work, struct mlx5_dpll,
					       work.work);
	enum mlx5_msees_admin_status admin_status;
	enum mlx5_msees_oper_status oper_status;
	enum dpll_lock_status lock_status;
	enum dpll_pin_state pin_state;
	bool ho_acq;
	int err;

	err = mlx5_dpll_synce_status_get(mdpll->mdev, &admin_status,
					 &oper_status, &ho_acq);
	if (err)
		goto err_out;
	lock_status = mlx5_dpll_lock_status_get(oper_status, ho_acq);
	pin_state = mlx5_dpll_pin_state_get(admin_status, oper_status);

	if (!mdpll->last.valid)
		goto invalid_out;

	if (mdpll->last.lock_status != lock_status)
		dpll_device_change_ntf(mdpll->dpll);
	if (mdpll->last.pin_state != pin_state)
		dpll_pin_change_ntf(mdpll->dpll_pin);

invalid_out:
	mdpll->last.lock_status = lock_status;
	mdpll->last.pin_state = pin_state;
	mdpll->last.valid = true;
err_out:
	mlx5_dpll_periodic_work_queue(mdpll);
}

static void mlx5_dpll_netdev_dpll_pin_set(struct mlx5_dpll *mdpll,
					  struct net_device *netdev)
{
	if (mdpll->tracking_netdev)
		return;
	netdev_dpll_pin_set(netdev, mdpll->dpll_pin);
	mdpll->tracking_netdev = netdev;
}

static void mlx5_dpll_netdev_dpll_pin_clear(struct mlx5_dpll *mdpll)
{
	if (!mdpll->tracking_netdev)
		return;
	netdev_dpll_pin_clear(mdpll->tracking_netdev);
	mdpll->tracking_netdev = NULL;
}

static int mlx5_dpll_mdev_notifier_event(struct notifier_block *nb,
					 unsigned long event, void *data)
{
	struct mlx5_dpll *mdpll = container_of(nb, struct mlx5_dpll, mdev_nb);
	struct net_device *netdev = data;

	switch (event) {
	case MLX5_DRIVER_EVENT_UPLINK_NETDEV:
		if (netdev)
			mlx5_dpll_netdev_dpll_pin_set(mdpll, netdev);
		else
			mlx5_dpll_netdev_dpll_pin_clear(mdpll);
		break;
	default:
		return NOTIFY_DONE;
	}

	return NOTIFY_OK;
}

static void mlx5_dpll_mdev_netdev_track(struct mlx5_dpll *mdpll,
					struct mlx5_core_dev *mdev)
{
	mdpll->mdev_nb.notifier_call = mlx5_dpll_mdev_notifier_event;
	mlx5_blocking_notifier_register(mdev, &mdpll->mdev_nb);
	mlx5_core_uplink_netdev_event_replay(mdev);
}

static void mlx5_dpll_mdev_netdev_untrack(struct mlx5_dpll *mdpll,
					  struct mlx5_core_dev *mdev)
{
	mlx5_blocking_notifier_unregister(mdev, &mdpll->mdev_nb);
	mlx5_dpll_netdev_dpll_pin_clear(mdpll);
}

static int mlx5_dpll_probe(struct auxiliary_device *adev,
			   const struct auxiliary_device_id *id)
{
	struct mlx5_adev *edev = container_of(adev, struct mlx5_adev, adev);
	struct mlx5_core_dev *mdev = edev->mdev;
	struct mlx5_dpll *mdpll;
	u64 clock_id;
	int err;

	err = mlx5_dpll_synce_status_set(mdev,
					 MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
	if (err)
		return err;

	err = mlx5_dpll_clock_id_get(mdev, &clock_id);
	if (err)
		return err;

	mdpll = kzalloc(sizeof(*mdpll), GFP_KERNEL);
	if (!mdpll)
		return -ENOMEM;
	mdpll->mdev = mdev;
	auxiliary_set_drvdata(adev, mdpll);

	/* Multiple mdev instances might share one DPLL device. */
	mdpll->dpll = dpll_device_get(clock_id, 0, THIS_MODULE);
	if (IS_ERR(mdpll->dpll)) {
		err = PTR_ERR(mdpll->dpll);
		goto err_free_mdpll;
	}

	err = dpll_device_register(mdpll->dpll, DPLL_TYPE_EEC,
				   &mlx5_dpll_device_ops, mdpll);
	if (err)
		goto err_put_dpll_device;

	/* Multiple mdev instances might share one DPLL pin. */
	mdpll->dpll_pin = dpll_pin_get(clock_id, mlx5_get_dev_index(mdev),
				       THIS_MODULE, &mlx5_dpll_pin_properties);
	if (IS_ERR(mdpll->dpll_pin)) {
		err = PTR_ERR(mdpll->dpll_pin);
		goto err_unregister_dpll_device;
	}

	err = dpll_pin_register(mdpll->dpll, mdpll->dpll_pin,
				&mlx5_dpll_pins_ops, mdpll);
	if (err)
		goto err_put_dpll_pin;

	mdpll->wq = create_singlethread_workqueue("mlx5_dpll");
	if (!mdpll->wq) {
		err = -ENOMEM;
		goto err_unregister_dpll_pin;
	}

	mlx5_dpll_mdev_netdev_track(mdpll, mdev);

	INIT_DELAYED_WORK(&mdpll->work, &mlx5_dpll_periodic_work);
	mlx5_dpll_periodic_work_queue(mdpll);

	return 0;

err_unregister_dpll_pin:
	dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin,
			    &mlx5_dpll_pins_ops, mdpll);
err_put_dpll_pin:
	dpll_pin_put(mdpll->dpll_pin);
err_unregister_dpll_device:
	dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll);
err_put_dpll_device:
	dpll_device_put(mdpll->dpll);
err_free_mdpll:
	kfree(mdpll);
	return err;
}

static void mlx5_dpll_remove(struct auxiliary_device *adev)
{
	struct mlx5_dpll *mdpll = auxiliary_get_drvdata(adev);
	struct mlx5_core_dev *mdev = mdpll->mdev;

	cancel_delayed_work(&mdpll->work);
	mlx5_dpll_mdev_netdev_untrack(mdpll, mdev);
	destroy_workqueue(mdpll->wq);
	dpll_pin_unregister(mdpll->dpll, mdpll->dpll_pin,
			    &mlx5_dpll_pins_ops, mdpll);
	dpll_pin_put(mdpll->dpll_pin);
	dpll_device_unregister(mdpll->dpll, &mlx5_dpll_device_ops, mdpll);
	dpll_device_put(mdpll->dpll);
	kfree(mdpll);

	mlx5_dpll_synce_status_set(mdev,
				   MLX5_MSEES_ADMIN_STATUS_FREE_RUNNING);
}

static int mlx5_dpll_suspend(struct auxiliary_device *adev, pm_message_t state)
{
	return 0;
}

static int mlx5_dpll_resume(struct auxiliary_device *adev)
{
	return 0;
}

static const struct auxiliary_device_id mlx5_dpll_id_table[] = {
	{ .name = MLX5_ADEV_NAME ".dpll", },
	{},
};

MODULE_DEVICE_TABLE(auxiliary, mlx5_dpll_id_table);

static struct auxiliary_driver mlx5_dpll_driver = {
	.name = "dpll",
	.probe = mlx5_dpll_probe,
	.remove = mlx5_dpll_remove,
	.suspend = mlx5_dpll_suspend,
	.resume = mlx5_dpll_resume,
	.id_table = mlx5_dpll_id_table,
};

static int __init mlx5_dpll_init(void)
{
	return auxiliary_driver_register(&mlx5_dpll_driver);
}

static void __exit mlx5_dpll_exit(void)
{
	auxiliary_driver_unregister(&mlx5_dpll_driver);
}

module_init(mlx5_dpll_init);
module_exit(mlx5_dpll_exit);

MODULE_AUTHOR("Jiri Pirko <jiri@nvidia.com>");
MODULE_DESCRIPTION("Mellanox 5th generation network adapters (ConnectX series) DPLL driver");
MODULE_LICENSE("Dual BSD/GPL");
+2 −0
Original line number Diff line number Diff line
@@ -155,6 +155,8 @@ enum {
	MLX5_REG_MCC		 = 0x9062,
	MLX5_REG_MCDA		 = 0x9063,
	MLX5_REG_MCAM		 = 0x907f,
	MLX5_REG_MSECQ		 = 0x9155,
	MLX5_REG_MSEES		 = 0x9156,
	MLX5_REG_MIRC		 = 0x9162,
	MLX5_REG_SBCAM		 = 0xB01F,
	MLX5_REG_RESOURCE_DUMP   = 0xC000,
Loading