All of lore.kernel.org
 help / color / mirror / Atom feed
From: Michal Kubecek <mkubecek@suse.cz>
To: David Miller <davem@davemloft.net>, netdev@vger.kernel.org
Cc: Jakub Kicinski <jakub.kicinski@netronome.com>,
	Jiri Pirko <jiri@resnulli.us>, Andrew Lunn <andrew@lunn.ch>,
	Florian Fainelli <f.fainelli@gmail.com>,
	John Linville <linville@tuxdriver.com>,
	linux-kernel@vger.kernel.org
Subject: [PATCH net-next v4 10/22] ethtool: generic handlers for GET requests
Date: Thu, 21 Mar 2019 14:40:48 +0100 (CET)	[thread overview]
Message-ID: <3fadcd2f6d02306175b14fe7083e76db12403ea0.1553170807.git.mkubecek@suse.cz> (raw)
In-Reply-To: <cover.1553170807.git.mkubecek@suse.cz>

Some parts of processing GET type requests and related notifications are
independent of a command. Provide universal functions so that only four
callbacks need to be defined for each command type:

  parse_request() - parse incoming message
  prepare_data()  - retrieve data from driver or NIC
  reply_size()    - estimate reply message size
  fill_reply()    - compose reply message

These callback are defined in an instance of struct get_request_ops.

Signed-off-by: Michal Kubecek <mkubecek@suse.cz>
---
 net/ethtool/netlink.c | 319 ++++++++++++++++++++++++++++++++++++++++++
 net/ethtool/netlink.h | 111 +++++++++++++++
 2 files changed, 430 insertions(+)

diff --git a/net/ethtool/netlink.c b/net/ethtool/netlink.c
index 9ab23eaea029..18f300e42d21 100644
--- a/net/ethtool/netlink.c
+++ b/net/ethtool/netlink.c
@@ -149,6 +149,325 @@ struct sk_buff *ethnl_reply_init(size_t payload, struct net_device *dev, u8 cmd,
 	return NULL;
 }
 
