]> git.ipfire.org Git - thirdparty/linux.git/commitdiff
net: pse-pd: Add support for reporting events
authorKory Maincent (Dent Project) <kory.maincent@bootlin.com>
Tue, 17 Jun 2025 12:12:01 +0000 (14:12 +0200)
committerJakub Kicinski <kuba@kernel.org>
Thu, 19 Jun 2025 02:00:15 +0000 (19:00 -0700)
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: Kory 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: Jakub Kicinski <kuba@kernel.org>
Documentation/netlink/specs/ethtool.yaml
Documentation/networking/ethtool-netlink.rst
drivers/net/pse-pd/pse_core.c
include/linux/ethtool_netlink.h
include/linux/pse-pd/pse.h
include/uapi/linux/ethtool_netlink_generated.h
net/ethtool/pse-pd.c

index ed9bcdec01cca4ac2f6f4623ebc0a0ab77c11c4a..92b34a19f308f6633b3e28750cbfc116bb982d5b 100644 (file)
@@ -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
index b6e9af4d0f1b9df943c7041fd0011eb125e80632..433737865bc2262bfcc736cb38349ba7ce4f4f96 100644 (file)
@@ -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
 =======
 
index 4610c1f0ddd6d3da6f2f087e8d94742032d9396d..16cc1dc0724682b0736751b74d7c325a6f024349 100644 (file)
@@ -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 @@ out:
        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)
index aba91335273af4665427781fc14aa7159df5ad55..1dcc4059b5abf07e98e3ea2d4a8360432940df16 100644 (file)
@@ -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,
index 8b0866fad2ad383945df321680ef5644a9fdd85c..6eb064722aa8e6e0cfc8a14c0418641553878757 100644 (file)
@@ -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);
index 9a02f579de2255898709df2f1327967150a0f61d..3864aa0de8c72945c9774f2054d3f7387b1bfb26 100644 (file)
@@ -49,6 +49,16 @@ enum hwtstamp_source {
        HWTSTAMP_SOURCE_PHYLIB,
 };
 
+/**
+ * enum ethtool_pse_event - PSE event list for the PSE controller
+ * @ETHTOOL_PSE_EVENT_OVER_CURRENT: PSE output current is too high
+ * @ETHTOOL_PSE_EVENT_OVER_TEMP: PSE in over temperature state
+ */
+enum ethtool_pse_event {
+       ETHTOOL_PSE_EVENT_OVER_CURRENT = 1,
+       ETHTOOL_PSE_EVENT_OVER_TEMP = 2,
+};
+
 enum {
        ETHTOOL_A_HEADER_UNSPEC,
        ETHTOOL_A_HEADER_DEV_INDEX,
@@ -718,6 +728,14 @@ enum {
        ETHTOOL_A_TSCONFIG_MAX = (__ETHTOOL_A_TSCONFIG_CNT - 1)
 };
 
+enum {
+       ETHTOOL_A_PSE_NTF_HEADER = 1,
+       ETHTOOL_A_PSE_NTF_EVENTS,
+
+       __ETHTOOL_A_PSE_NTF_CNT,
+       ETHTOOL_A_PSE_NTF_MAX = (__ETHTOOL_A_PSE_NTF_CNT - 1)
+};
+
 enum {
        ETHTOOL_MSG_USER_NONE = 0,
        ETHTOOL_MSG_STRSET_GET = 1,
@@ -822,6 +840,7 @@ enum {
        ETHTOOL_MSG_PHY_NTF,
        ETHTOOL_MSG_TSCONFIG_GET_REPLY,
        ETHTOOL_MSG_TSCONFIG_SET_REPLY,
+       ETHTOOL_MSG_PSE_NTF,
 
        __ETHTOOL_MSG_KERNEL_CNT,
        ETHTOOL_MSG_KERNEL_MAX = (__ETHTOOL_MSG_KERNEL_CNT - 1)
index 4f6b99eab2a6c346af6d01166bf6721ab66e9cb3..5443b4e0065ac6a3e54c0e55911347ce5b7e6e04 100644 (file)
@@ -315,3 +315,42 @@ const struct ethnl_request_ops ethnl_pse_request_ops = {
        .set                    = ethnl_set_pse,
        /* PSE has no notification */
 };
+
+void ethnl_pse_send_ntf(struct net_device *netdev, unsigned long notifs)
+{
+       void *reply_payload;
+       struct sk_buff *skb;
+       int reply_len;
+       int ret;
+
+       ASSERT_RTNL();
+
+       if (!netdev || !notifs)
+               return;
+
+       reply_len = ethnl_reply_header_size() +
+                   nla_total_size(sizeof(u32)); /* _PSE_NTF_EVENTS */
+
+       skb = genlmsg_new(reply_len, GFP_KERNEL);
+       if (!skb)
+               return;
+
+       reply_payload = ethnl_bcastmsg_put(skb, ETHTOOL_MSG_PSE_NTF);
+       if (!reply_payload)
+               goto err_skb;
+
+       ret = ethnl_fill_reply_header(skb, netdev, ETHTOOL_A_PSE_NTF_HEADER);
+       if (ret < 0)
+               goto err_skb;
+
+       if (nla_put_uint(skb, ETHTOOL_A_PSE_NTF_EVENTS, notifs))
+               goto err_skb;
+
+       genlmsg_end(skb, reply_payload);
+       ethnl_multicast(skb, netdev);
+       return;
+
+err_skb:
+       nlmsg_free(skb);
+}
+EXPORT_SYMBOL_GPL(ethnl_pse_send_ntf);