Re: [PATCH net-next] net: mdio: Add netlink interface

From: Tobias Waldekranz
Date: Tue Mar 07 2023 - 07:26:44 EST


On mån, mar 06, 2023 at 15:45, Sean Anderson <sean.anderson@xxxxxxxx> wrote:
> 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.

What about taking a page from the BPF playbook and require all loaded
programs (MDIO_GENL_XFERs) to be licensed under GPL? That would mean
that the userspace program that generated it would also have to be
GPLed.

My view has always been that a vendor looking to build a userspace SDK
won't be deterred by this limitation. They can easily build
mdio-netlink.ko from mdio-tools and use that to drive it, or (more
likely) they already have their own implementation that they are stuck
with for legacy reasons. In other words: we are only punishing
legitimate users (mdio-tools being one of them, IMO).

Perhaps with this approach we could have our cake and eat it too.

> 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.

Fine by me :)

> [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 &regs[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