+/* GET request helpers */
+
+const struct get_request_ops *get_requests[__ETHNL_CMD_CNT] = {
+};
+
+/**
+ * ethnl_alloc_get_data() - Allocate and initialize data for a GET request
+ * @ops: instance of struct get_request_ops describing size and layout
+ *
+ * This initializes only the first part (req_info), second part (reply_data)
+ * is initialized before filling the reply data into it (which is done for
+ * each iteration in dump requests).
+ *
+ * Return: pointer to allocated and initialized data, NULL on error
+ */
+static struct common_req_info *
+ethnl_alloc_get_data(const struct get_request_ops *ops)
+{
+	struct common_req_info *req_info;
+
+	req_info = kmalloc(ops->data_size, GFP_KERNEL);
+	if (!req_info)
+		return NULL;
+
+	memset(req_info, '\0', ops->repdata_offset);
+	req_info->reply_data =
+		(struct common_reply_data *)((char *)req_info +
+					     ops->repdata_offset);
+
+	return req_info;
+}
+
+/**
+ * ethnl_free_get_data() - free GET request data
+ * @ops: instance of struct get_request_ops describing the layout
+ * @req_info: pointer to embedded struct common_req_info (at offset 0)
+ *
+ * Calls ->cleanup() handler if defined and frees the data block.
+ */
+static void ethnl_free_get_data(const struct get_request_ops *ops,
+				struct common_req_info *req_info)
+{
+	if (ops->cleanup)
+		ops->cleanup(req_info);
+	kfree(req_info);
+}
+
+/**
+ * ethnl_init_reply_data() - Initialize reply data for GET request
+ * @req_info: pointer to embedded struct common_req_info
+ * @ops:      instance of struct get_request_ops describing the layout
+ * @dev:      network device to initialize the reply for
+ *
+ * Fills the reply data part with zeros and sets the dev member. Must be called
+ * before calling the ->fill_reply() callback (for each iteration when handling
+ * dump requests).
+ */
+static void ethnl_init_reply_data(const struct common_req_info *req_info,
+				  const struct get_request_ops *ops,
+				  struct net_device *dev)
+{
+	memset(req_info->reply_data, '\0',
+	       ops->data_size - ops->repdata_offset);
+	req_info->reply_data->dev = dev;
+}
+
+/* generic ->doit() handler for GET type requests */
+int ethnl_get_doit(struct sk_buff *skb, struct genl_info *info)
+{
+	const u8 cmd = info->genlhdr->cmd;
+	struct common_req_info *req_info;
+	const struct get_request_ops *ops;
+	struct sk_buff *rskb;
+	void *reply_payload;
+	int reply_len;
+	int ret;
+
+	ops = get_requests[cmd];
+	if (WARN_ONCE(!ops, "cmd %u has no get_request_ops\n", cmd))
+		return -EOPNOTSUPP;
+	req_info = ethnl_alloc_get_data(ops);
+	if (!req_info)
+		return -ENOMEM;
+	ret = ops->parse_request(req_info, skb, info, info->nlhdr);
+	if (!ops->allow_nodev_do && !req_info->dev) {
+		ETHNL_SET_ERRMSG(info, "device not specified in do request");
+		ret = -EINVAL;
+	}
+	if (ret < 0)
+		goto err_dev;
+	ethnl_init_reply_data(req_info, ops, req_info->dev);
+
+	rtnl_lock();
+	ret = ops->prepare_data(req_info, info);
+	if (ret < 0)
+		goto err_rtnl;
+	reply_len = ops->reply_size(req_info);
+	if (ret < 0)
+		goto err_rtnl;
+	ret = -ENOMEM;
+	rskb = ethnl_reply_init(reply_len, req_info->dev, ops->reply_cmd,
+				ops->dev_attrtype, info, &reply_payload);
+	if (!rskb)
+		goto err_rtnl;
+	ret = ops->fill_reply(rskb, req_info);
+	if (ret < 0)
+		goto err;
+	rtnl_unlock();
+
+	genlmsg_end(rskb, reply_payload);
+	if (req_info->dev)
+		dev_put(req_info->dev);
+	ethnl_free_get_data(ops, req_info);
+	return genlmsg_reply(rskb, info);
+
+err:
+	WARN_ONCE(ret == -EMSGSIZE,
+		  "calculated message payload length (%d) not sufficient\n",
+		  reply_len);
+	nlmsg_free(rskb);
+	ethnl_free_get_data(ops, req_info);
+err_rtnl:
+	rtnl_unlock();
+err_dev:
+	if (req_info->dev)
+		dev_put(req_info->dev);
+	return ret;
+}
+
+static int ethnl_get_dump_one(struct sk_buff *skb,
+			      struct net_device *dev,
+			      const struct get_request_ops *ops,
+			      struct common_req_info *req_info)
+{
+	int ret;
+
+	ethnl_init_reply_data(req_info, ops, dev);
+	rtnl_lock();
+	ret = ops->prepare_data(req_info, NULL);
+	if (ret < 0)
+		return ret;
+	ret = ethnl_fill_dev(skb, dev, ops->dev_attrtype);
+	if (ret < 0)
+		return ret;
+	ret = ops->fill_reply(skb, req_info);
+	rtnl_unlock();
+
+	req_info->reply_data->dev = NULL;
+	return ret;
+}
+
+/* generic ->dumpit() handler for GET requests; device iteration copied from
+ * rtnl_dump_ifinfo()
+ * cb->args[0]: pointer to struct get_request_ops
+ * cb->args[1]: pointer to request data
+ * cb->args[2]: iteration position - hashbucket
+ * cb->args[3]: iteration position - ifindex
+ */
+int ethnl_get_dumpit(struct sk_buff *skb, struct netlink_callback *cb)
+{
+	struct net *net = sock_net(skb->sk);
+	struct common_req_info *req_info;
+	const struct get_request_ops *ops;
+	int h, s_h, idx = 0, s_idx;
+	struct hlist_head *head;
+	struct net_device *dev;
+	int ret = 0;
+	void *ehdr;
+
+	ops = (const struct get_request_ops *)cb->args[0];
+	req_info = (struct common_req_info *)cb->args[1];
+	s_h = cb->args[2];
+	s_idx = cb->args[3];
+
+	for (h = s_h; h < NETDEV_HASHENTRIES; h++, s_idx = 0) {
+		idx = 0;
+		head = &net->dev_index_head[h];
+		hlist_for_each_entry(dev, head, index_hlist) {
+			if (idx < s_idx)
+				goto cont;
+			ehdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid,
+					   cb->nlh->nlmsg_seq,
+					   &ethtool_genl_family, 0,
+					   ops->reply_cmd);
+			ret = ethnl_get_dump_one(skb, dev, ops, req_info);
+			if (ret < 0) {
+				genlmsg_cancel(skb, ehdr);
+				if (ret == -EOPNOTSUPP)
+					goto cont;
+				if (likely(skb->len))
+					goto out;
+				goto out_err;
+			}
+			genlmsg_end(skb, ehdr);
+cont:
+			idx++;
+		}
+	}
+out:
+	ret = skb->len;
+out_err:
+	cb->args[2] = h;
+	cb->args[3] = idx;
+	cb->seq = net->dev_base_seq;
+	nl_dump_check_consistent(cb, nlmsg_hdr(skb));
+
+	return ret;
+}
+
+/* generic ->start() handler for GET requests */
+static int ethnl_get_start(struct netlink_callback *cb)
+{
+	struct common_req_info *req_info;
+	const struct get_request_ops *ops;
+	struct genlmsghdr *ghdr;
+	int ret;
+
+	ghdr = nlmsg_data(cb->nlh);
+	ops = get_requests[ghdr->cmd];
+	if (WARN_ONCE(!ops, "cmd %u has no get_request_ops\n", ghdr->cmd))
+		return -EOPNOTSUPP;
+	req_info = ethnl_alloc_get_data(ops);
+	if (!req_info)
+		return -ENOMEM;
+
+	ret = ops->parse_request(req_info, cb->skb, NULL, cb->nlh);
+	if (req_info->dev) {
+		/* We ignore device specification in dump requests but as the
+		 * same parser as for non-dump (doit) requests is used, it
+		 * would take reference to the device if it finds one
+		 */
+		dev_put(req_info->dev);
+		req_info->dev = NULL;
+	}
+	if (ret < 0)
+		return ret;
+
+	cb->args[0] = (long)ops;
+	cb->args[1] = (long)req_info;
+	cb->args[2] = 0;
+	cb->args[3] = 0;
+
+	return 0;
+}
+
+/* generic ->done() handler for GET requests */
+static int ethnl_get_done(struct netlink_callback *cb)
+{
+	ethnl_free_get_data((const struct get_request_ops *)cb->args[0],
+			    (struct common_req_info *)cb->args[1]);
+
+	return 0;
+}
+
+/* generic notification handler */
+static void ethnl_std_notify(struct net_device *dev,
+			     struct netlink_ext_ack *extack, unsigned int cmd,
+			     u32 req_mask, const void *data)
+{
+	struct common_req_info *req_info;
+	const struct get_request_ops *ops;
+	struct sk_buff *skb;
+	void *reply_payload;
+	int reply_len;
+	int ret;
+
+	ops = get_requests[cmd - 1];
+	if (WARN_ONCE(!ops, "cmd %u has no get_request_ops\n", cmd - 1))
+		return;
+	/* when ethnl_std_notify() is used as notify handler, command id of
+	 * corresponding GET request must be one less than cmd argument passed
+	 * to ethnl_std_notify()
+	 */
+	if (WARN_ONCE(ops->reply_cmd != cmd,
+		      "reply_cmd for %u is %u, expected %u\n", cmd - 1,
+		      ops->reply_cmd, cmd))
+		return;
+
+	req_info = ethnl_alloc_get_data(ops);
+	if (!req_info)
+		return;
+	req_info->dev = dev;
+	req_info->req_mask = req_mask;
+	req_info->compact = true;
+
+	ethnl_init_reply_data(req_info, ops, dev);
+	ret = ops->prepare_data(req_info, NULL);
+	if (ret < 0)
+		goto err_data;
+	reply_len = ops->reply_size(req_info);
+	if (reply_len < 0)
+		goto err_data;
+	skb = genlmsg_new(reply_len, GFP_KERNEL);
+	if (!skb)
+		goto err_data;
+	reply_payload = genlmsg_put(skb, 0, ++ethnl_bcast_seq,
+				    &ethtool_genl_family, 0, ops->reply_cmd);
+	if (!reply_payload)
+		goto err_skb;
+
+	ret = ethnl_fill_dev(skb, dev, ops->dev_attrtype);
+	if (ret < 0)
+		goto err_skb;
+	ret = ops->fill_reply(skb, req_info);
+	if (ret < 0)
+		goto err_skb;
+	ethnl_free_get_data(ops, req_info);
+	genlmsg_end(skb, reply_payload);
+
+	genlmsg_multicast(&ethtool_genl_family, skb, 0, ETHNL_MCGRP_MONITOR,
+			  GFP_KERNEL);
+	return;
+
+err_skb:
+	nlmsg_free(skb);
+err_data:
+	ethnl_free_get_data(ops, req_info);
+}
+
 /* notifications */
 
 typedef void (*ethnl_notify_handler_t)(struct net_device *dev,
diff --git a/net/ethtool/netlink.h b/net/ethtool/netlink.h
index 5f2299548915..625f912144b1 100644
--- a/net/ethtool/netlink.h
+++ b/net/ethtool/netlink.h
@@ -164,4 +164,115 @@ static inline unsigned int dev_ident_size(void)
 			      nla_total_size(IFNAMSIZ));
 }
 
