From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org X-Spam-Level: X-Spam-Status: No, score=-7.0 required=3.0 tests=HEADER_FROM_DIFFERENT_DOMAINS, INCLUDES_PATCH,MAILING_LIST_MULTI,SIGNED_OFF_BY,SPF_PASS,URIBL_BLOCKED autolearn=ham autolearn_force=no version=3.4.0 Received: from mail.kernel.org (mail.kernel.org [198.145.29.99]) by smtp.lore.kernel.org (Postfix) with ESMTP id 83AFEC43381 for ; Thu, 21 Mar 2019 13:40:59 +0000 (UTC) Received: from vger.kernel.org (vger.kernel.org [209.132.180.67]) by mail.kernel.org (Postfix) with ESMTP id 480412075E for ; Thu, 21 Mar 2019 13:40:59 +0000 (UTC) Received: (majordomo@vger.kernel.org) by vger.kernel.org via listexpand id S1728541AbfCUNk5 (ORCPT ); Thu, 21 Mar 2019 09:40:57 -0400 Received: from mx2.suse.de ([195.135.220.15]:36354 "EHLO mx1.suse.de" rhost-flags-OK-OK-OK-FAIL) by vger.kernel.org with ESMTP id S1728465AbfCUNku (ORCPT ); Thu, 21 Mar 2019 09:40:50 -0400 X-Virus-Scanned: by amavisd-new at test-mx.suse.de Received: from relay2.suse.de (unknown [195.135.220.254]) by mx1.suse.de (Postfix) with ESMTP id 9EAA2AF9A; Thu, 21 Mar 2019 13:40:48 +0000 (UTC) Received: by unicorn.suse.cz (Postfix, from userid 1000) id 4A5EFE00BF; Thu, 21 Mar 2019 14:40:48 +0100 (CET) Message-Id: <3fadcd2f6d02306175b14fe7083e76db12403ea0.1553170807.git.mkubecek@suse.cz> In-Reply-To: References: From: Michal Kubecek Subject: [PATCH net-next v4 10/22] ethtool: generic handlers for GET requests To: David Miller , netdev@vger.kernel.org Cc: Jakub Kicinski , Jiri Pirko , Andrew Lunn , Florian Fainelli , John Linville , linux-kernel@vger.kernel.org Date: Thu, 21 Mar 2019 14:40:48 +0100 (CET) Sender: linux-kernel-owner@vger.kernel.org Precedence: bulk List-ID: X-Mailing-List: linux-kernel@vger.kernel.org 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 --- 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, + ðtool_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, + ðtool_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(ðtool_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 ðnl_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