Commit fc0e6db3 authored by Kory Maincent (Dent Project)'s avatar Kory Maincent (Dent Project) Committed by Jakub Kicinski
Browse files

net: pse-pd: Add support for reporting events



Add support for devm_pse_irq_helper() to register PSE interrupts and report
events such as over-current or over-temperature conditions. This follows a
similar approach to the regulator API but also sends notifications using a
dedicated PSE ethtool netlink socket.

Signed-off-by: default avatarKory Maincent (Dent Project) <kory.maincent@bootlin.com>
Link: https://patch.msgid.link/20250617-feature_poe_port_prio-v14-2-78a1a645e2ee@bootlin.com


Signed-off-by: default avatarJakub Kicinski <kuba@kernel.org>
parent fa2f0454
Loading
Loading
Loading
Loading
+34 −0
Original line number Diff line number Diff line
@@ -118,6 +118,17 @@ definitions:
        doc: |
          Hardware timestamp comes from one PHY device
          of the network topology
  -
    name: pse-event
    doc: PSE event list for the PSE controller
    type: flags
    entries:
      -
        name: over-current
        doc: PSE output current is too high
      -
        name: over-temp
        doc: PSE in over temperature state

attribute-sets:
  -
@@ -1555,6 +1566,19 @@ attribute-sets:
        name: hwtstamp-flags
        type: nest
        nested-attributes: bitset
  -
    name: pse-ntf
    attr-cnt-name: --ethtool-a-pse-ntf-cnt
    attributes:
      -
        name: header
        type: nest
        nested-attributes: header
      -
        name: events
        type: uint
        enum: pse-event
        doc: List of events reported by the PSE controller

operations:
  enum-model: directional
@@ -2413,3 +2437,13 @@ operations:
          attributes: *tsconfig
        reply:
          attributes: *tsconfig
    -
      name: pse-ntf
      doc: Notification for PSE events.

      attribute-set: pse-ntf

      event:
        attributes:
          - header
          - events
+19 −0
Original line number Diff line number Diff line
@@ -290,6 +290,7 @@ Kernel to userspace:
  ``ETHTOOL_MSG_PHY_NTF``                  Ethernet PHY information change
  ``ETHTOOL_MSG_TSCONFIG_GET_REPLY``       hw timestamping configuration
  ``ETHTOOL_MSG_TSCONFIG_SET_REPLY``       new hw timestamping configuration
  ``ETHTOOL_MSG_PSE_NTF``                  PSE events notification
  ======================================== =================================

``GET`` requests are sent by userspace applications to retrieve device
@@ -1896,6 +1897,24 @@ various existing products that document power consumption in watts rather than
classes. If power limit configuration based on classes is needed, the
conversion can be done in user space, for example by ethtool.

PSE_NTF
=======

Notify PSE events.

Notification contents:

  ===============================  ======  ========================
  ``ETHTOOL_A_PSE_HEADER``         nested  request header
  ``ETHTOOL_A_PSE_EVENTS``         bitset  PSE events
  ===============================  ======  ========================

When set, the optional ``ETHTOOL_A_PSE_EVENTS`` attribute identifies the
PSE events.

.. kernel-doc:: include/uapi/linux/ethtool_netlink_generated.h
    :identifiers: ethtool_pse_event

RSS_GET
=======

+179 −0
Original line number Diff line number Diff line
@@ -7,10 +7,14 @@

#include <linux/device.h>
#include <linux/ethtool.h>
#include <linux/ethtool_netlink.h>
#include <linux/of.h>
#include <linux/phy.h>
#include <linux/pse-pd/pse.h>
#include <linux/regulator/driver.h>
#include <linux/regulator/machine.h>
#include <linux/rtnetlink.h>
#include <net/net_trackers.h>

static DEFINE_MUTEX(pse_list_mutex);
static LIST_HEAD(pse_controller_list);
@@ -210,6 +214,48 @@ static int of_load_pse_pis(struct pse_controller_dev *pcdev)
	return ret;
}

/**
 * pse_control_find_net_by_id - Find net attached to the pse control id
 * @pcdev: a pointer to the PSE
 * @id: index of the PSE control
 *
 * Return: pse_control pointer or NULL. The device returned has had a
 *	   reference added and the pointer is safe until the user calls
 *	   pse_control_put() to indicate they have finished with it.
 */
