From: Or Har-Toov The current devlink resource infrastructure only supports device-level resources. Some hardware resources are associated with specific ports rather than the entire device, and today we have no way to show resource per-port. Add support for registering and querying resources at the port level, allowing drivers to expose per-port resource limits and usage. Example output: $ devlink port resource show pci/0000:03:00.0/196608: name max_SFs size 20 unit entry pci/0000:03:00.1/262144: name max_SFs size 20 unit entry $ devlink port resource show pci/0000:03:00.0/196608 pci/0000:03:00.0/196608: name max_SFs size 20 unit entry Signed-off-by: Or Har-Toov Reviewed-by: Shay Drori Reviewed-by: Moshe Shemesh Signed-off-by: Tariq Toukan --- Documentation/netlink/specs/devlink.yaml | 23 ++ include/net/devlink.h | 8 + include/uapi/linux/devlink.h | 2 + net/devlink/netlink.c | 2 +- net/devlink/netlink_gen.c | 32 ++- net/devlink/netlink_gen.h | 6 +- net/devlink/port.c | 3 + net/devlink/resource.c | 282 ++++++++++++++++++----- 8 files changed, 301 insertions(+), 57 deletions(-) diff --git a/Documentation/netlink/specs/devlink.yaml b/Documentation/netlink/specs/devlink.yaml index 837112da6738..0290db1b8393 100644 --- a/Documentation/netlink/specs/devlink.yaml +++ b/Documentation/netlink/specs/devlink.yaml @@ -2336,3 +2336,26 @@ operations: - bus-name - dev-name - port-index + + - + name: port-resource-get + doc: Get port resources. + attribute-set: devlink + dont-validate: [strict] + do: + pre: devlink-nl-pre-doit-port + post: devlink-nl-post-doit + request: + value: 85 + attributes: *port-id-attrs + reply: &port-resource-get-reply + value: 85 + attributes: + - bus-name + - dev-name + - port-index + - resource-list + dump: + request: + attributes: *dev-id-attrs + reply: *port-resource-get-reply diff --git a/include/net/devlink.h b/include/net/devlink.h index cb839e0435a1..40335ecc3343 100644 --- a/include/net/devlink.h +++ b/include/net/devlink.h @@ -129,6 +129,7 @@ struct devlink_rate { struct devlink_port { struct list_head list; struct list_head region_list; + struct list_head resource_list; struct devlink *devlink; const struct devlink_port_ops *ops; unsigned int index; @@ -1881,6 +1882,13 @@ void devlink_resources_unregister(struct devlink *devlink); int devl_resource_size_get(struct devlink *devlink, u64 resource_id, u64 *p_resource_size); +int +devl_port_resource_register(struct devlink_port *devlink_port, + const char *resource_name, + u64 resource_size, u64 resource_id, + u64 parent_resource_id, + const struct devlink_resource_size_params *params); +void devl_port_resources_unregister(struct devlink_port *devlink_port); int devl_dpipe_table_resource_set(struct devlink *devlink, const char *table_name, u64 resource_id, u64 resource_units); diff --git a/include/uapi/linux/devlink.h b/include/uapi/linux/devlink.h index e7d6b6d13470..74a541013af6 100644 --- a/include/uapi/linux/devlink.h +++ b/include/uapi/linux/devlink.h @@ -141,6 +141,8 @@ enum devlink_command { DEVLINK_CMD_NOTIFY_FILTER_SET, + DEVLINK_CMD_PORT_RESOURCE_GET, /* can dump */ + /* add new commands above here */ __DEVLINK_CMD_MAX, DEVLINK_CMD_MAX = __DEVLINK_CMD_MAX - 1 diff --git a/net/devlink/netlink.c b/net/devlink/netlink.c index 593605c1b1ef..c78c31779622 100644 --- a/net/devlink/netlink.c +++ b/net/devlink/netlink.c @@ -367,7 +367,7 @@ struct genl_family devlink_nl_family __ro_after_init = { .module = THIS_MODULE, .split_ops = devlink_nl_ops, .n_split_ops = ARRAY_SIZE(devlink_nl_ops), - .resv_start_op = DEVLINK_CMD_SELFTESTS_RUN + 1, + .resv_start_op = DEVLINK_CMD_PORT_RESOURCE_GET + 1, .mcgrps = devlink_nl_mcgrps, .n_mcgrps = ARRAY_SIZE(devlink_nl_mcgrps), .sock_priv_size = sizeof(struct devlink_nl_sock_priv), diff --git a/net/devlink/netlink_gen.c b/net/devlink/netlink_gen.c index f4c61c2b4f22..692d7862183a 100644 --- a/net/devlink/netlink_gen.c +++ b/net/devlink/netlink_gen.c @@ -604,8 +604,21 @@ static const struct nla_policy devlink_notify_filter_set_nl_policy[DEVLINK_ATTR_ [DEVLINK_ATTR_PORT_INDEX] = { .type = NLA_U32, }, }; +/* DEVLINK_CMD_PORT_RESOURCE_GET - do */ +static const struct nla_policy devlink_port_resource_get_do_nl_policy[DEVLINK_ATTR_PORT_INDEX + 1] = { + [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_PORT_INDEX] = { .type = NLA_U32, }, +}; + +/* DEVLINK_CMD_PORT_RESOURCE_GET - dump */ +static const struct nla_policy devlink_port_resource_get_dump_nl_policy[DEVLINK_ATTR_DEV_NAME + 1] = { + [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING, }, + [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING, }, +}; + /* Ops table for devlink */ -const struct genl_split_ops devlink_nl_ops[74] = { +const struct genl_split_ops devlink_nl_ops[76] = { { .cmd = DEVLINK_CMD_GET, .validate = GENL_DONT_VALIDATE_STRICT, @@ -1284,4 +1297,21 @@ const struct genl_split_ops devlink_nl_ops[74] = { .maxattr = DEVLINK_ATTR_PORT_INDEX, .flags = GENL_CMD_CAP_DO, }, + { + .cmd = DEVLINK_CMD_PORT_RESOURCE_GET, + .validate = GENL_DONT_VALIDATE_STRICT, + .pre_doit = devlink_nl_pre_doit_port, + .doit = devlink_nl_port_resource_get_doit, + .post_doit = devlink_nl_post_doit, + .policy = devlink_port_resource_get_do_nl_policy, + .maxattr = DEVLINK_ATTR_PORT_INDEX, + .flags = GENL_CMD_CAP_DO, + }, + { + .cmd = DEVLINK_CMD_PORT_RESOURCE_GET, + .dumpit = devlink_nl_port_resource_get_dumpit, + .policy = devlink_port_resource_get_dump_nl_policy, + .maxattr = DEVLINK_ATTR_DEV_NAME, + .flags = GENL_CMD_CAP_DUMP, + }, }; diff --git a/net/devlink/netlink_gen.h b/net/devlink/netlink_gen.h index 2817d53a0eba..204a665d2fd2 100644 --- a/net/devlink/netlink_gen.h +++ b/net/devlink/netlink_gen.h @@ -18,7 +18,7 @@ extern const struct nla_policy devlink_dl_rate_tc_bws_nl_policy[DEVLINK_RATE_TC_ extern const struct nla_policy devlink_dl_selftest_id_nl_policy[DEVLINK_ATTR_SELFTEST_ID_FLASH + 1]; /* Ops table for devlink */ -extern const struct genl_split_ops devlink_nl_ops[74]; +extern const struct genl_split_ops devlink_nl_ops[76]; int devlink_nl_pre_doit(const struct genl_split_ops *ops, struct sk_buff *skb, struct genl_info *info); @@ -146,5 +146,9 @@ int devlink_nl_selftests_get_dumpit(struct sk_buff *skb, int devlink_nl_selftests_run_doit(struct sk_buff *skb, struct genl_info *info); int devlink_nl_notify_filter_set_doit(struct sk_buff *skb, struct genl_info *info); +int devlink_nl_port_resource_get_doit(struct sk_buff *skb, + struct genl_info *info); +int devlink_nl_port_resource_get_dumpit(struct sk_buff *skb, + struct netlink_callback *cb); #endif /* _LINUX_DEVLINK_GEN_H */ diff --git a/net/devlink/port.c b/net/devlink/port.c index 93d8a25bb920..10d0d88894a3 100644 --- a/net/devlink/port.c +++ b/net/devlink/port.c @@ -1024,6 +1024,7 @@ void devlink_port_init(struct devlink *devlink, return; devlink_port->devlink = devlink; INIT_LIST_HEAD(&devlink_port->region_list); + INIT_LIST_HEAD(&devlink_port->resource_list); devlink_port->initialized = true; } EXPORT_SYMBOL_GPL(devlink_port_init); @@ -1041,6 +1042,7 @@ EXPORT_SYMBOL_GPL(devlink_port_init); void devlink_port_fini(struct devlink_port *devlink_port) { WARN_ON(!list_empty(&devlink_port->region_list)); + WARN_ON(!list_empty(&devlink_port->resource_list)); } EXPORT_SYMBOL_GPL(devlink_port_fini); @@ -1135,6 +1137,7 @@ void devl_port_unregister(struct devlink_port *devlink_port) devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_DEL); xa_erase(&devlink_port->devlink->ports, devlink_port->index); WARN_ON(!list_empty(&devlink_port->reporter_list)); + devlink_port_fini(devlink_port); devlink_port->registered = false; } EXPORT_SYMBOL_GPL(devl_port_unregister); diff --git a/net/devlink/resource.c b/net/devlink/resource.c index 2d6324f3d91f..1c563c76ddb6 100644 --- a/net/devlink/resource.c +++ b/net/devlink/resource.c @@ -36,15 +36,16 @@ struct devlink_resource { }; static struct devlink_resource * -devlink_resource_find(struct devlink *devlink, - struct devlink_resource *resource, u64 resource_id) +devlink_resource_find_by_list(struct list_head *res_list_head, + struct devlink_resource *resource, + u64 resource_id) { struct list_head *resource_list; if (resource) resource_list = &resource->resource_list; else - resource_list = &devlink->resource_list; + resource_list = res_list_head; list_for_each_entry(resource, resource_list, list) { struct devlink_resource *child_resource; @@ -52,14 +53,23 @@ devlink_resource_find(struct devlink *devlink, if (resource->id == resource_id) return resource; - child_resource = devlink_resource_find(devlink, resource, - resource_id); + child_resource = devlink_resource_find_by_list(res_list_head, + resource, + resource_id); if (child_resource) return child_resource; } return NULL; } +static struct devlink_resource * +devlink_resource_find(struct devlink *devlink, + struct devlink_resource *resource, u64 resource_id) +{ + return devlink_resource_find_by_list(&devlink->resource_list, + resource, resource_id); +} + static void devlink_resource_validate_children(struct devlink_resource *resource) { @@ -214,8 +224,10 @@ static int devlink_resource_put(struct devlink *devlink, struct sk_buff *skb, } static int devlink_resource_fill(struct genl_info *info, + struct list_head *resource_list, enum devlink_command cmd, int flags) { + struct devlink_port *devlink_port = info->user_ptr[1]; struct devlink *devlink = info->user_ptr[0]; struct devlink_resource *resource; struct nlattr *resources_attr; @@ -226,8 +238,11 @@ static int devlink_resource_fill(struct genl_info *info, int i; int err; - resource = list_first_entry(&devlink->resource_list, - struct devlink_resource, list); + if (list_empty(resource_list)) + return -EOPNOTSUPP; + + resource = list_first_entry(resource_list, struct devlink_resource, + list); start_again: err = devlink_nl_msg_reply_and_new(&skb, info); if (err) @@ -243,6 +258,10 @@ static int devlink_resource_fill(struct genl_info *info, if (devlink_nl_put_handle(skb, devlink)) goto nla_put_failure; + if (devlink_port && nla_put_u32(skb, DEVLINK_ATTR_PORT_INDEX, + devlink_port->index)) + goto nla_put_failure; + resources_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_RESOURCE_LIST); if (!resources_attr) @@ -250,7 +269,7 @@ static int devlink_resource_fill(struct genl_info *info, incomplete = false; i = 0; - list_for_each_entry_from(resource, &devlink->resource_list, list) { + list_for_each_entry_from(resource, resource_list, list) { err = devlink_resource_put(devlink, skb, resource); if (err) { if (!i) @@ -286,10 +305,8 @@ int devlink_nl_resource_dump_doit(struct sk_buff *skb, struct genl_info *info) { struct devlink *devlink = info->user_ptr[0]; - if (list_empty(&devlink->resource_list)) - return -EOPNOTSUPP; - - return devlink_resource_fill(info, DEVLINK_CMD_RESOURCE_DUMP, 0); + return devlink_resource_fill(info, &devlink->resource_list, + DEVLINK_CMD_RESOURCE_DUMP, 0); } int devlink_resources_validate(struct devlink *devlink, @@ -314,26 +331,12 @@ int devlink_resources_validate(struct devlink *devlink, return err; } -/** - * devl_resource_register - devlink resource register - * - * @devlink: devlink - * @resource_name: resource's name - * @resource_size: resource's size - * @resource_id: resource's id - * @parent_resource_id: resource's parent id - * @size_params: size parameters - * - * Generic resources should reuse the same names across drivers. - * Please see the generic resources list at: - * Documentation/networking/devlink/devlink-resource.rst - */ -int devl_resource_register(struct devlink *devlink, - const char *resource_name, - u64 resource_size, - u64 resource_id, - u64 parent_resource_id, - const struct devlink_resource_size_params *size_params) +static int +devl_resource_reg_by_list(struct devlink *devlink, + struct list_head *res_list_head, + const char *resource_name, u64 resource_size, + u64 resource_id, u64 parent_res_id, + const struct devlink_resource_size_params *params) { struct devlink_resource *resource; struct list_head *resource_list; @@ -341,9 +344,10 @@ int devl_resource_register(struct devlink *devlink, lockdep_assert_held(&devlink->lock); - top_hierarchy = parent_resource_id == DEVLINK_RESOURCE_ID_PARENT_TOP; + top_hierarchy = parent_res_id == DEVLINK_RESOURCE_ID_PARENT_TOP; - resource = devlink_resource_find(devlink, NULL, resource_id); + resource = devlink_resource_find_by_list(res_list_head, NULL, + resource_id); if (resource) return -EEXIST; @@ -352,15 +356,15 @@ int devl_resource_register(struct devlink *devlink, return -ENOMEM; if (top_hierarchy) { - resource_list = &devlink->resource_list; + resource_list = res_list_head; } else { - struct devlink_resource *parent_resource; + struct devlink_resource *parent_res; - parent_resource = devlink_resource_find(devlink, NULL, - parent_resource_id); - if (parent_resource) { - resource_list = &parent_resource->resource_list; - resource->parent = parent_resource; + parent_res = devlink_resource_find_by_list(res_list_head, NULL, + parent_res_id); + if (parent_res) { + resource_list = &parent_res->resource_list; + resource->parent = parent_res; } else { kfree(resource); return -EINVAL; @@ -372,46 +376,78 @@ int devl_resource_register(struct devlink *devlink, resource->size_new = resource_size; resource->id = resource_id; resource->size_valid = true; - memcpy(&resource->size_params, size_params, - sizeof(resource->size_params)); + memcpy(&resource->size_params, params, sizeof(resource->size_params)); INIT_LIST_HEAD(&resource->resource_list); list_add_tail(&resource->list, resource_list); return 0; } + +/** + * devl_resource_register - devlink resource register + * + * @devlink: devlink + * @resource_name: resource's name + * @resource_size: resource's size + * @resource_id: resource's id + * @parent_resource_id: resource's parent id + * @params: size parameters + * + * Generic resources should reuse the same names across drivers. + * Please see the generic resources list at: + * Documentation/networking/devlink/devlink-resource.rst + * + * Return: 0 on success, negative error code otherwise. + */ +int devl_resource_register(struct devlink *devlink, const char *resource_name, + u64 resource_size, u64 resource_id, + u64 parent_resource_id, + const struct devlink_resource_size_params *params) +{ + return devl_resource_reg_by_list(devlink, &devlink->resource_list, + resource_name, resource_size, + resource_id, parent_resource_id, + params); +} EXPORT_SYMBOL_GPL(devl_resource_register); -static void devlink_resource_unregister(struct devlink *devlink, - struct devlink_resource *resource) +static void devlink_resource_unregister(struct devlink_resource *resource) { struct devlink_resource *tmp, *child_resource; list_for_each_entry_safe(child_resource, tmp, &resource->resource_list, list) { - devlink_resource_unregister(devlink, child_resource); + devlink_resource_unregister(child_resource); list_del(&child_resource->list); kfree(child_resource); } } -/** - * devl_resources_unregister - free all resources - * - * @devlink: devlink - */ -void devl_resources_unregister(struct devlink *devlink) +static void +devl_resources_unregister_by_list(struct devlink *devlink, + struct list_head *res_list_head) { struct devlink_resource *tmp, *child_resource; lockdep_assert_held(&devlink->lock); - list_for_each_entry_safe(child_resource, tmp, &devlink->resource_list, + list_for_each_entry_safe(child_resource, tmp, res_list_head, list) { - devlink_resource_unregister(devlink, child_resource); + devlink_resource_unregister(child_resource); list_del(&child_resource->list); kfree(child_resource); } } + +/** + * devl_resources_unregister - free all resources + * + * @devlink: devlink + */ +void devl_resources_unregister(struct devlink *devlink) +{ + devl_resources_unregister_by_list(devlink, &devlink->resource_list); +} EXPORT_SYMBOL_GPL(devl_resources_unregister); /** @@ -502,3 +538,141 @@ void devl_resource_occ_get_unregister(struct devlink *devlink, resource->occ_get_priv = NULL; } EXPORT_SYMBOL_GPL(devl_resource_occ_get_unregister); + +/** + * devl_port_resource_register - devlink port resource register + * + * @devlink_port: devlink port + * @resource_name: resource's name + * @resource_size: resource's size + * @resource_id: resource's id + * @parent_resource_id: resource's parent id + * @params: size parameters for the resource + * + * Generic resources should reuse the same names across drivers. + * Please see the generic resources list at: + * Documentation/networking/devlink/devlink-resource.rst + * + * Return: 0 on success, negative error code otherwise. + */ +int +devl_port_resource_register(struct devlink_port *devlink_port, + const char *resource_name, + u64 resource_size, u64 resource_id, + u64 parent_resource_id, + const struct devlink_resource_size_params *params) +{ + return devl_resource_reg_by_list(devlink_port->devlink, + &devlink_port->resource_list, + resource_name, resource_size, + resource_id, parent_resource_id, + params); +} +EXPORT_SYMBOL_GPL(devl_port_resource_register); + +/** + * devl_port_resources_unregister - unregister all devlink port resources + * + * @devlink_port: devlink port + */ +void devl_port_resources_unregister(struct devlink_port *devlink_port) +{ + devl_resources_unregister_by_list(devlink_port->devlink, + &devlink_port->resource_list); +} +EXPORT_SYMBOL_GPL(devl_port_resources_unregister); + +static int devlink_nl_port_resource_fill(struct sk_buff *msg, + struct devlink_port *devlink_port, + enum devlink_command cmd, + u32 portid, u32 seq, int flags) +{ + struct devlink *devlink = devlink_port->devlink; + struct devlink_resource *resource; + struct nlattr *resources_attr; + void *hdr; + + if (list_empty(&devlink_port->resource_list)) + return 0; + + hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd); + if (!hdr) + return -EMSGSIZE; + + if (devlink_nl_put_handle(msg, devlink)) + goto nla_put_failure; + if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, devlink_port->index)) + goto nla_put_failure; + + resources_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_RESOURCE_LIST); + if (!resources_attr) + goto nla_put_failure; + + list_for_each_entry(resource, &devlink_port->resource_list, list) { + if (devlink_resource_put(devlink, msg, resource)) { + nla_nest_cancel(msg, resources_attr); + goto nla_put_failure; + } + } + nla_nest_end(msg, resources_attr); + genlmsg_end(msg, hdr); + return 0; + +nla_put_failure: + genlmsg_cancel(msg, hdr); + return -EMSGSIZE; +} + +int devlink_nl_port_resource_get_doit(struct sk_buff *skb, + struct genl_info *info) +{ + struct devlink_port *devlink_port = info->user_ptr[1]; + struct sk_buff *msg; + int err; + + msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); + if (!msg) + return -ENOMEM; + + err = devlink_nl_port_resource_fill(msg, devlink_port, + DEVLINK_CMD_PORT_RESOURCE_GET, + info->snd_portid, info->snd_seq, 0); + if (err) { + nlmsg_free(msg); + return err; + } + + return genlmsg_reply(msg, info); +} + +static int +devlink_nl_port_resource_get_dump_one(struct sk_buff *msg, + struct devlink *devlink, + struct netlink_callback *cb, int flags) +{ + struct devlink_nl_dump_state *state = devlink_dump_state(cb); + u32 cmd = DEVLINK_CMD_PORT_RESOURCE_GET; + struct devlink_port *devlink_port; + unsigned long port_index; + int err; + + xa_for_each_start(&devlink->ports, port_index, devlink_port, + state->idx) { + err = devlink_nl_port_resource_fill(msg, devlink_port, cmd, + NETLINK_CB(cb->skb).portid, + cb->nlh->nlmsg_seq, flags); + if (err) { + state->idx = port_index; + return err; + } + } + + return 0; +} + +int devlink_nl_port_resource_get_dumpit(struct sk_buff *skb, + struct netlink_callback *cb) +{ + return devlink_nl_dumpit(skb, cb, + devlink_nl_port_resource_get_dump_one); +} -- 2.40.1