Add missing patch description, so all generic patches can be applied to a kernel tree using 'git am'. Signed-off-by: Daniel Golle <daniel@makrotopia.org>
365 lines
10 KiB
Diff
365 lines
10 KiB
Diff
From fc0e6db30941a66e284b8516b82356f97f31061d Mon Sep 17 00:00:00 2001
|
|
From: "Kory Maincent (Dent Project)" <kory.maincent@bootlin.com>
|
|
Date: Tue, 17 Jun 2025 14:12:01 +0200
|
|
Subject: [PATCH] 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: 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>
|
|
Signed-off-by: Carlo Szelinsky <github@szelinsky.de>
|
|
---
|
|
# ADAPTED FOR OPENWRT 6.12.67 - Documentation changes removed
|
|
# Original commit: fc0e6db30941
|
|
|
|
Documentation/netlink/specs/ethtool.yaml | 34 ++++
|
|
Documentation/networking/ethtool-netlink.rst | 19 ++
|
|
drivers/net/pse-pd/pse_core.c | 179 ++++++++++++++++++
|
|
include/linux/ethtool_netlink.h | 7 +
|
|
include/linux/pse-pd/pse.h | 20 ++
|
|
.../uapi/linux/ethtool_netlink_generated.h | 19 ++
|
|
net/ethtool/pse-pd.c | 39 ++++
|
|
7 files changed, 317 insertions(+)
|
|
|
|
--- a/drivers/net/pse-pd/pse_core.c
|
|
+++ b/drivers/net/pse-pd/pse_core.c
|
|
@@ -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
|
|
}
|
|
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, ¬ifs_mask);
|
|
+ mutex_unlock(&pcdev->lock);
|
|
+ if (ret || !notifs_mask)
|
|
+ return IRQ_NONE;
|
|
+
|
|
+ for_each_set_bit(i, ¬ifs_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)
|
|
--- a/include/linux/ethtool_netlink.h
|
|
+++ b/include/linux/ethtool_netlink.h
|
|
@@ -43,6 +43,8 @@ void ethtool_aggregate_rmon_stats(struct
|
|
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_suppor
|
|
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,
|
|
--- a/include/linux/pse-pd/pse.h
|
|
+++ b/include/linux/pse-pd/pse.h
|
|
@@ -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;
|
|
@@ -38,6 +41,19 @@ struct ethtool_c33_pse_pw_limit_range {
|
|
};
|
|
|
|
/**
|
|
+ * 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.
|
|
*
|
|
* @podl_admin_control: set PoDL PSE admin control as described in
|
|
@@ -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 ps
|
|
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);
|
|
--- a/net/ethtool/pse-pd.c
|
|
+++ b/net/ethtool/pse-pd.c
|
|
@@ -315,3 +315,42 @@ const struct ethnl_request_ops ethnl_pse
|
|
.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);
|