+/* GET request handling */
+
+struct common_reply_data;
+
+/* The structure holding data for unified processing a GET request consists of
+ * two parts: request info and reply data. Request info starts at offset 0 with
+ * embedded struct common_req_info, is usually filled by ->parse_request() and
+ * is common for all reply messages to one request. Reply data start with
+ * embedded struct common_reply_data and contain data specific to a reply
+ * message (usually one per device for dump requests); this part is filled by
+ * ->prepare_data()
+ */
+
+/**
+ * struct common_req_info - base type of request information for GET requests
+ * @reply_data: pointer to reply data within the same block
+ * @dev:        network device the request is for (may be null)
+ * @req_mask:   request mask, bitmap of requested information
+ * @compact:    true if compact format of bitsets in reply is requested
+ *
+ * This is a common base, additional members may follow after this structure.
+ */
+struct common_req_info {
+	struct common_reply_data	*reply_data;
+	struct net_device		*dev;
+	u32				req_mask;
+	bool				compact;
+};
+
+/**
+ * struct common_reply_data - base type of reply data for GET requests
+ * @dev:       device for current reply message; in single shot requests it is
+ *             equal to &common_req_info.dev; in dumps it's different for each
+ *             reply message
+ * @info_mask: bitmap of information actually provided in reply; it is a subset
+ *             of &common_req_info.req_mask with cleared bits corresponding to
+ *             information which cannot be provided
+ *
+ * This structure is usually followed by additional members filled by
+ * ->prepare_data() and used by ->cleanup().
+ */
+struct common_reply_data {
+	struct net_device		*dev;
+	u32				info_mask;
+};
+
+static inline int ethnl_before_ops(struct net_device *dev)
+{
+	if (dev && dev->ethtool_ops->begin)
+		return dev->ethtool_ops->begin(dev);
+	else
+		return 0;
+}
+
+static inline void ethnl_after_ops(struct net_device *dev)
+{
+	if (dev && dev->ethtool_ops->complete)
+		dev->ethtool_ops->complete(dev);
+}
+
+/**
+ * struct get_request_ops - unified handling of GET requests
+ * @request_cmd:    command id for request (GET)
+ * @reply_cmd:      command id for reply (SET)
+ * @dev_attr:       attribute type for device specification
+ * @data_size:      total length of data structure
+ * @repdata_offset: offset of "reply data" part (struct common_reply_data)
+ * @allow_nodev_do: do not fail if device is not specified for non-dump request
+ * @parse_request:
+ *	parse request message and fill request info; request info is zero
+ *	initialized on entry except reply_data pointer (which is initialized)
+ * @prepare_data:
+ *	retrieve data needed to compose a reply message; reply data are zero
+ *	initialized on entry except for @dev
+ * @reply_size:
+ *	return size of reply message payload without device specification;
+ *	returned size may be bigger than actual reply size but it must suffice
+ *	to hold the reply
+ * @fill_reply:
+ *	fill reply message payload using the data prepared by @prepare_data()
+ * @cleanup
+ *	(optional) called when data are no longer needed; use e.g. to free
+ *	any additional data structures allocated in prepare_data() which are
+ *	not part of the main structure
+ *
+ * Description of variable parts of GET request handling when using the unified
+ * infrastructure. When used, a pointer to an instance of this structure is to
+ * be added to &get_requests array, generic handlers ethnl_get_doit(),
+ * ethnl_get_dumpit(), ethnl_get_start() and ethnl_get_done() used in
+ * @ethnl_genl_ops and (optionally) ethnl_std_notify() as notification handler
+ * in &ethnl_notify_handlers.
+ */
+struct get_request_ops {
+	u8			request_cmd;
+	u8			reply_cmd;
+	u16			dev_attrtype;
+	unsigned int		data_size;
+	unsigned int		repdata_offset;
+	bool			allow_nodev_do;
+
+	int (*parse_request)(struct common_req_info *req_info,
+			     struct sk_buff *skb, struct genl_info *info,
+			     const struct nlmsghdr *nlhdr);
+	int (*prepare_data)(struct common_req_info *req_info,
+			    struct genl_info *info);
+	int (*reply_size)(const struct common_req_info *req_info);
+	int (*fill_reply)(struct sk_buff *skb,
+			  const struct common_req_info *req_info);
+	void (*cleanup)(struct common_req_info *req_info);
+};
+
 #endif /* _NET_ETHTOOL_NETLINK_H */