static struct pse_control *
pse_control_find_by_id(struct pse_controller_dev *pcdev, int id)
{
	struct pse_control *psec;

	mutex_lock(&pse_list_mutex);
	list_for_each_entry(psec, &pcdev->pse_control_head, list) {
		if (psec->id == id) {
			kref_get(&psec->refcnt);
			mutex_unlock(&pse_list_mutex);
			return psec;
		}
	}
	mutex_unlock(&pse_list_mutex);
	return NULL;
}

/**
 * pse_control_get_netdev - Return netdev associated to a PSE control
 * @psec: PSE control pointer
 *
 * Return: netdev pointer or NULL
 */
static struct net_device *pse_control_get_netdev(struct pse_control *psec)
{
	ASSERT_RTNL();

	if (!psec || !psec->attached_phydev)
		return NULL;

	return psec->attached_phydev->attached_dev;
}

static int pse_pi_is_enabled(struct regulator_dev *rdev)
{
	struct pse_controller_dev *pcdev = rdev_get_drvdata(rdev);
@@ -559,6 +605,139 @@ int devm_pse_controller_register(struct device *dev,
}
EXPORT_SYMBOL_GPL(devm_pse_controller_register);

struct pse_irq {
	struct pse_controller_dev *pcdev;
	struct pse_irq_desc desc;
	unsigned long *notifs;
};

/**
 * pse_to_regulator_notifs - Convert PSE notifications to Regulator
 *			     notifications
 * @notifs: PSE notifications
 *
 * Return: Regulator notifications
 */
static unsigned long pse_to_regulator_notifs(unsigned long notifs)
{
	unsigned long rnotifs = 0;

	if (notifs & ETHTOOL_PSE_EVENT_OVER_CURRENT)
		rnotifs |= REGULATOR_EVENT_OVER_CURRENT;
	if (notifs & ETHTOOL_PSE_EVENT_OVER_TEMP)
		rnotifs |= REGULATOR_EVENT_OVER_TEMP;

	return rnotifs;
}

/**
 * pse_isr - IRQ handler for PSE
 * @irq: irq number
 * @data: pointer to user interrupt structure
 *
 * Return: irqreturn_t - status of IRQ
 */
static irqreturn_t pse_isr(int irq, void *data)
{
	struct pse_controller_dev *pcdev;
	unsigned long notifs_mask = 0;
	struct pse_irq_desc *desc;
	struct pse_irq *h = data;
	int ret, i;

	desc = &h->desc;
	pcdev = h->pcdev;

	/* Clear notifs mask */
	memset(h->notifs, 0, pcdev->nr_lines * sizeof(*h->notifs));
	mutex_lock(&pcdev->lock);
	ret = desc->map_event(irq, pcdev, h->notifs, &notifs_mask);
	mutex_unlock(&pcdev->lock);
	if (ret || !notifs_mask)
		return IRQ_NONE;

	for_each_set_bit(i, &notifs_mask, pcdev->nr_lines) {
		unsigned long notifs, rnotifs;
		struct net_device *netdev;
		struct pse_control *psec;

		/* Do nothing PI not described */
		if (!pcdev->pi[i].rdev)
			continue;

		notifs = h->notifs[i];
		dev_dbg(h->pcdev->dev,
			"Sending PSE notification EVT 0x%lx\n", notifs);

		psec = pse_control_find_by_id(pcdev, i);
		rtnl_lock();
		netdev = pse_control_get_netdev(psec);
		if (netdev)
			ethnl_pse_send_ntf(netdev, notifs);
		rtnl_unlock();
		pse_control_put(psec);

		rnotifs = pse_to_regulator_notifs(notifs);
		regulator_notifier_call_chain(pcdev->pi[i].rdev, rnotifs,
					      NULL);
	}

	return IRQ_HANDLED;
}

/**
 * devm_pse_irq_helper - Register IRQ based PSE event notifier
 * @pcdev: a pointer to the PSE
 * @irq: the irq value to be passed to request_irq
 * @irq_flags: the flags to be passed to request_irq
 * @d: PSE interrupt description
 *
 * Return: 0 on success and errno on failure
 */
int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
			int irq_flags, const struct pse_irq_desc *d)
{
	struct device *dev = pcdev->dev;
	size_t irq_name_len;
	struct pse_irq *h;
	char *irq_name;
	int ret;

	if (!d || !d->map_event || !d->name)
		return -EINVAL;

	h = devm_kzalloc(dev, sizeof(*h), GFP_KERNEL);
	if (!h)
		return -ENOMEM;

	h->pcdev = pcdev;
	h->desc = *d;

	/* IRQ name len is pcdev dev name + 5 char + irq desc name + 1 */
	irq_name_len = strlen(dev_name(pcdev->dev)) + 5 + strlen(d->name) + 1;
	irq_name = devm_kzalloc(dev, irq_name_len, GFP_KERNEL);
	if (!irq_name)
		return -ENOMEM;

	snprintf(irq_name, irq_name_len, "pse-%s:%s", dev_name(pcdev->dev),
		 d->name);

	h->notifs = devm_kcalloc(dev, pcdev->nr_lines,
				 sizeof(*h->notifs), GFP_KERNEL);
	if (!h->notifs)
		return -ENOMEM;

	ret = devm_request_threaded_irq(dev, irq, NULL, pse_isr,
					IRQF_ONESHOT | irq_flags,
					irq_name, h);
	if (ret)
		dev_err(pcdev->dev, "Failed to request IRQ %d\n", irq);

	pcdev->irq = irq;
	return ret;
}
EXPORT_SYMBOL_GPL(devm_pse_irq_helper);

