lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-ID: <0963cc8b-a092-c743-05dc-c96a5c9d618f@seco.com>
Date:   Tue, 7 Mar 2023 11:30:39 -0500
From:   Sean Anderson <sean.anderson@...o.com>
To:     Tobias Waldekranz <tobias@...dekranz.com>,
        Andrew Lunn <andrew@...n.ch>,
        Heiner Kallweit <hkallweit1@...il.com>,
        Russell King <linux@...linux.org.uk>, netdev@...r.kernel.org
Cc:     "David S . Miller" <davem@...emloft.net>,
        Vladimir Oltean <olteanv@...il.com>,
        linux-kernel@...r.kernel.org, Paolo Abeni <pabeni@...hat.com>,
        Eric Dumazet <edumazet@...gle.com>,
        Florian Fainelli <f.fainelli@...il.com>,
        Jakub Kicinski <kuba@...nel.org>
Subject: Re: [PATCH net-next] net: mdio: Add netlink interface

On 3/7/23 07:26, Tobias Waldekranz wrote:
> On mån, mar 06, 2023 at 15:45, Sean Anderson <sean.anderson@...o.com> 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@...o.com>
>> ---
>> 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@...o.com>
>> + * Copyright (C) 2020-22 Tobias Waldekranz <tobias@...dekranz.com>
>> + */
>> +
>> +#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@...dekranz.com>");
>> +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@...dekranz.com>
>> + */
>> +
>> +#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

Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