-- 
2.21.0


  parent reply	other threads:[~2019-03-21 13:40 UTC|newest]

Thread overview: 34+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2019-03-21 13:40 [PATCH net-next v4 00/22] ethtool netlink interface, part 1 Michal Kubecek
2019-03-21 13:31 ` Michal Kubecek
2019-03-21 13:56   ` Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 01/22] rtnetlink: provide permanent hardware address in RTM_NEWLINK Michal Kubecek
2019-03-21 15:47   ` Stephen Hemminger
2019-03-21 20:35   ` Jakub Kicinski
2019-03-22  6:32     ` Michal Kubecek
2019-03-21 21:58   ` David Miller
2019-03-21 13:40 ` [PATCH net-next v4 02/22] netlink: introduce nla_put_bitfield32() Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 03/22] netlink: add strict version of nla_parse_nested() Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 04/22] ethtool: move to its own directory Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 05/22] ethtool: introduce ethtool netlink interface Michal Kubecek
2019-03-21 13:57   ` Andrew Lunn
2019-03-21 14:13     ` Michal Kubecek
2019-03-21 15:25       ` Andrew Lunn
2019-03-21 16:21         ` Jiri Pirko
2019-03-21 16:47         ` Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 06/22] ethtool: helper functions for " Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 07/22] ethtool: netlink bitset handling Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 08/22] ethtool: support for netlink notifications Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 09/22] ethtool: implement EVENT notifications Michal Kubecek
2019-03-21 13:40 ` Michal Kubecek [this message]
2019-03-21 13:40 ` [PATCH net-next v4 11/22] ethtool: move string arrays into common file Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 12/22] ethtool: provide string sets with GET_STRSET request Michal Kubecek
2019-03-21 13:40 ` [PATCH net-next v4 13/22] ethtool: provide driver/device information in GET_INFO request Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 14/22] ethtool: provide timestamping " Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 15/22] ethtool: provide link mode names as a string set Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 16/22] ethtool: provide link settings and link modes in GET_SETTINGS request Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 17/22] ethtool: set link settings and link modes with SET_SETTINGS request Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 18/22] ethtool: provide link state in GET_SETTINGS request Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 19/22] ethtool: provide WoL information " Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 20/22] ethtool: set WoL settings with SET_SETTINGS request Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 21/22] ethtool: provide message level in GET_SETTINGS request Michal Kubecek
2019-03-21 13:41 ` [PATCH net-next v4 22/22] ethtool: set message level with SET_SETTINGS request Michal Kubecek

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=3fadcd2f6d02306175b14fe7083e76db12403ea0.1553170807.git.mkubecek@suse.cz \
    --to=mkubecek@suse.cz \
    --cc=andrew@lunn.ch \
    --cc=davem@davemloft.net \
    --cc=f.fainelli@gmail.com \
    --cc=jakub.kicinski@netronome.com \
    --cc=jiri@resnulli.us \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linville@tuxdriver.com \
    --cc=netdev@vger.kernel.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.