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

From: Sean Anderson
Date: Tue Mar 07 2023 - 11:31:12 EST


On 3/7/23 07:26, Tobias Waldekranz wrote:
> 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).

Yes, I agree with this. It's always seemed silly to me to exclude a good
debugging interface on the grounds that it could be used for userspace
drivers when a vendor can just as easily supply their own proprietary
module implementing the same thing.

Last time, the discussion seemed to get hung up on this topic, so I wanted
to start off with an approach which would obviously prevent misuse (albeit
rather draconian).

--Sean

> 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