[PATCH net-next] net: mdio: Add netlink interface
From: Sean Anderson
Date: Mon Mar 06 2023 - 15:46:00 EST
This adds a netlink interface to make reads/writes to mdio buses. This
makes it easier to debug devices. This is especially useful when there
is a PCS involved (and the ethtool reads are faked), when there is no
MAC associated with a PHY, or when the MDIO device is not a PHY.
The closest existing in-kernel interfaces are the SIOCG/SMIIREG ioctls, but
they have several drawbacks:
1. "Write register" is not always exactly that. The kernel will try to
be extra helpful and do things behind the scenes if it detects a
write to the reset bit of a PHY for example.
2. Only one op per syscall. This means that is impossible to implement
many operations in a safe manner. Something as simple as a
read/mask/write cycle can race against an in-kernel driver.
3. Addressing is awkward since you don't address the MDIO bus
directly, rather "the MDIO bus to which this netdev's PHY is
connected". This makes it hard to talk to devices on buses to which
no PHYs are connected, the typical case being Ethernet switches.
To address these shortcomings, this adds a GENL interface with which a user
can interact with an MDIO bus directly. The user sends a program that
mdio-netlink executes, possibly emitting data back to the user. I.e. it
implements a very simple VM. Read/mask/write operations could be
implemented by dedicated commands, but when you start looking at more
advanced things like reading out the VLAN database of a switch you need
state and branching.
To prevent userspace phy drivers, writes are disabled by default, and can
only be enabled by editing the source. This is the same strategy used by
regmap for debugfs writes. Unfortunately, this disallows several useful
features, including
- Register writes (obviously)
- C45-over-C22
- Atomic access to paged registers
- Better MDIO emulation for e.g. QEMU
However, the read-only interface remains broadly useful for debugging.
Users who want to use the above features can re-enable them by defining
MDIO_NETLINK_ALLOW_WRITE and recompiling their kernel.
Signed-off-by: Sean Anderson <sean.anderson@xxxxxxxx>
---
This driver was written by Tobias Waldekranz. It is adapted from the
version he released with mdio-tools [1]. This was last discussed 2.5
years ago [2], and I have incorperated his cover letter into this commit
message. The discussion mainly centered around the write capability
allowing for userspace phy drivers. Although it comes with reduced
functionality, I hope this new approach satisfies Andrew. I have also
made several minor changes for style and to stay abrest of changing
APIs.
Tobias, I've taken the liberty of adding some copyright notices
attributing this to you.
[1] https://github.com/wkz/mdio-tools
[2] https://lore.kernel.org/netdev/C42DZQLTPHM5.2THDSRK84BI3T@wkz-x280/
drivers/net/mdio/Kconfig | 8 +
drivers/net/mdio/Makefile | 1 +
drivers/net/mdio/mdio-netlink.c | 464 ++++++++++++++++++++++++++++++
include/uapi/linux/mdio-netlink.h | 61 ++++
4 files changed, 534 insertions(+)
create mode 100644 drivers/net/mdio/mdio-netlink.c
create mode 100644 include/uapi/linux/mdio-netlink.h
diff --git a/drivers/net/mdio/Kconfig b/drivers/net/mdio/Kconfig
index 90309980686e..8a01978e5b51 100644
--- a/drivers/net/mdio/Kconfig
+++ b/drivers/net/mdio/Kconfig
@@ -43,6 +43,14 @@ config ACPI_MDIO
if MDIO_BUS
+config MDIO_NETLINK
+ tristate "Netlink interface for MDIO buses"
+ help
+ Enable a netlink interface to allow reading MDIO buses from
+ userspace. A small virtual machine allows implementing complex
+ operations, such as conditional reads or polling. All operations
+ submitted in the same program are evaluated atomically.
+
config MDIO_DEVRES
tristate
diff --git a/drivers/net/mdio/Makefile b/drivers/net/mdio/Makefile
index 7d4cb4c11e4e..5583d5b8d174 100644
--- a/drivers/net/mdio/Makefile
+++ b/drivers/net/mdio/Makefile
@@ -4,6 +4,7 @@
obj-$(CONFIG_ACPI_MDIO) += acpi_mdio.o
obj-$(CONFIG_FWNODE_MDIO) += fwnode_mdio.o
obj-$(CONFIG_OF_MDIO) += of_mdio.o
+obj-$(CONFIG_MDIO_NETLINK) += mdio-netlink.o
obj-$(CONFIG_MDIO_ASPEED) += mdio-aspeed.o
obj-$(CONFIG_MDIO_BCM_IPROC) += mdio-bcm-iproc.o
diff --git a/drivers/net/mdio/mdio-netlink.c b/drivers/net/mdio/mdio-netlink.c
new file mode 100644
index 000000000000..3e32d1a9bab3
--- /dev/null
+++ b/drivers/net/mdio/mdio-netlink.c
@@ -0,0 +1,464 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (C) 2022-23 Sean Anderson <sean.anderson@xxxxxxxx>
+ * Copyright (C) 2020-22 Tobias Waldekranz <tobias@xxxxxxxxxxxxxx>
+ */
+
+#include <linux/init.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/netlink.h>
+#include <linux/phy.h>
+#include <net/genetlink.h>
+#include <net/netlink.h>
+#include <uapi/linux/mdio-netlink.h>
+
+struct mdio_nl_xfer {
+ struct genl_info *info;
+ struct sk_buff *msg;
+ void *hdr;
+ struct nlattr *data;
+
+ struct mii_bus *mdio;
+ int timeout_ms;
+
+ int prog_len;
+ struct mdio_nl_insn *prog;
+};
+
+static int mdio_nl_open(struct mdio_nl_xfer *xfer);
+static int mdio_nl_close(struct mdio_nl_xfer *xfer, bool last, int xerr);
+
+static int mdio_nl_flush(struct mdio_nl_xfer *xfer)
+{
+ int err;
+
+ err = mdio_nl_close(xfer, false, 0);
+ if (err)
+ return err;
+
+ return mdio_nl_open(xfer);
+}
+
+static int mdio_nl_emit(struct mdio_nl_xfer *xfer, u32 datum)
+{
+ int err = 0;
+
+ if (!nla_put_nohdr(xfer->msg, sizeof(datum), &datum))
+ return 0;
+
+ err = mdio_nl_flush(xfer);
+ if (err)
+ return err;
+
+ return nla_put_nohdr(xfer->msg, sizeof(datum), &datum);
+}
+
+static inline u16 *__arg_r(u32 arg, u16 *regs)
+{
+ WARN_ON_ONCE(arg >> 16 != MDIO_NL_ARG_REG);
+
+ return ®s[arg & 0x7];
+}
+
+static inline u16 __arg_i(u32 arg)
+{
+ WARN_ON_ONCE(arg >> 16 != MDIO_NL_ARG_IMM);
+
+ return arg & 0xffff;
+}
+
+static inline u16 __arg_ri(u32 arg, u16 *regs)
+{
+ switch ((enum mdio_nl_argmode)(arg >> 16)) {
+ case MDIO_NL_ARG_IMM:
+ return arg & 0xffff;
+ case MDIO_NL_ARG_REG:
+ return regs[arg & 7];
+ default:
+ WARN_ON_ONCE(1);
+ return 0;
+ }
+}
+
+/* To prevent out-of-tree drivers from being implemented through this
+ * interface, disallow writes by default. This does disallow read-only uses,
+ * such as c45-over-c22 or reading phys with pages. However, with a such a
+ * flexible interface, we must use a big hammer. People who want to use this
+ * will need to modify the source code directly.
+ */
+#undef MDIO_NETLINK_ALLOW_WRITE
+
+static int mdio_nl_eval(struct mdio_nl_xfer *xfer)
+{
+ struct mdio_nl_insn *insn;
+ unsigned long timeout;
+ u16 regs[8] = { 0 };
+ int pc, ret = 0;
+ int phy_id, reg, prtad, devad, val;
+
+ timeout = jiffies + msecs_to_jiffies(xfer->timeout_ms);
+
+ mutex_lock(&xfer->mdio->mdio_lock);
+
+ for (insn = xfer->prog, pc = 0;
+ pc < xfer->prog_len;
+ insn = &xfer->prog[++pc]) {
+ if (time_after(jiffies, timeout)) {
+ ret = -ETIMEDOUT;
+ break;
+ }
+
+ switch ((enum mdio_nl_op)insn->op) {
+ case MDIO_NL_OP_READ:
+ phy_id = __arg_ri(insn->arg0, regs);
+ prtad = mdio_phy_id_prtad(phy_id);
+ devad = mdio_phy_id_devad(phy_id);
+ reg = __arg_ri(insn->arg1, regs);
+
+ if (mdio_phy_id_is_c45(phy_id))
+ ret = __mdiobus_c45_read(xfer->mdio, prtad,
+ devad, reg);
+ else
+ ret = __mdiobus_read(xfer->mdio, phy_id, reg);
+
+ if (ret < 0)
+ goto exit;
+ *__arg_r(insn->arg2, regs) = ret;
+ ret = 0;
+ break;
+
+ case MDIO_NL_OP_WRITE:
+ phy_id = __arg_ri(insn->arg0, regs);
+ prtad = mdio_phy_id_prtad(phy_id);
+ devad = mdio_phy_id_devad(phy_id);
+ reg = __arg_ri(insn->arg1, regs);
+ val = __arg_ri(insn->arg2, regs);
+
+#ifdef MDIO_NETLINK_ALLOW_WRITE
+ add_taint(TAINT_USER, LOCKDEP_STILL_OK);
+ if (mdio_phy_id_is_c45(phy_id))
+ ret = __mdiobus_c45_write(xfer->mdio, prtad,
+ devad, reg, val
+ else
+ ret = __mdiobus_write(xfer->mdio, dev, reg,
+ val);
+#else
+ ret = -EPERM;
+#endif
+ if (ret < 0)
+ goto exit;
+ ret = 0;
+ break;
+
+ case MDIO_NL_OP_AND:
+ *__arg_r(insn->arg2, regs) =
+ __arg_ri(insn->arg0, regs) &
+ __arg_ri(insn->arg1, regs);
+ break;
+
+ case MDIO_NL_OP_OR:
+ *__arg_r(insn->arg2, regs) =
+ __arg_ri(insn->arg0, regs) |
+ __arg_ri(insn->arg1, regs);
+ break;
+
+ case MDIO_NL_OP_ADD:
+ *__arg_r(insn->arg2, regs) =
+ __arg_ri(insn->arg0, regs) +
+ __arg_ri(insn->arg1, regs);
+ break;
+
+ case MDIO_NL_OP_JEQ:
+ if (__arg_ri(insn->arg0, regs) ==
+ __arg_ri(insn->arg1, regs))
+ pc += (s16)__arg_i(insn->arg2);
+ break;
+
+ case MDIO_NL_OP_JNE:
+ if (__arg_ri(insn->arg0, regs) !=
+ __arg_ri(insn->arg1, regs))
+ pc += (s16)__arg_i(insn->arg2);
+ break;
+
+ case MDIO_NL_OP_EMIT:
+ ret = mdio_nl_emit(xfer, __arg_ri(insn->arg0, regs));
+ if (ret < 0)
+ goto exit;
+ ret = 0;
+ break;
+
+ case MDIO_NL_OP_UNSPEC:
+ default:
+ ret = -EINVAL;
+ goto exit;
+ }
+ }
+exit:
+ mutex_unlock(&xfer->mdio->mdio_lock);
+ return ret;
+}
+
+struct mdio_nl_op_proto {
+ u8 arg0;
+ u8 arg1;
+ u8 arg2;
+};
+
+static const struct mdio_nl_op_proto mdio_nl_op_protos[MDIO_NL_OP_MAX + 1] = {
+ [MDIO_NL_OP_READ] = {
+ .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg2 = BIT(MDIO_NL_ARG_REG),
+ },
+ [MDIO_NL_OP_WRITE] = {
+ .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg2 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ },
+ [MDIO_NL_OP_AND] = {
+ .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg2 = BIT(MDIO_NL_ARG_REG),
+ },
+ [MDIO_NL_OP_OR] = {
+ .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg2 = BIT(MDIO_NL_ARG_REG),
+ },
+ [MDIO_NL_OP_ADD] = {
+ .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg2 = BIT(MDIO_NL_ARG_REG),
+ },
+ [MDIO_NL_OP_JEQ] = {
+ .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg2 = BIT(MDIO_NL_ARG_IMM),
+ },
+ [MDIO_NL_OP_JNE] = {
+ .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg1 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg2 = BIT(MDIO_NL_ARG_IMM),
+ },
+ [MDIO_NL_OP_EMIT] = {
+ .arg0 = BIT(MDIO_NL_ARG_REG) | BIT(MDIO_NL_ARG_IMM),
+ .arg1 = BIT(MDIO_NL_ARG_NONE),
+ .arg2 = BIT(MDIO_NL_ARG_NONE),
+ },
+};
+
+static int mdio_nl_validate_insn(const struct nlattr *attr,
+ struct netlink_ext_ack *extack,
+ const struct mdio_nl_insn *insn)
+{
+ const struct mdio_nl_op_proto *proto;
+
+ if (insn->op > MDIO_NL_OP_MAX) {
+ NL_SET_ERR_MSG_ATTR(extack, attr, "Illegal instruction");
+ return -EINVAL;
+ }
+
+ proto = &mdio_nl_op_protos[insn->op];
+
+ if (!(BIT(insn->arg0 >> 16) & proto->arg0)) {
+ NL_SET_ERR_MSG_ATTR(extack, attr, "Argument 0 invalid");
+ return -EINVAL;
+ }
+
+ if (!(BIT(insn->arg1 >> 16) & proto->arg1)) {
+ NL_SET_ERR_MSG_ATTR(extack, attr, "Argument 1 invalid");
+ return -EINVAL;
+ }
+
+ if (!(BIT(insn->arg2 >> 16) & proto->arg2)) {
+ NL_SET_ERR_MSG_ATTR(extack, attr, "Argument 2 invalid");
+ return -EINVAL;
+ }
+
+ return 0;
+}
+
+static int mdio_nl_validate_prog(const struct nlattr *attr,
+ struct netlink_ext_ack *extack)
+{
+ const struct mdio_nl_insn *prog = nla_data(attr);
+ int len = nla_len(attr);
+ int i, err = 0;
+
+ if (len % sizeof(*prog)) {
+ NL_SET_ERR_MSG_ATTR(extack, attr, "Unaligned instruction");
+ return -EINVAL;
+ }
+
+ len /= sizeof(*prog);
+ for (i = 0; i < len; i++) {
+ err = mdio_nl_validate_insn(attr, extack, &prog[i]);
+ if (err)
+ break;
+ }
+
+ return err;
+}
+
+static const struct nla_policy mdio_nl_policy[MDIO_NLA_MAX + 1] = {
+ [MDIO_NLA_UNSPEC] = { .type = NLA_UNSPEC, },
+ [MDIO_NLA_BUS_ID] = { .type = NLA_STRING, .len = MII_BUS_ID_SIZE },
+ [MDIO_NLA_TIMEOUT] = NLA_POLICY_MAX(NLA_U16, 10 * MSEC_PER_SEC),
+ [MDIO_NLA_PROG] = NLA_POLICY_VALIDATE_FN(NLA_BINARY,
+ mdio_nl_validate_prog,
+ 0x1000),
+ [MDIO_NLA_DATA] = { .type = NLA_NESTED },
+ [MDIO_NLA_ERROR] = { .type = NLA_S32, },
+};
+
+static struct genl_family mdio_nl_family;
+
+static int mdio_nl_open(struct mdio_nl_xfer *xfer)
+{
+ int err;
+
+ xfer->msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+ if (!xfer->msg) {
+ err = -ENOMEM;
+ goto err;
+ }
+
+ xfer->hdr = genlmsg_put(xfer->msg, xfer->info->snd_portid,
+ xfer->info->snd_seq, &mdio_nl_family,
+ NLM_F_ACK | NLM_F_MULTI, MDIO_GENL_XFER);
+ if (!xfer->hdr) {
+ err = -EMSGSIZE;
+ goto err_free;
+ }
+
+ xfer->data = nla_nest_start(xfer->msg, MDIO_NLA_DATA);
+ if (!xfer->data) {
+ err = -EMSGSIZE;
+ goto err_free;
+ }
+
+ return 0;
+
+err_free:
+ nlmsg_free(xfer->msg);
+err:
+ return err;
+}
+
+static int mdio_nl_close(struct mdio_nl_xfer *xfer, bool last, int xerr)
+{
+ struct nlmsghdr *end;
+ int err;
+
+ nla_nest_end(xfer->msg, xfer->data);
+
+ if (xerr && nla_put_s32(xfer->msg, MDIO_NLA_ERROR, xerr)) {
+ err = mdio_nl_flush(xfer);
+ if (err)
+ goto err_free;
+
+ if (nla_put_s32(xfer->msg, MDIO_NLA_ERROR, xerr)) {
+ err = -EMSGSIZE;
+ goto err_free;
+ }
+ }
+
+ genlmsg_end(xfer->msg, xfer->hdr);
+
+ if (last) {
+ end = nlmsg_put(xfer->msg, xfer->info->snd_portid,
+ xfer->info->snd_seq, NLMSG_DONE, 0,
+ NLM_F_ACK | NLM_F_MULTI);
+ if (!end) {
+ err = mdio_nl_flush(xfer);
+ if (err)
+ goto err_free;
+
+ end = nlmsg_put(xfer->msg, xfer->info->snd_portid,
+ xfer->info->snd_seq, NLMSG_DONE, 0,
+ NLM_F_ACK | NLM_F_MULTI);
+ if (!end) {
+ err = -EMSGSIZE;
+ goto err_free;
+ }
+ }
+ }
+
+ return genlmsg_unicast(genl_info_net(xfer->info), xfer->msg,
+ xfer->info->snd_portid);
+
+err_free:
+ nlmsg_free(xfer->msg);
+ return err;
+}
+
+static int mdio_nl_cmd_xfer(struct sk_buff *skb, struct genl_info *info)
+{
+ struct mdio_nl_xfer xfer;
+ int err;
+
+ if (!info->attrs[MDIO_NLA_BUS_ID] ||
+ !info->attrs[MDIO_NLA_PROG] ||
+ info->attrs[MDIO_NLA_DATA] ||
+ info->attrs[MDIO_NLA_ERROR])
+ return -EINVAL;
+
+ xfer.mdio = mdio_find_bus(nla_data(info->attrs[MDIO_NLA_BUS_ID]));
+ if (!xfer.mdio)
+ return -ENODEV;
+
+ if (info->attrs[MDIO_NLA_TIMEOUT])
+ xfer.timeout_ms = nla_get_u32(info->attrs[MDIO_NLA_TIMEOUT]);
+ else
+ xfer.timeout_ms = 100;
+
+ xfer.info = info;
+ xfer.prog_len = nla_len(info->attrs[MDIO_NLA_PROG]) / sizeof(*xfer.prog);
+ xfer.prog = nla_data(info->attrs[MDIO_NLA_PROG]);
+
+ err = mdio_nl_open(&xfer);
+ if (err)
+ return err;
+
+ err = mdio_nl_eval(&xfer);
+
+ err = mdio_nl_close(&xfer, true, err);
+ return err;
+}
+
+static const struct genl_ops mdio_nl_ops[] = {
+ {
+ .cmd = MDIO_GENL_XFER,
+ .doit = mdio_nl_cmd_xfer,
+ .flags = GENL_ADMIN_PERM,
+ },
+};
+
+static struct genl_family mdio_nl_family = {
+ .name = "mdio",
+ .version = 1,
+ .maxattr = MDIO_NLA_MAX,
+ .netnsok = false,
+ .module = THIS_MODULE,
+ .ops = mdio_nl_ops,
+ .n_ops = ARRAY_SIZE(mdio_nl_ops),
+ .policy = mdio_nl_policy,
+};
+
+static int __init mdio_nl_init(void)
+{
+ return genl_register_family(&mdio_nl_family);
+}
+
+static void __exit mdio_nl_exit(void)
+{
+ genl_unregister_family(&mdio_nl_family);
+}
+
+MODULE_AUTHOR("Tobias Waldekranz <tobias@xxxxxxxxxxxxxx>");
+MODULE_DESCRIPTION("MDIO Netlink Interface");
+MODULE_LICENSE("GPL");
+
+module_init(mdio_nl_init);
+module_exit(mdio_nl_exit);
diff --git a/include/uapi/linux/mdio-netlink.h b/include/uapi/linux/mdio-netlink.h
new file mode 100644
index 000000000000..bebd3b45c882
--- /dev/null
+++ b/include/uapi/linux/mdio-netlink.h
@@ -0,0 +1,61 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+/*
+ * Copyright (C) 2020-22 Tobias Waldekranz <tobias@xxxxxxxxxxxxxx>
+ */
+
+#ifndef _UAPI_LINUX_MDIO_NETLINK_H
+#define _UAPI_LINUX_MDIO_NETLINK_H
+
+#include <linux/types.h>
+
+enum {
+ MDIO_GENL_UNSPEC,
+ MDIO_GENL_XFER,
+
+ __MDIO_GENL_MAX,
+ MDIO_GENL_MAX = __MDIO_GENL_MAX - 1
+};
+
+enum {
+ MDIO_NLA_UNSPEC,
+ MDIO_NLA_BUS_ID, /* string */
+ MDIO_NLA_TIMEOUT, /* u32 */
+ MDIO_NLA_PROG, /* struct mdio_nl_insn[] */
+ MDIO_NLA_DATA, /* nest */
+ MDIO_NLA_ERROR, /* s32 */
+
+ __MDIO_NLA_MAX,
+ MDIO_NLA_MAX = __MDIO_NLA_MAX - 1
+};
+
+enum mdio_nl_op {
+ MDIO_NL_OP_UNSPEC,
+ MDIO_NL_OP_READ, /* read dev(RI), port(RI), dst(R) */
+ MDIO_NL_OP_WRITE, /* write dev(RI), port(RI), src(RI) */
+ MDIO_NL_OP_AND, /* and a(RI), b(RI), dst(R) */
+ MDIO_NL_OP_OR, /* or a(RI), b(RI), dst(R) */
+ MDIO_NL_OP_ADD, /* add a(RI), b(RI), dst(R) */
+ MDIO_NL_OP_JEQ, /* jeq a(RI), b(RI), jmp(I) */
+ MDIO_NL_OP_JNE, /* jeq a(RI), b(RI), jmp(I) */
+ MDIO_NL_OP_EMIT, /* emit src(RI) */
+
+ __MDIO_NL_OP_MAX,
+ MDIO_NL_OP_MAX = __MDIO_NL_OP_MAX - 1
+};
+
+enum mdio_nl_argmode {
+ MDIO_NL_ARG_NONE,
+ MDIO_NL_ARG_REG,
+ MDIO_NL_ARG_IMM,
+ MDIO_NL_ARG_RESERVED
+};
+
+struct mdio_nl_insn {
+ __u64 op:8;
+ __u64 reserved:2;
+ __u64 arg0:18;
+ __u64 arg1:18;
+ __u64 arg2:18;
+};
+
+#endif /* _UAPI_LINUX_MDIO_NETLINK_H */
--
2.35.1.1320.gc452695387.dirty