/* PSE control section */

static void __pse_control_release(struct kref *kref)
+7 −0
Original line number Diff line number Diff line
@@ -43,6 +43,8 @@ void ethtool_aggregate_rmon_stats(struct net_device *dev,
				  struct ethtool_rmon_stats *rmon_stats);
bool ethtool_dev_mm_supported(struct net_device *dev);

void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notif);

#else
static inline int ethnl_cable_test_alloc(struct phy_device *phydev, u8 cmd)
{
@@ -120,6 +122,11 @@ static inline bool ethtool_dev_mm_supported(struct net_device *dev)
	return false;
}

static inline void ethnl_pse_send_ntf(struct phy_device *phydev,
				      unsigned long notif)
{
}

#endif /* IS_ENABLED(CONFIG_ETHTOOL_NETLINK) */

static inline int ethnl_cable_test_result(struct phy_device *phydev, u8 pair,
+20 −0
Original line number Diff line number Diff line
@@ -7,12 +7,15 @@

#include <linux/list.h>
#include <uapi/linux/ethtool.h>
#include <uapi/linux/ethtool_netlink_generated.h>
#include <linux/regulator/driver.h>

/* Maximum current in uA according to IEEE 802.3-2022 Table 145-1 */
#define MAX_PI_CURRENT 1920000
/* Maximum power in mW according to IEEE 802.3-2022 Table 145-16 */
#define MAX_PI_PW 99900

struct net_device;
struct phy_device;
struct pse_controller_dev;
struct netlink_ext_ack;
@@ -37,6 +40,19 @@ struct ethtool_c33_pse_pw_limit_range {
	u32 max;
};

/**
 * struct pse_irq_desc - notification sender description for IRQ based events.
 *
 * @name: the visible name for the IRQ
 * @map_event: driver callback to map IRQ status into PSE devices with events.
 */
struct pse_irq_desc {
	const char *name;
	int (*map_event)(int irq, struct pse_controller_dev *pcdev,
			 unsigned long *notifs,
			 unsigned long *notifs_mask);
};

/**
 * struct pse_control_config - PSE control/channel configuration.
 *
@@ -228,6 +244,7 @@ struct pse_pi {
 * @types: types of the PSE controller
 * @pi: table of PSE PIs described in this controller device
 * @no_of_pse_pi: flag set if the pse_pis devicetree node is not used
 * @irq: PSE interrupt
 */
struct pse_controller_dev {
	const struct pse_controller_ops *ops;
@@ -241,6 +258,7 @@ struct pse_controller_dev {
	enum ethtool_pse_types types;
	struct pse_pi *pi;
	bool no_of_pse_pi;
	int irq;
};

#if IS_ENABLED(CONFIG_PSE_CONTROLLER)
@@ -249,6 +267,8 @@ void pse_controller_unregister(struct pse_controller_dev *pcdev);
struct device;
int devm_pse_controller_register(struct device *dev,
				 struct pse_controller_dev *pcdev);
int devm_pse_irq_helper(struct pse_controller_dev *pcdev, int irq,
			int irq_flags, const struct pse_irq_desc *d);

struct pse_control *of_pse_control_get(struct device_node *node,
				       struct phy_device *phydev);
Loading