162306a36Sopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later
262306a36Sopenharmony_ci/*
362306a36Sopenharmony_ci * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
462306a36Sopenharmony_ci * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com>
562306a36Sopenharmony_ci */
662306a36Sopenharmony_ci
762306a36Sopenharmony_ci#include <net/genetlink.h>
862306a36Sopenharmony_ci#include <net/sock.h>
962306a36Sopenharmony_ci#include <trace/events/devlink.h>
1062306a36Sopenharmony_ci#include "devl_internal.h"
1162306a36Sopenharmony_ci
1262306a36Sopenharmony_cistruct devlink_fmsg_item {
1362306a36Sopenharmony_ci	struct list_head list;
1462306a36Sopenharmony_ci	int attrtype;
1562306a36Sopenharmony_ci	u8 nla_type;
1662306a36Sopenharmony_ci	u16 len;
1762306a36Sopenharmony_ci	int value[];
1862306a36Sopenharmony_ci};
1962306a36Sopenharmony_ci
2062306a36Sopenharmony_cistruct devlink_fmsg {
2162306a36Sopenharmony_ci	struct list_head item_list;
2262306a36Sopenharmony_ci	bool putting_binary; /* This flag forces enclosing of binary data
2362306a36Sopenharmony_ci			      * in an array brackets. It forces using
2462306a36Sopenharmony_ci			      * of designated API:
2562306a36Sopenharmony_ci			      * devlink_fmsg_binary_pair_nest_start()
2662306a36Sopenharmony_ci			      * devlink_fmsg_binary_pair_nest_end()
2762306a36Sopenharmony_ci			      */
2862306a36Sopenharmony_ci};
2962306a36Sopenharmony_ci
3062306a36Sopenharmony_cistatic struct devlink_fmsg *devlink_fmsg_alloc(void)
3162306a36Sopenharmony_ci{
3262306a36Sopenharmony_ci	struct devlink_fmsg *fmsg;
3362306a36Sopenharmony_ci
3462306a36Sopenharmony_ci	fmsg = kzalloc(sizeof(*fmsg), GFP_KERNEL);
3562306a36Sopenharmony_ci	if (!fmsg)
3662306a36Sopenharmony_ci		return NULL;
3762306a36Sopenharmony_ci
3862306a36Sopenharmony_ci	INIT_LIST_HEAD(&fmsg->item_list);
3962306a36Sopenharmony_ci
4062306a36Sopenharmony_ci	return fmsg;
4162306a36Sopenharmony_ci}
4262306a36Sopenharmony_ci
4362306a36Sopenharmony_cistatic void devlink_fmsg_free(struct devlink_fmsg *fmsg)
4462306a36Sopenharmony_ci{
4562306a36Sopenharmony_ci	struct devlink_fmsg_item *item, *tmp;
4662306a36Sopenharmony_ci
4762306a36Sopenharmony_ci	list_for_each_entry_safe(item, tmp, &fmsg->item_list, list) {
4862306a36Sopenharmony_ci		list_del(&item->list);
4962306a36Sopenharmony_ci		kfree(item);
5062306a36Sopenharmony_ci	}
5162306a36Sopenharmony_ci	kfree(fmsg);
5262306a36Sopenharmony_ci}
5362306a36Sopenharmony_ci
5462306a36Sopenharmony_cistruct devlink_health_reporter {
5562306a36Sopenharmony_ci	struct list_head list;
5662306a36Sopenharmony_ci	void *priv;
5762306a36Sopenharmony_ci	const struct devlink_health_reporter_ops *ops;
5862306a36Sopenharmony_ci	struct devlink *devlink;
5962306a36Sopenharmony_ci	struct devlink_port *devlink_port;
6062306a36Sopenharmony_ci	struct devlink_fmsg *dump_fmsg;
6162306a36Sopenharmony_ci	u64 graceful_period;
6262306a36Sopenharmony_ci	bool auto_recover;
6362306a36Sopenharmony_ci	bool auto_dump;
6462306a36Sopenharmony_ci	u8 health_state;
6562306a36Sopenharmony_ci	u64 dump_ts;
6662306a36Sopenharmony_ci	u64 dump_real_ts;
6762306a36Sopenharmony_ci	u64 error_count;
6862306a36Sopenharmony_ci	u64 recovery_count;
6962306a36Sopenharmony_ci	u64 last_recovery_ts;
7062306a36Sopenharmony_ci};
7162306a36Sopenharmony_ci
7262306a36Sopenharmony_civoid *
7362306a36Sopenharmony_cidevlink_health_reporter_priv(struct devlink_health_reporter *reporter)
7462306a36Sopenharmony_ci{
7562306a36Sopenharmony_ci	return reporter->priv;
7662306a36Sopenharmony_ci}
7762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_health_reporter_priv);
7862306a36Sopenharmony_ci
7962306a36Sopenharmony_cistatic struct devlink_health_reporter *
8062306a36Sopenharmony_ci__devlink_health_reporter_find_by_name(struct list_head *reporter_list,
8162306a36Sopenharmony_ci				       const char *reporter_name)
8262306a36Sopenharmony_ci{
8362306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
8462306a36Sopenharmony_ci
8562306a36Sopenharmony_ci	list_for_each_entry(reporter, reporter_list, list)
8662306a36Sopenharmony_ci		if (!strcmp(reporter->ops->name, reporter_name))
8762306a36Sopenharmony_ci			return reporter;
8862306a36Sopenharmony_ci	return NULL;
8962306a36Sopenharmony_ci}
9062306a36Sopenharmony_ci
9162306a36Sopenharmony_cistatic struct devlink_health_reporter *
9262306a36Sopenharmony_cidevlink_health_reporter_find_by_name(struct devlink *devlink,
9362306a36Sopenharmony_ci				     const char *reporter_name)
9462306a36Sopenharmony_ci{
9562306a36Sopenharmony_ci	return __devlink_health_reporter_find_by_name(&devlink->reporter_list,
9662306a36Sopenharmony_ci						      reporter_name);
9762306a36Sopenharmony_ci}
9862306a36Sopenharmony_ci
9962306a36Sopenharmony_cistatic struct devlink_health_reporter *
10062306a36Sopenharmony_cidevlink_port_health_reporter_find_by_name(struct devlink_port *devlink_port,
10162306a36Sopenharmony_ci					  const char *reporter_name)
10262306a36Sopenharmony_ci{
10362306a36Sopenharmony_ci	return __devlink_health_reporter_find_by_name(&devlink_port->reporter_list,
10462306a36Sopenharmony_ci						      reporter_name);
10562306a36Sopenharmony_ci}
10662306a36Sopenharmony_ci
10762306a36Sopenharmony_cistatic struct devlink_health_reporter *
10862306a36Sopenharmony_ci__devlink_health_reporter_create(struct devlink *devlink,
10962306a36Sopenharmony_ci				 const struct devlink_health_reporter_ops *ops,
11062306a36Sopenharmony_ci				 u64 graceful_period, void *priv)
11162306a36Sopenharmony_ci{
11262306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
11362306a36Sopenharmony_ci
11462306a36Sopenharmony_ci	if (WARN_ON(graceful_period && !ops->recover))
11562306a36Sopenharmony_ci		return ERR_PTR(-EINVAL);
11662306a36Sopenharmony_ci
11762306a36Sopenharmony_ci	reporter = kzalloc(sizeof(*reporter), GFP_KERNEL);
11862306a36Sopenharmony_ci	if (!reporter)
11962306a36Sopenharmony_ci		return ERR_PTR(-ENOMEM);
12062306a36Sopenharmony_ci
12162306a36Sopenharmony_ci	reporter->priv = priv;
12262306a36Sopenharmony_ci	reporter->ops = ops;
12362306a36Sopenharmony_ci	reporter->devlink = devlink;
12462306a36Sopenharmony_ci	reporter->graceful_period = graceful_period;
12562306a36Sopenharmony_ci	reporter->auto_recover = !!ops->recover;
12662306a36Sopenharmony_ci	reporter->auto_dump = !!ops->dump;
12762306a36Sopenharmony_ci	return reporter;
12862306a36Sopenharmony_ci}
12962306a36Sopenharmony_ci
13062306a36Sopenharmony_ci/**
13162306a36Sopenharmony_ci * devl_port_health_reporter_create() - create devlink health reporter for
13262306a36Sopenharmony_ci *                                      specified port instance
13362306a36Sopenharmony_ci *
13462306a36Sopenharmony_ci * @port: devlink_port to which health reports will relate
13562306a36Sopenharmony_ci * @ops: devlink health reporter ops
13662306a36Sopenharmony_ci * @graceful_period: min time (in msec) between recovery attempts
13762306a36Sopenharmony_ci * @priv: driver priv pointer
13862306a36Sopenharmony_ci */
13962306a36Sopenharmony_cistruct devlink_health_reporter *
14062306a36Sopenharmony_cidevl_port_health_reporter_create(struct devlink_port *port,
14162306a36Sopenharmony_ci				 const struct devlink_health_reporter_ops *ops,
14262306a36Sopenharmony_ci				 u64 graceful_period, void *priv)
14362306a36Sopenharmony_ci{
14462306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
14562306a36Sopenharmony_ci
14662306a36Sopenharmony_ci	devl_assert_locked(port->devlink);
14762306a36Sopenharmony_ci
14862306a36Sopenharmony_ci	if (__devlink_health_reporter_find_by_name(&port->reporter_list,
14962306a36Sopenharmony_ci						   ops->name))
15062306a36Sopenharmony_ci		return ERR_PTR(-EEXIST);
15162306a36Sopenharmony_ci
15262306a36Sopenharmony_ci	reporter = __devlink_health_reporter_create(port->devlink, ops,
15362306a36Sopenharmony_ci						    graceful_period, priv);
15462306a36Sopenharmony_ci	if (IS_ERR(reporter))
15562306a36Sopenharmony_ci		return reporter;
15662306a36Sopenharmony_ci
15762306a36Sopenharmony_ci	reporter->devlink_port = port;
15862306a36Sopenharmony_ci	list_add_tail(&reporter->list, &port->reporter_list);
15962306a36Sopenharmony_ci	return reporter;
16062306a36Sopenharmony_ci}
16162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devl_port_health_reporter_create);
16262306a36Sopenharmony_ci
16362306a36Sopenharmony_cistruct devlink_health_reporter *
16462306a36Sopenharmony_cidevlink_port_health_reporter_create(struct devlink_port *port,
16562306a36Sopenharmony_ci				    const struct devlink_health_reporter_ops *ops,
16662306a36Sopenharmony_ci				    u64 graceful_period, void *priv)
16762306a36Sopenharmony_ci{
16862306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
16962306a36Sopenharmony_ci	struct devlink *devlink = port->devlink;
17062306a36Sopenharmony_ci
17162306a36Sopenharmony_ci	devl_lock(devlink);
17262306a36Sopenharmony_ci	reporter = devl_port_health_reporter_create(port, ops,
17362306a36Sopenharmony_ci						    graceful_period, priv);
17462306a36Sopenharmony_ci	devl_unlock(devlink);
17562306a36Sopenharmony_ci	return reporter;
17662306a36Sopenharmony_ci}
17762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_port_health_reporter_create);
17862306a36Sopenharmony_ci
17962306a36Sopenharmony_ci/**
18062306a36Sopenharmony_ci * devl_health_reporter_create - create devlink health reporter
18162306a36Sopenharmony_ci *
18262306a36Sopenharmony_ci * @devlink: devlink instance which the health reports will relate
18362306a36Sopenharmony_ci * @ops: devlink health reporter ops
18462306a36Sopenharmony_ci * @graceful_period: min time (in msec) between recovery attempts
18562306a36Sopenharmony_ci * @priv: driver priv pointer
18662306a36Sopenharmony_ci */
18762306a36Sopenharmony_cistruct devlink_health_reporter *
18862306a36Sopenharmony_cidevl_health_reporter_create(struct devlink *devlink,
18962306a36Sopenharmony_ci			    const struct devlink_health_reporter_ops *ops,
19062306a36Sopenharmony_ci			    u64 graceful_period, void *priv)
19162306a36Sopenharmony_ci{
19262306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
19362306a36Sopenharmony_ci
19462306a36Sopenharmony_ci	devl_assert_locked(devlink);
19562306a36Sopenharmony_ci
19662306a36Sopenharmony_ci	if (devlink_health_reporter_find_by_name(devlink, ops->name))
19762306a36Sopenharmony_ci		return ERR_PTR(-EEXIST);
19862306a36Sopenharmony_ci
19962306a36Sopenharmony_ci	reporter = __devlink_health_reporter_create(devlink, ops,
20062306a36Sopenharmony_ci						    graceful_period, priv);
20162306a36Sopenharmony_ci	if (IS_ERR(reporter))
20262306a36Sopenharmony_ci		return reporter;
20362306a36Sopenharmony_ci
20462306a36Sopenharmony_ci	list_add_tail(&reporter->list, &devlink->reporter_list);
20562306a36Sopenharmony_ci	return reporter;
20662306a36Sopenharmony_ci}
20762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devl_health_reporter_create);
20862306a36Sopenharmony_ci
20962306a36Sopenharmony_cistruct devlink_health_reporter *
21062306a36Sopenharmony_cidevlink_health_reporter_create(struct devlink *devlink,
21162306a36Sopenharmony_ci			       const struct devlink_health_reporter_ops *ops,
21262306a36Sopenharmony_ci			       u64 graceful_period, void *priv)
21362306a36Sopenharmony_ci{
21462306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
21562306a36Sopenharmony_ci
21662306a36Sopenharmony_ci	devl_lock(devlink);
21762306a36Sopenharmony_ci	reporter = devl_health_reporter_create(devlink, ops,
21862306a36Sopenharmony_ci					       graceful_period, priv);
21962306a36Sopenharmony_ci	devl_unlock(devlink);
22062306a36Sopenharmony_ci	return reporter;
22162306a36Sopenharmony_ci}
22262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_health_reporter_create);
22362306a36Sopenharmony_ci
22462306a36Sopenharmony_cistatic void
22562306a36Sopenharmony_cidevlink_health_reporter_free(struct devlink_health_reporter *reporter)
22662306a36Sopenharmony_ci{
22762306a36Sopenharmony_ci	if (reporter->dump_fmsg)
22862306a36Sopenharmony_ci		devlink_fmsg_free(reporter->dump_fmsg);
22962306a36Sopenharmony_ci	kfree(reporter);
23062306a36Sopenharmony_ci}
23162306a36Sopenharmony_ci
23262306a36Sopenharmony_ci/**
23362306a36Sopenharmony_ci * devl_health_reporter_destroy() - destroy devlink health reporter
23462306a36Sopenharmony_ci *
23562306a36Sopenharmony_ci * @reporter: devlink health reporter to destroy
23662306a36Sopenharmony_ci */
23762306a36Sopenharmony_civoid
23862306a36Sopenharmony_cidevl_health_reporter_destroy(struct devlink_health_reporter *reporter)
23962306a36Sopenharmony_ci{
24062306a36Sopenharmony_ci	devl_assert_locked(reporter->devlink);
24162306a36Sopenharmony_ci
24262306a36Sopenharmony_ci	list_del(&reporter->list);
24362306a36Sopenharmony_ci	devlink_health_reporter_free(reporter);
24462306a36Sopenharmony_ci}
24562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devl_health_reporter_destroy);
24662306a36Sopenharmony_ci
24762306a36Sopenharmony_civoid
24862306a36Sopenharmony_cidevlink_health_reporter_destroy(struct devlink_health_reporter *reporter)
24962306a36Sopenharmony_ci{
25062306a36Sopenharmony_ci	struct devlink *devlink = reporter->devlink;
25162306a36Sopenharmony_ci
25262306a36Sopenharmony_ci	devl_lock(devlink);
25362306a36Sopenharmony_ci	devl_health_reporter_destroy(reporter);
25462306a36Sopenharmony_ci	devl_unlock(devlink);
25562306a36Sopenharmony_ci}
25662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_health_reporter_destroy);
25762306a36Sopenharmony_ci
25862306a36Sopenharmony_cistatic int
25962306a36Sopenharmony_cidevlink_nl_health_reporter_fill(struct sk_buff *msg,
26062306a36Sopenharmony_ci				struct devlink_health_reporter *reporter,
26162306a36Sopenharmony_ci				enum devlink_command cmd, u32 portid,
26262306a36Sopenharmony_ci				u32 seq, int flags)
26362306a36Sopenharmony_ci{
26462306a36Sopenharmony_ci	struct devlink *devlink = reporter->devlink;
26562306a36Sopenharmony_ci	struct nlattr *reporter_attr;
26662306a36Sopenharmony_ci	void *hdr;
26762306a36Sopenharmony_ci
26862306a36Sopenharmony_ci	hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd);
26962306a36Sopenharmony_ci	if (!hdr)
27062306a36Sopenharmony_ci		return -EMSGSIZE;
27162306a36Sopenharmony_ci
27262306a36Sopenharmony_ci	if (devlink_nl_put_handle(msg, devlink))
27362306a36Sopenharmony_ci		goto genlmsg_cancel;
27462306a36Sopenharmony_ci
27562306a36Sopenharmony_ci	if (reporter->devlink_port) {
27662306a36Sopenharmony_ci		if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, reporter->devlink_port->index))
27762306a36Sopenharmony_ci			goto genlmsg_cancel;
27862306a36Sopenharmony_ci	}
27962306a36Sopenharmony_ci	reporter_attr = nla_nest_start_noflag(msg,
28062306a36Sopenharmony_ci					      DEVLINK_ATTR_HEALTH_REPORTER);
28162306a36Sopenharmony_ci	if (!reporter_attr)
28262306a36Sopenharmony_ci		goto genlmsg_cancel;
28362306a36Sopenharmony_ci	if (nla_put_string(msg, DEVLINK_ATTR_HEALTH_REPORTER_NAME,
28462306a36Sopenharmony_ci			   reporter->ops->name))
28562306a36Sopenharmony_ci		goto reporter_nest_cancel;
28662306a36Sopenharmony_ci	if (nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_STATE,
28762306a36Sopenharmony_ci		       reporter->health_state))
28862306a36Sopenharmony_ci		goto reporter_nest_cancel;
28962306a36Sopenharmony_ci	if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_ERR_COUNT,
29062306a36Sopenharmony_ci			      reporter->error_count, DEVLINK_ATTR_PAD))
29162306a36Sopenharmony_ci		goto reporter_nest_cancel;
29262306a36Sopenharmony_ci	if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT,
29362306a36Sopenharmony_ci			      reporter->recovery_count, DEVLINK_ATTR_PAD))
29462306a36Sopenharmony_ci		goto reporter_nest_cancel;
29562306a36Sopenharmony_ci	if (reporter->ops->recover &&
29662306a36Sopenharmony_ci	    nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD,
29762306a36Sopenharmony_ci			      reporter->graceful_period,
29862306a36Sopenharmony_ci			      DEVLINK_ATTR_PAD))
29962306a36Sopenharmony_ci		goto reporter_nest_cancel;
30062306a36Sopenharmony_ci	if (reporter->ops->recover &&
30162306a36Sopenharmony_ci	    nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER,
30262306a36Sopenharmony_ci		       reporter->auto_recover))
30362306a36Sopenharmony_ci		goto reporter_nest_cancel;
30462306a36Sopenharmony_ci	if (reporter->dump_fmsg &&
30562306a36Sopenharmony_ci	    nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS,
30662306a36Sopenharmony_ci			      jiffies_to_msecs(reporter->dump_ts),
30762306a36Sopenharmony_ci			      DEVLINK_ATTR_PAD))
30862306a36Sopenharmony_ci		goto reporter_nest_cancel;
30962306a36Sopenharmony_ci	if (reporter->dump_fmsg &&
31062306a36Sopenharmony_ci	    nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS_NS,
31162306a36Sopenharmony_ci			      reporter->dump_real_ts, DEVLINK_ATTR_PAD))
31262306a36Sopenharmony_ci		goto reporter_nest_cancel;
31362306a36Sopenharmony_ci	if (reporter->ops->dump &&
31462306a36Sopenharmony_ci	    nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP,
31562306a36Sopenharmony_ci		       reporter->auto_dump))
31662306a36Sopenharmony_ci		goto reporter_nest_cancel;
31762306a36Sopenharmony_ci
31862306a36Sopenharmony_ci	nla_nest_end(msg, reporter_attr);
31962306a36Sopenharmony_ci	genlmsg_end(msg, hdr);
32062306a36Sopenharmony_ci	return 0;
32162306a36Sopenharmony_ci
32262306a36Sopenharmony_cireporter_nest_cancel:
32362306a36Sopenharmony_ci	nla_nest_cancel(msg, reporter_attr);
32462306a36Sopenharmony_cigenlmsg_cancel:
32562306a36Sopenharmony_ci	genlmsg_cancel(msg, hdr);
32662306a36Sopenharmony_ci	return -EMSGSIZE;
32762306a36Sopenharmony_ci}
32862306a36Sopenharmony_ci
32962306a36Sopenharmony_cistatic struct devlink_health_reporter *
33062306a36Sopenharmony_cidevlink_health_reporter_get_from_attrs(struct devlink *devlink,
33162306a36Sopenharmony_ci				       struct nlattr **attrs)
33262306a36Sopenharmony_ci{
33362306a36Sopenharmony_ci	struct devlink_port *devlink_port;
33462306a36Sopenharmony_ci	char *reporter_name;
33562306a36Sopenharmony_ci
33662306a36Sopenharmony_ci	if (!attrs[DEVLINK_ATTR_HEALTH_REPORTER_NAME])
33762306a36Sopenharmony_ci		return NULL;
33862306a36Sopenharmony_ci
33962306a36Sopenharmony_ci	reporter_name = nla_data(attrs[DEVLINK_ATTR_HEALTH_REPORTER_NAME]);
34062306a36Sopenharmony_ci	devlink_port = devlink_port_get_from_attrs(devlink, attrs);
34162306a36Sopenharmony_ci	if (IS_ERR(devlink_port))
34262306a36Sopenharmony_ci		return devlink_health_reporter_find_by_name(devlink,
34362306a36Sopenharmony_ci							    reporter_name);
34462306a36Sopenharmony_ci	else
34562306a36Sopenharmony_ci		return devlink_port_health_reporter_find_by_name(devlink_port,
34662306a36Sopenharmony_ci								 reporter_name);
34762306a36Sopenharmony_ci}
34862306a36Sopenharmony_ci
34962306a36Sopenharmony_cistatic struct devlink_health_reporter *
35062306a36Sopenharmony_cidevlink_health_reporter_get_from_info(struct devlink *devlink,
35162306a36Sopenharmony_ci				      struct genl_info *info)
35262306a36Sopenharmony_ci{
35362306a36Sopenharmony_ci	return devlink_health_reporter_get_from_attrs(devlink, info->attrs);
35462306a36Sopenharmony_ci}
35562306a36Sopenharmony_ci
35662306a36Sopenharmony_ciint devlink_nl_health_reporter_get_doit(struct sk_buff *skb,
35762306a36Sopenharmony_ci					struct genl_info *info)
35862306a36Sopenharmony_ci{
35962306a36Sopenharmony_ci	struct devlink *devlink = info->user_ptr[0];
36062306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
36162306a36Sopenharmony_ci	struct sk_buff *msg;
36262306a36Sopenharmony_ci	int err;
36362306a36Sopenharmony_ci
36462306a36Sopenharmony_ci	reporter = devlink_health_reporter_get_from_info(devlink, info);
36562306a36Sopenharmony_ci	if (!reporter)
36662306a36Sopenharmony_ci		return -EINVAL;
36762306a36Sopenharmony_ci
36862306a36Sopenharmony_ci	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
36962306a36Sopenharmony_ci	if (!msg)
37062306a36Sopenharmony_ci		return -ENOMEM;
37162306a36Sopenharmony_ci
37262306a36Sopenharmony_ci	err = devlink_nl_health_reporter_fill(msg, reporter,
37362306a36Sopenharmony_ci					      DEVLINK_CMD_HEALTH_REPORTER_GET,
37462306a36Sopenharmony_ci					      info->snd_portid, info->snd_seq,
37562306a36Sopenharmony_ci					      0);
37662306a36Sopenharmony_ci	if (err) {
37762306a36Sopenharmony_ci		nlmsg_free(msg);
37862306a36Sopenharmony_ci		return err;
37962306a36Sopenharmony_ci	}
38062306a36Sopenharmony_ci
38162306a36Sopenharmony_ci	return genlmsg_reply(msg, info);
38262306a36Sopenharmony_ci}
38362306a36Sopenharmony_ci
38462306a36Sopenharmony_cistatic int devlink_nl_health_reporter_get_dump_one(struct sk_buff *msg,
38562306a36Sopenharmony_ci						   struct devlink *devlink,
38662306a36Sopenharmony_ci						   struct netlink_callback *cb,
38762306a36Sopenharmony_ci						   int flags)
38862306a36Sopenharmony_ci{
38962306a36Sopenharmony_ci	struct devlink_nl_dump_state *state = devlink_dump_state(cb);
39062306a36Sopenharmony_ci	const struct genl_info *info = genl_info_dump(cb);
39162306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
39262306a36Sopenharmony_ci	unsigned long port_index_end = ULONG_MAX;
39362306a36Sopenharmony_ci	struct nlattr **attrs = info->attrs;
39462306a36Sopenharmony_ci	unsigned long port_index_start = 0;
39562306a36Sopenharmony_ci	struct devlink_port *port;
39662306a36Sopenharmony_ci	unsigned long port_index;
39762306a36Sopenharmony_ci	int idx = 0;
39862306a36Sopenharmony_ci	int err;
39962306a36Sopenharmony_ci
40062306a36Sopenharmony_ci	if (attrs && attrs[DEVLINK_ATTR_PORT_INDEX]) {
40162306a36Sopenharmony_ci		port_index_start = nla_get_u32(attrs[DEVLINK_ATTR_PORT_INDEX]);
40262306a36Sopenharmony_ci		port_index_end = port_index_start;
40362306a36Sopenharmony_ci		flags |= NLM_F_DUMP_FILTERED;
40462306a36Sopenharmony_ci		goto per_port_dump;
40562306a36Sopenharmony_ci	}
40662306a36Sopenharmony_ci
40762306a36Sopenharmony_ci	list_for_each_entry(reporter, &devlink->reporter_list, list) {
40862306a36Sopenharmony_ci		if (idx < state->idx) {
40962306a36Sopenharmony_ci			idx++;
41062306a36Sopenharmony_ci			continue;
41162306a36Sopenharmony_ci		}
41262306a36Sopenharmony_ci		err = devlink_nl_health_reporter_fill(msg, reporter,
41362306a36Sopenharmony_ci						      DEVLINK_CMD_HEALTH_REPORTER_GET,
41462306a36Sopenharmony_ci						      NETLINK_CB(cb->skb).portid,
41562306a36Sopenharmony_ci						      cb->nlh->nlmsg_seq,
41662306a36Sopenharmony_ci						      flags);
41762306a36Sopenharmony_ci		if (err) {
41862306a36Sopenharmony_ci			state->idx = idx;
41962306a36Sopenharmony_ci			return err;
42062306a36Sopenharmony_ci		}
42162306a36Sopenharmony_ci		idx++;
42262306a36Sopenharmony_ci	}
42362306a36Sopenharmony_ciper_port_dump:
42462306a36Sopenharmony_ci	xa_for_each_range(&devlink->ports, port_index, port,
42562306a36Sopenharmony_ci			  port_index_start, port_index_end) {
42662306a36Sopenharmony_ci		list_for_each_entry(reporter, &port->reporter_list, list) {
42762306a36Sopenharmony_ci			if (idx < state->idx) {
42862306a36Sopenharmony_ci				idx++;
42962306a36Sopenharmony_ci				continue;
43062306a36Sopenharmony_ci			}
43162306a36Sopenharmony_ci			err = devlink_nl_health_reporter_fill(msg, reporter,
43262306a36Sopenharmony_ci							      DEVLINK_CMD_HEALTH_REPORTER_GET,
43362306a36Sopenharmony_ci							      NETLINK_CB(cb->skb).portid,
43462306a36Sopenharmony_ci							      cb->nlh->nlmsg_seq,
43562306a36Sopenharmony_ci							      flags);
43662306a36Sopenharmony_ci			if (err) {
43762306a36Sopenharmony_ci				state->idx = idx;
43862306a36Sopenharmony_ci				return err;
43962306a36Sopenharmony_ci			}
44062306a36Sopenharmony_ci			idx++;
44162306a36Sopenharmony_ci		}
44262306a36Sopenharmony_ci	}
44362306a36Sopenharmony_ci
44462306a36Sopenharmony_ci	return 0;
44562306a36Sopenharmony_ci}
44662306a36Sopenharmony_ci
44762306a36Sopenharmony_ciint devlink_nl_health_reporter_get_dumpit(struct sk_buff *skb,
44862306a36Sopenharmony_ci					  struct netlink_callback *cb)
44962306a36Sopenharmony_ci{
45062306a36Sopenharmony_ci	return devlink_nl_dumpit(skb, cb,
45162306a36Sopenharmony_ci				 devlink_nl_health_reporter_get_dump_one);
45262306a36Sopenharmony_ci}
45362306a36Sopenharmony_ci
45462306a36Sopenharmony_ciint devlink_nl_cmd_health_reporter_set_doit(struct sk_buff *skb,
45562306a36Sopenharmony_ci					    struct genl_info *info)
45662306a36Sopenharmony_ci{
45762306a36Sopenharmony_ci	struct devlink *devlink = info->user_ptr[0];
45862306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
45962306a36Sopenharmony_ci
46062306a36Sopenharmony_ci	reporter = devlink_health_reporter_get_from_info(devlink, info);
46162306a36Sopenharmony_ci	if (!reporter)
46262306a36Sopenharmony_ci		return -EINVAL;
46362306a36Sopenharmony_ci
46462306a36Sopenharmony_ci	if (!reporter->ops->recover &&
46562306a36Sopenharmony_ci	    (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] ||
46662306a36Sopenharmony_ci	     info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]))
46762306a36Sopenharmony_ci		return -EOPNOTSUPP;
46862306a36Sopenharmony_ci
46962306a36Sopenharmony_ci	if (!reporter->ops->dump &&
47062306a36Sopenharmony_ci	    info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP])
47162306a36Sopenharmony_ci		return -EOPNOTSUPP;
47262306a36Sopenharmony_ci
47362306a36Sopenharmony_ci	if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD])
47462306a36Sopenharmony_ci		reporter->graceful_period =
47562306a36Sopenharmony_ci			nla_get_u64(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD]);
47662306a36Sopenharmony_ci
47762306a36Sopenharmony_ci	if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER])
47862306a36Sopenharmony_ci		reporter->auto_recover =
47962306a36Sopenharmony_ci			nla_get_u8(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]);
48062306a36Sopenharmony_ci
48162306a36Sopenharmony_ci	if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP])
48262306a36Sopenharmony_ci		reporter->auto_dump =
48362306a36Sopenharmony_ci		nla_get_u8(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_DUMP]);
48462306a36Sopenharmony_ci
48562306a36Sopenharmony_ci	return 0;
48662306a36Sopenharmony_ci}
48762306a36Sopenharmony_ci
48862306a36Sopenharmony_cistatic void devlink_recover_notify(struct devlink_health_reporter *reporter,
48962306a36Sopenharmony_ci				   enum devlink_command cmd)
49062306a36Sopenharmony_ci{
49162306a36Sopenharmony_ci	struct devlink *devlink = reporter->devlink;
49262306a36Sopenharmony_ci	struct sk_buff *msg;
49362306a36Sopenharmony_ci	int err;
49462306a36Sopenharmony_ci
49562306a36Sopenharmony_ci	WARN_ON(cmd != DEVLINK_CMD_HEALTH_REPORTER_RECOVER);
49662306a36Sopenharmony_ci	ASSERT_DEVLINK_REGISTERED(devlink);
49762306a36Sopenharmony_ci
49862306a36Sopenharmony_ci	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
49962306a36Sopenharmony_ci	if (!msg)
50062306a36Sopenharmony_ci		return;
50162306a36Sopenharmony_ci
50262306a36Sopenharmony_ci	err = devlink_nl_health_reporter_fill(msg, reporter, cmd, 0, 0, 0);
50362306a36Sopenharmony_ci	if (err) {
50462306a36Sopenharmony_ci		nlmsg_free(msg);
50562306a36Sopenharmony_ci		return;
50662306a36Sopenharmony_ci	}
50762306a36Sopenharmony_ci
50862306a36Sopenharmony_ci	genlmsg_multicast_netns(&devlink_nl_family, devlink_net(devlink), msg,
50962306a36Sopenharmony_ci				0, DEVLINK_MCGRP_CONFIG, GFP_KERNEL);
51062306a36Sopenharmony_ci}
51162306a36Sopenharmony_ci
51262306a36Sopenharmony_civoid
51362306a36Sopenharmony_cidevlink_health_reporter_recovery_done(struct devlink_health_reporter *reporter)
51462306a36Sopenharmony_ci{
51562306a36Sopenharmony_ci	reporter->recovery_count++;
51662306a36Sopenharmony_ci	reporter->last_recovery_ts = jiffies;
51762306a36Sopenharmony_ci}
51862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_health_reporter_recovery_done);
51962306a36Sopenharmony_ci
52062306a36Sopenharmony_cistatic int
52162306a36Sopenharmony_cidevlink_health_reporter_recover(struct devlink_health_reporter *reporter,
52262306a36Sopenharmony_ci				void *priv_ctx, struct netlink_ext_ack *extack)
52362306a36Sopenharmony_ci{
52462306a36Sopenharmony_ci	int err;
52562306a36Sopenharmony_ci
52662306a36Sopenharmony_ci	if (reporter->health_state == DEVLINK_HEALTH_REPORTER_STATE_HEALTHY)
52762306a36Sopenharmony_ci		return 0;
52862306a36Sopenharmony_ci
52962306a36Sopenharmony_ci	if (!reporter->ops->recover)
53062306a36Sopenharmony_ci		return -EOPNOTSUPP;
53162306a36Sopenharmony_ci
53262306a36Sopenharmony_ci	err = reporter->ops->recover(reporter, priv_ctx, extack);
53362306a36Sopenharmony_ci	if (err)
53462306a36Sopenharmony_ci		return err;
53562306a36Sopenharmony_ci
53662306a36Sopenharmony_ci	devlink_health_reporter_recovery_done(reporter);
53762306a36Sopenharmony_ci	reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_HEALTHY;
53862306a36Sopenharmony_ci	devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER);
53962306a36Sopenharmony_ci
54062306a36Sopenharmony_ci	return 0;
54162306a36Sopenharmony_ci}
54262306a36Sopenharmony_ci
54362306a36Sopenharmony_cistatic void
54462306a36Sopenharmony_cidevlink_health_dump_clear(struct devlink_health_reporter *reporter)
54562306a36Sopenharmony_ci{
54662306a36Sopenharmony_ci	if (!reporter->dump_fmsg)
54762306a36Sopenharmony_ci		return;
54862306a36Sopenharmony_ci	devlink_fmsg_free(reporter->dump_fmsg);
54962306a36Sopenharmony_ci	reporter->dump_fmsg = NULL;
55062306a36Sopenharmony_ci}
55162306a36Sopenharmony_ci
55262306a36Sopenharmony_cistatic int devlink_health_do_dump(struct devlink_health_reporter *reporter,
55362306a36Sopenharmony_ci				  void *priv_ctx,
55462306a36Sopenharmony_ci				  struct netlink_ext_ack *extack)
55562306a36Sopenharmony_ci{
55662306a36Sopenharmony_ci	int err;
55762306a36Sopenharmony_ci
55862306a36Sopenharmony_ci	if (!reporter->ops->dump)
55962306a36Sopenharmony_ci		return 0;
56062306a36Sopenharmony_ci
56162306a36Sopenharmony_ci	if (reporter->dump_fmsg)
56262306a36Sopenharmony_ci		return 0;
56362306a36Sopenharmony_ci
56462306a36Sopenharmony_ci	reporter->dump_fmsg = devlink_fmsg_alloc();
56562306a36Sopenharmony_ci	if (!reporter->dump_fmsg) {
56662306a36Sopenharmony_ci		err = -ENOMEM;
56762306a36Sopenharmony_ci		return err;
56862306a36Sopenharmony_ci	}
56962306a36Sopenharmony_ci
57062306a36Sopenharmony_ci	err = devlink_fmsg_obj_nest_start(reporter->dump_fmsg);
57162306a36Sopenharmony_ci	if (err)
57262306a36Sopenharmony_ci		goto dump_err;
57362306a36Sopenharmony_ci
57462306a36Sopenharmony_ci	err = reporter->ops->dump(reporter, reporter->dump_fmsg,
57562306a36Sopenharmony_ci				  priv_ctx, extack);
57662306a36Sopenharmony_ci	if (err)
57762306a36Sopenharmony_ci		goto dump_err;
57862306a36Sopenharmony_ci
57962306a36Sopenharmony_ci	err = devlink_fmsg_obj_nest_end(reporter->dump_fmsg);
58062306a36Sopenharmony_ci	if (err)
58162306a36Sopenharmony_ci		goto dump_err;
58262306a36Sopenharmony_ci
58362306a36Sopenharmony_ci	reporter->dump_ts = jiffies;
58462306a36Sopenharmony_ci	reporter->dump_real_ts = ktime_get_real_ns();
58562306a36Sopenharmony_ci
58662306a36Sopenharmony_ci	return 0;
58762306a36Sopenharmony_ci
58862306a36Sopenharmony_cidump_err:
58962306a36Sopenharmony_ci	devlink_health_dump_clear(reporter);
59062306a36Sopenharmony_ci	return err;
59162306a36Sopenharmony_ci}
59262306a36Sopenharmony_ci
59362306a36Sopenharmony_ciint devlink_health_report(struct devlink_health_reporter *reporter,
59462306a36Sopenharmony_ci			  const char *msg, void *priv_ctx)
59562306a36Sopenharmony_ci{
59662306a36Sopenharmony_ci	enum devlink_health_reporter_state prev_health_state;
59762306a36Sopenharmony_ci	struct devlink *devlink = reporter->devlink;
59862306a36Sopenharmony_ci	unsigned long recover_ts_threshold;
59962306a36Sopenharmony_ci	int ret;
60062306a36Sopenharmony_ci
60162306a36Sopenharmony_ci	/* write a log message of the current error */
60262306a36Sopenharmony_ci	WARN_ON(!msg);
60362306a36Sopenharmony_ci	trace_devlink_health_report(devlink, reporter->ops->name, msg);
60462306a36Sopenharmony_ci	reporter->error_count++;
60562306a36Sopenharmony_ci	prev_health_state = reporter->health_state;
60662306a36Sopenharmony_ci	reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_ERROR;
60762306a36Sopenharmony_ci	devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER);
60862306a36Sopenharmony_ci
60962306a36Sopenharmony_ci	/* abort if the previous error wasn't recovered */
61062306a36Sopenharmony_ci	recover_ts_threshold = reporter->last_recovery_ts +
61162306a36Sopenharmony_ci			       msecs_to_jiffies(reporter->graceful_period);
61262306a36Sopenharmony_ci	if (reporter->auto_recover &&
61362306a36Sopenharmony_ci	    (prev_health_state != DEVLINK_HEALTH_REPORTER_STATE_HEALTHY ||
61462306a36Sopenharmony_ci	     (reporter->last_recovery_ts && reporter->recovery_count &&
61562306a36Sopenharmony_ci	      time_is_after_jiffies(recover_ts_threshold)))) {
61662306a36Sopenharmony_ci		trace_devlink_health_recover_aborted(devlink,
61762306a36Sopenharmony_ci						     reporter->ops->name,
61862306a36Sopenharmony_ci						     reporter->health_state,
61962306a36Sopenharmony_ci						     jiffies -
62062306a36Sopenharmony_ci						     reporter->last_recovery_ts);
62162306a36Sopenharmony_ci		return -ECANCELED;
62262306a36Sopenharmony_ci	}
62362306a36Sopenharmony_ci
62462306a36Sopenharmony_ci	if (reporter->auto_dump) {
62562306a36Sopenharmony_ci		devl_lock(devlink);
62662306a36Sopenharmony_ci		/* store current dump of current error, for later analysis */
62762306a36Sopenharmony_ci		devlink_health_do_dump(reporter, priv_ctx, NULL);
62862306a36Sopenharmony_ci		devl_unlock(devlink);
62962306a36Sopenharmony_ci	}
63062306a36Sopenharmony_ci
63162306a36Sopenharmony_ci	if (!reporter->auto_recover)
63262306a36Sopenharmony_ci		return 0;
63362306a36Sopenharmony_ci
63462306a36Sopenharmony_ci	devl_lock(devlink);
63562306a36Sopenharmony_ci	ret = devlink_health_reporter_recover(reporter, priv_ctx, NULL);
63662306a36Sopenharmony_ci	devl_unlock(devlink);
63762306a36Sopenharmony_ci
63862306a36Sopenharmony_ci	return ret;
63962306a36Sopenharmony_ci}
64062306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_health_report);
64162306a36Sopenharmony_ci
64262306a36Sopenharmony_civoid
64362306a36Sopenharmony_cidevlink_health_reporter_state_update(struct devlink_health_reporter *reporter,
64462306a36Sopenharmony_ci				     enum devlink_health_reporter_state state)
64562306a36Sopenharmony_ci{
64662306a36Sopenharmony_ci	if (WARN_ON(state != DEVLINK_HEALTH_REPORTER_STATE_HEALTHY &&
64762306a36Sopenharmony_ci		    state != DEVLINK_HEALTH_REPORTER_STATE_ERROR))
64862306a36Sopenharmony_ci		return;
64962306a36Sopenharmony_ci
65062306a36Sopenharmony_ci	if (reporter->health_state == state)
65162306a36Sopenharmony_ci		return;
65262306a36Sopenharmony_ci
65362306a36Sopenharmony_ci	reporter->health_state = state;
65462306a36Sopenharmony_ci	trace_devlink_health_reporter_state_update(reporter->devlink,
65562306a36Sopenharmony_ci						   reporter->ops->name, state);
65662306a36Sopenharmony_ci	devlink_recover_notify(reporter, DEVLINK_CMD_HEALTH_REPORTER_RECOVER);
65762306a36Sopenharmony_ci}
65862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_health_reporter_state_update);
65962306a36Sopenharmony_ci
66062306a36Sopenharmony_ciint devlink_nl_cmd_health_reporter_recover_doit(struct sk_buff *skb,
66162306a36Sopenharmony_ci						struct genl_info *info)
66262306a36Sopenharmony_ci{
66362306a36Sopenharmony_ci	struct devlink *devlink = info->user_ptr[0];
66462306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
66562306a36Sopenharmony_ci
66662306a36Sopenharmony_ci	reporter = devlink_health_reporter_get_from_info(devlink, info);
66762306a36Sopenharmony_ci	if (!reporter)
66862306a36Sopenharmony_ci		return -EINVAL;
66962306a36Sopenharmony_ci
67062306a36Sopenharmony_ci	return devlink_health_reporter_recover(reporter, NULL, info->extack);
67162306a36Sopenharmony_ci}
67262306a36Sopenharmony_ci
67362306a36Sopenharmony_cistatic int devlink_fmsg_nest_common(struct devlink_fmsg *fmsg,
67462306a36Sopenharmony_ci				    int attrtype)
67562306a36Sopenharmony_ci{
67662306a36Sopenharmony_ci	struct devlink_fmsg_item *item;
67762306a36Sopenharmony_ci
67862306a36Sopenharmony_ci	item = kzalloc(sizeof(*item), GFP_KERNEL);
67962306a36Sopenharmony_ci	if (!item)
68062306a36Sopenharmony_ci		return -ENOMEM;
68162306a36Sopenharmony_ci
68262306a36Sopenharmony_ci	item->attrtype = attrtype;
68362306a36Sopenharmony_ci	list_add_tail(&item->list, &fmsg->item_list);
68462306a36Sopenharmony_ci
68562306a36Sopenharmony_ci	return 0;
68662306a36Sopenharmony_ci}
68762306a36Sopenharmony_ci
68862306a36Sopenharmony_ciint devlink_fmsg_obj_nest_start(struct devlink_fmsg *fmsg)
68962306a36Sopenharmony_ci{
69062306a36Sopenharmony_ci	if (fmsg->putting_binary)
69162306a36Sopenharmony_ci		return -EINVAL;
69262306a36Sopenharmony_ci
69362306a36Sopenharmony_ci	return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_OBJ_NEST_START);
69462306a36Sopenharmony_ci}
69562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_start);
69662306a36Sopenharmony_ci
69762306a36Sopenharmony_cistatic int devlink_fmsg_nest_end(struct devlink_fmsg *fmsg)
69862306a36Sopenharmony_ci{
69962306a36Sopenharmony_ci	if (fmsg->putting_binary)
70062306a36Sopenharmony_ci		return -EINVAL;
70162306a36Sopenharmony_ci
70262306a36Sopenharmony_ci	return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_NEST_END);
70362306a36Sopenharmony_ci}
70462306a36Sopenharmony_ci
70562306a36Sopenharmony_ciint devlink_fmsg_obj_nest_end(struct devlink_fmsg *fmsg)
70662306a36Sopenharmony_ci{
70762306a36Sopenharmony_ci	if (fmsg->putting_binary)
70862306a36Sopenharmony_ci		return -EINVAL;
70962306a36Sopenharmony_ci
71062306a36Sopenharmony_ci	return devlink_fmsg_nest_end(fmsg);
71162306a36Sopenharmony_ci}
71262306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_end);
71362306a36Sopenharmony_ci
71462306a36Sopenharmony_ci#define DEVLINK_FMSG_MAX_SIZE (GENLMSG_DEFAULT_SIZE - GENL_HDRLEN - NLA_HDRLEN)
71562306a36Sopenharmony_ci
71662306a36Sopenharmony_cistatic int devlink_fmsg_put_name(struct devlink_fmsg *fmsg, const char *name)
71762306a36Sopenharmony_ci{
71862306a36Sopenharmony_ci	struct devlink_fmsg_item *item;
71962306a36Sopenharmony_ci
72062306a36Sopenharmony_ci	if (fmsg->putting_binary)
72162306a36Sopenharmony_ci		return -EINVAL;
72262306a36Sopenharmony_ci
72362306a36Sopenharmony_ci	if (strlen(name) + 1 > DEVLINK_FMSG_MAX_SIZE)
72462306a36Sopenharmony_ci		return -EMSGSIZE;
72562306a36Sopenharmony_ci
72662306a36Sopenharmony_ci	item = kzalloc(sizeof(*item) + strlen(name) + 1, GFP_KERNEL);
72762306a36Sopenharmony_ci	if (!item)
72862306a36Sopenharmony_ci		return -ENOMEM;
72962306a36Sopenharmony_ci
73062306a36Sopenharmony_ci	item->nla_type = NLA_NUL_STRING;
73162306a36Sopenharmony_ci	item->len = strlen(name) + 1;
73262306a36Sopenharmony_ci	item->attrtype = DEVLINK_ATTR_FMSG_OBJ_NAME;
73362306a36Sopenharmony_ci	memcpy(&item->value, name, item->len);
73462306a36Sopenharmony_ci	list_add_tail(&item->list, &fmsg->item_list);
73562306a36Sopenharmony_ci
73662306a36Sopenharmony_ci	return 0;
73762306a36Sopenharmony_ci}
73862306a36Sopenharmony_ci
73962306a36Sopenharmony_ciint devlink_fmsg_pair_nest_start(struct devlink_fmsg *fmsg, const char *name)
74062306a36Sopenharmony_ci{
74162306a36Sopenharmony_ci	int err;
74262306a36Sopenharmony_ci
74362306a36Sopenharmony_ci	if (fmsg->putting_binary)
74462306a36Sopenharmony_ci		return -EINVAL;
74562306a36Sopenharmony_ci
74662306a36Sopenharmony_ci	err = devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_PAIR_NEST_START);
74762306a36Sopenharmony_ci	if (err)
74862306a36Sopenharmony_ci		return err;
74962306a36Sopenharmony_ci
75062306a36Sopenharmony_ci	err = devlink_fmsg_put_name(fmsg, name);
75162306a36Sopenharmony_ci	if (err)
75262306a36Sopenharmony_ci		return err;
75362306a36Sopenharmony_ci
75462306a36Sopenharmony_ci	return 0;
75562306a36Sopenharmony_ci}
75662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_pair_nest_start);
75762306a36Sopenharmony_ci
75862306a36Sopenharmony_ciint devlink_fmsg_pair_nest_end(struct devlink_fmsg *fmsg)
75962306a36Sopenharmony_ci{
76062306a36Sopenharmony_ci	if (fmsg->putting_binary)
76162306a36Sopenharmony_ci		return -EINVAL;
76262306a36Sopenharmony_ci
76362306a36Sopenharmony_ci	return devlink_fmsg_nest_end(fmsg);
76462306a36Sopenharmony_ci}
76562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_pair_nest_end);
76662306a36Sopenharmony_ci
76762306a36Sopenharmony_ciint devlink_fmsg_arr_pair_nest_start(struct devlink_fmsg *fmsg,
76862306a36Sopenharmony_ci				     const char *name)
76962306a36Sopenharmony_ci{
77062306a36Sopenharmony_ci	int err;
77162306a36Sopenharmony_ci
77262306a36Sopenharmony_ci	if (fmsg->putting_binary)
77362306a36Sopenharmony_ci		return -EINVAL;
77462306a36Sopenharmony_ci
77562306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_start(fmsg, name);
77662306a36Sopenharmony_ci	if (err)
77762306a36Sopenharmony_ci		return err;
77862306a36Sopenharmony_ci
77962306a36Sopenharmony_ci	err = devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_ARR_NEST_START);
78062306a36Sopenharmony_ci	if (err)
78162306a36Sopenharmony_ci		return err;
78262306a36Sopenharmony_ci
78362306a36Sopenharmony_ci	return 0;
78462306a36Sopenharmony_ci}
78562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_start);
78662306a36Sopenharmony_ci
78762306a36Sopenharmony_ciint devlink_fmsg_arr_pair_nest_end(struct devlink_fmsg *fmsg)
78862306a36Sopenharmony_ci{
78962306a36Sopenharmony_ci	int err;
79062306a36Sopenharmony_ci
79162306a36Sopenharmony_ci	if (fmsg->putting_binary)
79262306a36Sopenharmony_ci		return -EINVAL;
79362306a36Sopenharmony_ci
79462306a36Sopenharmony_ci	err = devlink_fmsg_nest_end(fmsg);
79562306a36Sopenharmony_ci	if (err)
79662306a36Sopenharmony_ci		return err;
79762306a36Sopenharmony_ci
79862306a36Sopenharmony_ci	err = devlink_fmsg_nest_end(fmsg);
79962306a36Sopenharmony_ci	if (err)
80062306a36Sopenharmony_ci		return err;
80162306a36Sopenharmony_ci
80262306a36Sopenharmony_ci	return 0;
80362306a36Sopenharmony_ci}
80462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_end);
80562306a36Sopenharmony_ci
80662306a36Sopenharmony_ciint devlink_fmsg_binary_pair_nest_start(struct devlink_fmsg *fmsg,
80762306a36Sopenharmony_ci					const char *name)
80862306a36Sopenharmony_ci{
80962306a36Sopenharmony_ci	int err;
81062306a36Sopenharmony_ci
81162306a36Sopenharmony_ci	err = devlink_fmsg_arr_pair_nest_start(fmsg, name);
81262306a36Sopenharmony_ci	if (err)
81362306a36Sopenharmony_ci		return err;
81462306a36Sopenharmony_ci
81562306a36Sopenharmony_ci	fmsg->putting_binary = true;
81662306a36Sopenharmony_ci	return err;
81762306a36Sopenharmony_ci}
81862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_nest_start);
81962306a36Sopenharmony_ci
82062306a36Sopenharmony_ciint devlink_fmsg_binary_pair_nest_end(struct devlink_fmsg *fmsg)
82162306a36Sopenharmony_ci{
82262306a36Sopenharmony_ci	if (!fmsg->putting_binary)
82362306a36Sopenharmony_ci		return -EINVAL;
82462306a36Sopenharmony_ci
82562306a36Sopenharmony_ci	fmsg->putting_binary = false;
82662306a36Sopenharmony_ci	return devlink_fmsg_arr_pair_nest_end(fmsg);
82762306a36Sopenharmony_ci}
82862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_nest_end);
82962306a36Sopenharmony_ci
83062306a36Sopenharmony_cistatic int devlink_fmsg_put_value(struct devlink_fmsg *fmsg,
83162306a36Sopenharmony_ci				  const void *value, u16 value_len,
83262306a36Sopenharmony_ci				  u8 value_nla_type)
83362306a36Sopenharmony_ci{
83462306a36Sopenharmony_ci	struct devlink_fmsg_item *item;
83562306a36Sopenharmony_ci
83662306a36Sopenharmony_ci	if (value_len > DEVLINK_FMSG_MAX_SIZE)
83762306a36Sopenharmony_ci		return -EMSGSIZE;
83862306a36Sopenharmony_ci
83962306a36Sopenharmony_ci	item = kzalloc(sizeof(*item) + value_len, GFP_KERNEL);
84062306a36Sopenharmony_ci	if (!item)
84162306a36Sopenharmony_ci		return -ENOMEM;
84262306a36Sopenharmony_ci
84362306a36Sopenharmony_ci	item->nla_type = value_nla_type;
84462306a36Sopenharmony_ci	item->len = value_len;
84562306a36Sopenharmony_ci	item->attrtype = DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA;
84662306a36Sopenharmony_ci	memcpy(&item->value, value, item->len);
84762306a36Sopenharmony_ci	list_add_tail(&item->list, &fmsg->item_list);
84862306a36Sopenharmony_ci
84962306a36Sopenharmony_ci	return 0;
85062306a36Sopenharmony_ci}
85162306a36Sopenharmony_ci
85262306a36Sopenharmony_cistatic int devlink_fmsg_bool_put(struct devlink_fmsg *fmsg, bool value)
85362306a36Sopenharmony_ci{
85462306a36Sopenharmony_ci	if (fmsg->putting_binary)
85562306a36Sopenharmony_ci		return -EINVAL;
85662306a36Sopenharmony_ci
85762306a36Sopenharmony_ci	return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_FLAG);
85862306a36Sopenharmony_ci}
85962306a36Sopenharmony_ci
86062306a36Sopenharmony_cistatic int devlink_fmsg_u8_put(struct devlink_fmsg *fmsg, u8 value)
86162306a36Sopenharmony_ci{
86262306a36Sopenharmony_ci	if (fmsg->putting_binary)
86362306a36Sopenharmony_ci		return -EINVAL;
86462306a36Sopenharmony_ci
86562306a36Sopenharmony_ci	return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U8);
86662306a36Sopenharmony_ci}
86762306a36Sopenharmony_ci
86862306a36Sopenharmony_ciint devlink_fmsg_u32_put(struct devlink_fmsg *fmsg, u32 value)
86962306a36Sopenharmony_ci{
87062306a36Sopenharmony_ci	if (fmsg->putting_binary)
87162306a36Sopenharmony_ci		return -EINVAL;
87262306a36Sopenharmony_ci
87362306a36Sopenharmony_ci	return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U32);
87462306a36Sopenharmony_ci}
87562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_u32_put);
87662306a36Sopenharmony_ci
87762306a36Sopenharmony_cistatic int devlink_fmsg_u64_put(struct devlink_fmsg *fmsg, u64 value)
87862306a36Sopenharmony_ci{
87962306a36Sopenharmony_ci	if (fmsg->putting_binary)
88062306a36Sopenharmony_ci		return -EINVAL;
88162306a36Sopenharmony_ci
88262306a36Sopenharmony_ci	return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U64);
88362306a36Sopenharmony_ci}
88462306a36Sopenharmony_ci
88562306a36Sopenharmony_ciint devlink_fmsg_string_put(struct devlink_fmsg *fmsg, const char *value)
88662306a36Sopenharmony_ci{
88762306a36Sopenharmony_ci	if (fmsg->putting_binary)
88862306a36Sopenharmony_ci		return -EINVAL;
88962306a36Sopenharmony_ci
89062306a36Sopenharmony_ci	return devlink_fmsg_put_value(fmsg, value, strlen(value) + 1,
89162306a36Sopenharmony_ci				      NLA_NUL_STRING);
89262306a36Sopenharmony_ci}
89362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_string_put);
89462306a36Sopenharmony_ci
89562306a36Sopenharmony_ciint devlink_fmsg_binary_put(struct devlink_fmsg *fmsg, const void *value,
89662306a36Sopenharmony_ci			    u16 value_len)
89762306a36Sopenharmony_ci{
89862306a36Sopenharmony_ci	if (!fmsg->putting_binary)
89962306a36Sopenharmony_ci		return -EINVAL;
90062306a36Sopenharmony_ci
90162306a36Sopenharmony_ci	return devlink_fmsg_put_value(fmsg, value, value_len, NLA_BINARY);
90262306a36Sopenharmony_ci}
90362306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_binary_put);
90462306a36Sopenharmony_ci
90562306a36Sopenharmony_ciint devlink_fmsg_bool_pair_put(struct devlink_fmsg *fmsg, const char *name,
90662306a36Sopenharmony_ci			       bool value)
90762306a36Sopenharmony_ci{
90862306a36Sopenharmony_ci	int err;
90962306a36Sopenharmony_ci
91062306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_start(fmsg, name);
91162306a36Sopenharmony_ci	if (err)
91262306a36Sopenharmony_ci		return err;
91362306a36Sopenharmony_ci
91462306a36Sopenharmony_ci	err = devlink_fmsg_bool_put(fmsg, value);
91562306a36Sopenharmony_ci	if (err)
91662306a36Sopenharmony_ci		return err;
91762306a36Sopenharmony_ci
91862306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_end(fmsg);
91962306a36Sopenharmony_ci	if (err)
92062306a36Sopenharmony_ci		return err;
92162306a36Sopenharmony_ci
92262306a36Sopenharmony_ci	return 0;
92362306a36Sopenharmony_ci}
92462306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_bool_pair_put);
92562306a36Sopenharmony_ci
92662306a36Sopenharmony_ciint devlink_fmsg_u8_pair_put(struct devlink_fmsg *fmsg, const char *name,
92762306a36Sopenharmony_ci			     u8 value)
92862306a36Sopenharmony_ci{
92962306a36Sopenharmony_ci	int err;
93062306a36Sopenharmony_ci
93162306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_start(fmsg, name);
93262306a36Sopenharmony_ci	if (err)
93362306a36Sopenharmony_ci		return err;
93462306a36Sopenharmony_ci
93562306a36Sopenharmony_ci	err = devlink_fmsg_u8_put(fmsg, value);
93662306a36Sopenharmony_ci	if (err)
93762306a36Sopenharmony_ci		return err;
93862306a36Sopenharmony_ci
93962306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_end(fmsg);
94062306a36Sopenharmony_ci	if (err)
94162306a36Sopenharmony_ci		return err;
94262306a36Sopenharmony_ci
94362306a36Sopenharmony_ci	return 0;
94462306a36Sopenharmony_ci}
94562306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_u8_pair_put);
94662306a36Sopenharmony_ci
94762306a36Sopenharmony_ciint devlink_fmsg_u32_pair_put(struct devlink_fmsg *fmsg, const char *name,
94862306a36Sopenharmony_ci			      u32 value)
94962306a36Sopenharmony_ci{
95062306a36Sopenharmony_ci	int err;
95162306a36Sopenharmony_ci
95262306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_start(fmsg, name);
95362306a36Sopenharmony_ci	if (err)
95462306a36Sopenharmony_ci		return err;
95562306a36Sopenharmony_ci
95662306a36Sopenharmony_ci	err = devlink_fmsg_u32_put(fmsg, value);
95762306a36Sopenharmony_ci	if (err)
95862306a36Sopenharmony_ci		return err;
95962306a36Sopenharmony_ci
96062306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_end(fmsg);
96162306a36Sopenharmony_ci	if (err)
96262306a36Sopenharmony_ci		return err;
96362306a36Sopenharmony_ci
96462306a36Sopenharmony_ci	return 0;
96562306a36Sopenharmony_ci}
96662306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_u32_pair_put);
96762306a36Sopenharmony_ci
96862306a36Sopenharmony_ciint devlink_fmsg_u64_pair_put(struct devlink_fmsg *fmsg, const char *name,
96962306a36Sopenharmony_ci			      u64 value)
97062306a36Sopenharmony_ci{
97162306a36Sopenharmony_ci	int err;
97262306a36Sopenharmony_ci
97362306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_start(fmsg, name);
97462306a36Sopenharmony_ci	if (err)
97562306a36Sopenharmony_ci		return err;
97662306a36Sopenharmony_ci
97762306a36Sopenharmony_ci	err = devlink_fmsg_u64_put(fmsg, value);
97862306a36Sopenharmony_ci	if (err)
97962306a36Sopenharmony_ci		return err;
98062306a36Sopenharmony_ci
98162306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_end(fmsg);
98262306a36Sopenharmony_ci	if (err)
98362306a36Sopenharmony_ci		return err;
98462306a36Sopenharmony_ci
98562306a36Sopenharmony_ci	return 0;
98662306a36Sopenharmony_ci}
98762306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_u64_pair_put);
98862306a36Sopenharmony_ci
98962306a36Sopenharmony_ciint devlink_fmsg_string_pair_put(struct devlink_fmsg *fmsg, const char *name,
99062306a36Sopenharmony_ci				 const char *value)
99162306a36Sopenharmony_ci{
99262306a36Sopenharmony_ci	int err;
99362306a36Sopenharmony_ci
99462306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_start(fmsg, name);
99562306a36Sopenharmony_ci	if (err)
99662306a36Sopenharmony_ci		return err;
99762306a36Sopenharmony_ci
99862306a36Sopenharmony_ci	err = devlink_fmsg_string_put(fmsg, value);
99962306a36Sopenharmony_ci	if (err)
100062306a36Sopenharmony_ci		return err;
100162306a36Sopenharmony_ci
100262306a36Sopenharmony_ci	err = devlink_fmsg_pair_nest_end(fmsg);
100362306a36Sopenharmony_ci	if (err)
100462306a36Sopenharmony_ci		return err;
100562306a36Sopenharmony_ci
100662306a36Sopenharmony_ci	return 0;
100762306a36Sopenharmony_ci}
100862306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_string_pair_put);
100962306a36Sopenharmony_ci
101062306a36Sopenharmony_ciint devlink_fmsg_binary_pair_put(struct devlink_fmsg *fmsg, const char *name,
101162306a36Sopenharmony_ci				 const void *value, u32 value_len)
101262306a36Sopenharmony_ci{
101362306a36Sopenharmony_ci	u32 data_size;
101462306a36Sopenharmony_ci	int end_err;
101562306a36Sopenharmony_ci	u32 offset;
101662306a36Sopenharmony_ci	int err;
101762306a36Sopenharmony_ci
101862306a36Sopenharmony_ci	err = devlink_fmsg_binary_pair_nest_start(fmsg, name);
101962306a36Sopenharmony_ci	if (err)
102062306a36Sopenharmony_ci		return err;
102162306a36Sopenharmony_ci
102262306a36Sopenharmony_ci	for (offset = 0; offset < value_len; offset += data_size) {
102362306a36Sopenharmony_ci		data_size = value_len - offset;
102462306a36Sopenharmony_ci		if (data_size > DEVLINK_FMSG_MAX_SIZE)
102562306a36Sopenharmony_ci			data_size = DEVLINK_FMSG_MAX_SIZE;
102662306a36Sopenharmony_ci		err = devlink_fmsg_binary_put(fmsg, value + offset, data_size);
102762306a36Sopenharmony_ci		if (err)
102862306a36Sopenharmony_ci			break;
102962306a36Sopenharmony_ci		/* Exit from loop with a break (instead of
103062306a36Sopenharmony_ci		 * return) to make sure putting_binary is turned off in
103162306a36Sopenharmony_ci		 * devlink_fmsg_binary_pair_nest_end
103262306a36Sopenharmony_ci		 */
103362306a36Sopenharmony_ci	}
103462306a36Sopenharmony_ci
103562306a36Sopenharmony_ci	end_err = devlink_fmsg_binary_pair_nest_end(fmsg);
103662306a36Sopenharmony_ci	if (end_err)
103762306a36Sopenharmony_ci		err = end_err;
103862306a36Sopenharmony_ci
103962306a36Sopenharmony_ci	return err;
104062306a36Sopenharmony_ci}
104162306a36Sopenharmony_ciEXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_put);
104262306a36Sopenharmony_ci
104362306a36Sopenharmony_cistatic int
104462306a36Sopenharmony_cidevlink_fmsg_item_fill_type(struct devlink_fmsg_item *msg, struct sk_buff *skb)
104562306a36Sopenharmony_ci{
104662306a36Sopenharmony_ci	switch (msg->nla_type) {
104762306a36Sopenharmony_ci	case NLA_FLAG:
104862306a36Sopenharmony_ci	case NLA_U8:
104962306a36Sopenharmony_ci	case NLA_U32:
105062306a36Sopenharmony_ci	case NLA_U64:
105162306a36Sopenharmony_ci	case NLA_NUL_STRING:
105262306a36Sopenharmony_ci	case NLA_BINARY:
105362306a36Sopenharmony_ci		return nla_put_u8(skb, DEVLINK_ATTR_FMSG_OBJ_VALUE_TYPE,
105462306a36Sopenharmony_ci				  msg->nla_type);
105562306a36Sopenharmony_ci	default:
105662306a36Sopenharmony_ci		return -EINVAL;
105762306a36Sopenharmony_ci	}
105862306a36Sopenharmony_ci}
105962306a36Sopenharmony_ci
106062306a36Sopenharmony_cistatic int
106162306a36Sopenharmony_cidevlink_fmsg_item_fill_data(struct devlink_fmsg_item *msg, struct sk_buff *skb)
106262306a36Sopenharmony_ci{
106362306a36Sopenharmony_ci	int attrtype = DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA;
106462306a36Sopenharmony_ci	u8 tmp;
106562306a36Sopenharmony_ci
106662306a36Sopenharmony_ci	switch (msg->nla_type) {
106762306a36Sopenharmony_ci	case NLA_FLAG:
106862306a36Sopenharmony_ci		/* Always provide flag data, regardless of its value */
106962306a36Sopenharmony_ci		tmp = *(bool *)msg->value;
107062306a36Sopenharmony_ci
107162306a36Sopenharmony_ci		return nla_put_u8(skb, attrtype, tmp);
107262306a36Sopenharmony_ci	case NLA_U8:
107362306a36Sopenharmony_ci		return nla_put_u8(skb, attrtype, *(u8 *)msg->value);
107462306a36Sopenharmony_ci	case NLA_U32:
107562306a36Sopenharmony_ci		return nla_put_u32(skb, attrtype, *(u32 *)msg->value);
107662306a36Sopenharmony_ci	case NLA_U64:
107762306a36Sopenharmony_ci		return nla_put_u64_64bit(skb, attrtype, *(u64 *)msg->value,
107862306a36Sopenharmony_ci					 DEVLINK_ATTR_PAD);
107962306a36Sopenharmony_ci	case NLA_NUL_STRING:
108062306a36Sopenharmony_ci		return nla_put_string(skb, attrtype, (char *)&msg->value);
108162306a36Sopenharmony_ci	case NLA_BINARY:
108262306a36Sopenharmony_ci		return nla_put(skb, attrtype, msg->len, (void *)&msg->value);
108362306a36Sopenharmony_ci	default:
108462306a36Sopenharmony_ci		return -EINVAL;
108562306a36Sopenharmony_ci	}
108662306a36Sopenharmony_ci}
108762306a36Sopenharmony_ci
108862306a36Sopenharmony_cistatic int
108962306a36Sopenharmony_cidevlink_fmsg_prepare_skb(struct devlink_fmsg *fmsg, struct sk_buff *skb,
109062306a36Sopenharmony_ci			 int *start)
109162306a36Sopenharmony_ci{
109262306a36Sopenharmony_ci	struct devlink_fmsg_item *item;
109362306a36Sopenharmony_ci	struct nlattr *fmsg_nlattr;
109462306a36Sopenharmony_ci	int err = 0;
109562306a36Sopenharmony_ci	int i = 0;
109662306a36Sopenharmony_ci
109762306a36Sopenharmony_ci	fmsg_nlattr = nla_nest_start_noflag(skb, DEVLINK_ATTR_FMSG);
109862306a36Sopenharmony_ci	if (!fmsg_nlattr)
109962306a36Sopenharmony_ci		return -EMSGSIZE;
110062306a36Sopenharmony_ci
110162306a36Sopenharmony_ci	list_for_each_entry(item, &fmsg->item_list, list) {
110262306a36Sopenharmony_ci		if (i < *start) {
110362306a36Sopenharmony_ci			i++;
110462306a36Sopenharmony_ci			continue;
110562306a36Sopenharmony_ci		}
110662306a36Sopenharmony_ci
110762306a36Sopenharmony_ci		switch (item->attrtype) {
110862306a36Sopenharmony_ci		case DEVLINK_ATTR_FMSG_OBJ_NEST_START:
110962306a36Sopenharmony_ci		case DEVLINK_ATTR_FMSG_PAIR_NEST_START:
111062306a36Sopenharmony_ci		case DEVLINK_ATTR_FMSG_ARR_NEST_START:
111162306a36Sopenharmony_ci		case DEVLINK_ATTR_FMSG_NEST_END:
111262306a36Sopenharmony_ci			err = nla_put_flag(skb, item->attrtype);
111362306a36Sopenharmony_ci			break;
111462306a36Sopenharmony_ci		case DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA:
111562306a36Sopenharmony_ci			err = devlink_fmsg_item_fill_type(item, skb);
111662306a36Sopenharmony_ci			if (err)
111762306a36Sopenharmony_ci				break;
111862306a36Sopenharmony_ci			err = devlink_fmsg_item_fill_data(item, skb);
111962306a36Sopenharmony_ci			break;
112062306a36Sopenharmony_ci		case DEVLINK_ATTR_FMSG_OBJ_NAME:
112162306a36Sopenharmony_ci			err = nla_put_string(skb, item->attrtype,
112262306a36Sopenharmony_ci					     (char *)&item->value);
112362306a36Sopenharmony_ci			break;
112462306a36Sopenharmony_ci		default:
112562306a36Sopenharmony_ci			err = -EINVAL;
112662306a36Sopenharmony_ci			break;
112762306a36Sopenharmony_ci		}
112862306a36Sopenharmony_ci		if (!err)
112962306a36Sopenharmony_ci			*start = ++i;
113062306a36Sopenharmony_ci		else
113162306a36Sopenharmony_ci			break;
113262306a36Sopenharmony_ci	}
113362306a36Sopenharmony_ci
113462306a36Sopenharmony_ci	nla_nest_end(skb, fmsg_nlattr);
113562306a36Sopenharmony_ci	return err;
113662306a36Sopenharmony_ci}
113762306a36Sopenharmony_ci
113862306a36Sopenharmony_cistatic int devlink_fmsg_snd(struct devlink_fmsg *fmsg,
113962306a36Sopenharmony_ci			    struct genl_info *info,
114062306a36Sopenharmony_ci			    enum devlink_command cmd, int flags)
114162306a36Sopenharmony_ci{
114262306a36Sopenharmony_ci	struct nlmsghdr *nlh;
114362306a36Sopenharmony_ci	struct sk_buff *skb;
114462306a36Sopenharmony_ci	bool last = false;
114562306a36Sopenharmony_ci	int index = 0;
114662306a36Sopenharmony_ci	void *hdr;
114762306a36Sopenharmony_ci	int err;
114862306a36Sopenharmony_ci
114962306a36Sopenharmony_ci	while (!last) {
115062306a36Sopenharmony_ci		int tmp_index = index;
115162306a36Sopenharmony_ci
115262306a36Sopenharmony_ci		skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
115362306a36Sopenharmony_ci		if (!skb)
115462306a36Sopenharmony_ci			return -ENOMEM;
115562306a36Sopenharmony_ci
115662306a36Sopenharmony_ci		hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq,
115762306a36Sopenharmony_ci				  &devlink_nl_family, flags | NLM_F_MULTI, cmd);
115862306a36Sopenharmony_ci		if (!hdr) {
115962306a36Sopenharmony_ci			err = -EMSGSIZE;
116062306a36Sopenharmony_ci			goto nla_put_failure;
116162306a36Sopenharmony_ci		}
116262306a36Sopenharmony_ci
116362306a36Sopenharmony_ci		err = devlink_fmsg_prepare_skb(fmsg, skb, &index);
116462306a36Sopenharmony_ci		if (!err)
116562306a36Sopenharmony_ci			last = true;
116662306a36Sopenharmony_ci		else if (err != -EMSGSIZE || tmp_index == index)
116762306a36Sopenharmony_ci			goto nla_put_failure;
116862306a36Sopenharmony_ci
116962306a36Sopenharmony_ci		genlmsg_end(skb, hdr);
117062306a36Sopenharmony_ci		err = genlmsg_reply(skb, info);
117162306a36Sopenharmony_ci		if (err)
117262306a36Sopenharmony_ci			return err;
117362306a36Sopenharmony_ci	}
117462306a36Sopenharmony_ci
117562306a36Sopenharmony_ci	skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
117662306a36Sopenharmony_ci	if (!skb)
117762306a36Sopenharmony_ci		return -ENOMEM;
117862306a36Sopenharmony_ci	nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq,
117962306a36Sopenharmony_ci			NLMSG_DONE, 0, flags | NLM_F_MULTI);
118062306a36Sopenharmony_ci	if (!nlh) {
118162306a36Sopenharmony_ci		err = -EMSGSIZE;
118262306a36Sopenharmony_ci		goto nla_put_failure;
118362306a36Sopenharmony_ci	}
118462306a36Sopenharmony_ci
118562306a36Sopenharmony_ci	return genlmsg_reply(skb, info);
118662306a36Sopenharmony_ci
118762306a36Sopenharmony_cinla_put_failure:
118862306a36Sopenharmony_ci	nlmsg_free(skb);
118962306a36Sopenharmony_ci	return err;
119062306a36Sopenharmony_ci}
119162306a36Sopenharmony_ci
119262306a36Sopenharmony_cistatic int devlink_fmsg_dumpit(struct devlink_fmsg *fmsg, struct sk_buff *skb,
119362306a36Sopenharmony_ci			       struct netlink_callback *cb,
119462306a36Sopenharmony_ci			       enum devlink_command cmd)
119562306a36Sopenharmony_ci{
119662306a36Sopenharmony_ci	struct devlink_nl_dump_state *state = devlink_dump_state(cb);
119762306a36Sopenharmony_ci	int index = state->idx;
119862306a36Sopenharmony_ci	int tmp_index = index;
119962306a36Sopenharmony_ci	void *hdr;
120062306a36Sopenharmony_ci	int err;
120162306a36Sopenharmony_ci
120262306a36Sopenharmony_ci	hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,
120362306a36Sopenharmony_ci			  &devlink_nl_family, NLM_F_ACK | NLM_F_MULTI, cmd);
120462306a36Sopenharmony_ci	if (!hdr) {
120562306a36Sopenharmony_ci		err = -EMSGSIZE;
120662306a36Sopenharmony_ci		goto nla_put_failure;
120762306a36Sopenharmony_ci	}
120862306a36Sopenharmony_ci
120962306a36Sopenharmony_ci	err = devlink_fmsg_prepare_skb(fmsg, skb, &index);
121062306a36Sopenharmony_ci	if ((err && err != -EMSGSIZE) || tmp_index == index)
121162306a36Sopenharmony_ci		goto nla_put_failure;
121262306a36Sopenharmony_ci
121362306a36Sopenharmony_ci	state->idx = index;
121462306a36Sopenharmony_ci	genlmsg_end(skb, hdr);
121562306a36Sopenharmony_ci	return skb->len;
121662306a36Sopenharmony_ci
121762306a36Sopenharmony_cinla_put_failure:
121862306a36Sopenharmony_ci	genlmsg_cancel(skb, hdr);
121962306a36Sopenharmony_ci	return err;
122062306a36Sopenharmony_ci}
122162306a36Sopenharmony_ci
122262306a36Sopenharmony_ciint devlink_nl_cmd_health_reporter_diagnose_doit(struct sk_buff *skb,
122362306a36Sopenharmony_ci						 struct genl_info *info)
122462306a36Sopenharmony_ci{
122562306a36Sopenharmony_ci	struct devlink *devlink = info->user_ptr[0];
122662306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
122762306a36Sopenharmony_ci	struct devlink_fmsg *fmsg;
122862306a36Sopenharmony_ci	int err;
122962306a36Sopenharmony_ci
123062306a36Sopenharmony_ci	reporter = devlink_health_reporter_get_from_info(devlink, info);
123162306a36Sopenharmony_ci	if (!reporter)
123262306a36Sopenharmony_ci		return -EINVAL;
123362306a36Sopenharmony_ci
123462306a36Sopenharmony_ci	if (!reporter->ops->diagnose)
123562306a36Sopenharmony_ci		return -EOPNOTSUPP;
123662306a36Sopenharmony_ci
123762306a36Sopenharmony_ci	fmsg = devlink_fmsg_alloc();
123862306a36Sopenharmony_ci	if (!fmsg)
123962306a36Sopenharmony_ci		return -ENOMEM;
124062306a36Sopenharmony_ci
124162306a36Sopenharmony_ci	err = devlink_fmsg_obj_nest_start(fmsg);
124262306a36Sopenharmony_ci	if (err)
124362306a36Sopenharmony_ci		goto out;
124462306a36Sopenharmony_ci
124562306a36Sopenharmony_ci	err = reporter->ops->diagnose(reporter, fmsg, info->extack);
124662306a36Sopenharmony_ci	if (err)
124762306a36Sopenharmony_ci		goto out;
124862306a36Sopenharmony_ci
124962306a36Sopenharmony_ci	err = devlink_fmsg_obj_nest_end(fmsg);
125062306a36Sopenharmony_ci	if (err)
125162306a36Sopenharmony_ci		goto out;
125262306a36Sopenharmony_ci
125362306a36Sopenharmony_ci	err = devlink_fmsg_snd(fmsg, info,
125462306a36Sopenharmony_ci			       DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE, 0);
125562306a36Sopenharmony_ci
125662306a36Sopenharmony_ciout:
125762306a36Sopenharmony_ci	devlink_fmsg_free(fmsg);
125862306a36Sopenharmony_ci	return err;
125962306a36Sopenharmony_ci}
126062306a36Sopenharmony_ci
126162306a36Sopenharmony_cistatic struct devlink_health_reporter *
126262306a36Sopenharmony_cidevlink_health_reporter_get_from_cb_lock(struct netlink_callback *cb)
126362306a36Sopenharmony_ci{
126462306a36Sopenharmony_ci	const struct genl_info *info = genl_info_dump(cb);
126562306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
126662306a36Sopenharmony_ci	struct nlattr **attrs = info->attrs;
126762306a36Sopenharmony_ci	struct devlink *devlink;
126862306a36Sopenharmony_ci
126962306a36Sopenharmony_ci	devlink = devlink_get_from_attrs_lock(sock_net(cb->skb->sk), attrs);
127062306a36Sopenharmony_ci	if (IS_ERR(devlink))
127162306a36Sopenharmony_ci		return NULL;
127262306a36Sopenharmony_ci
127362306a36Sopenharmony_ci	reporter = devlink_health_reporter_get_from_attrs(devlink, attrs);
127462306a36Sopenharmony_ci	if (!reporter) {
127562306a36Sopenharmony_ci		devl_unlock(devlink);
127662306a36Sopenharmony_ci		devlink_put(devlink);
127762306a36Sopenharmony_ci	}
127862306a36Sopenharmony_ci	return reporter;
127962306a36Sopenharmony_ci}
128062306a36Sopenharmony_ci
128162306a36Sopenharmony_ciint devlink_nl_cmd_health_reporter_dump_get_dumpit(struct sk_buff *skb,
128262306a36Sopenharmony_ci						   struct netlink_callback *cb)
128362306a36Sopenharmony_ci{
128462306a36Sopenharmony_ci	struct devlink_nl_dump_state *state = devlink_dump_state(cb);
128562306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
128662306a36Sopenharmony_ci	struct devlink *devlink;
128762306a36Sopenharmony_ci	int err;
128862306a36Sopenharmony_ci
128962306a36Sopenharmony_ci	reporter = devlink_health_reporter_get_from_cb_lock(cb);
129062306a36Sopenharmony_ci	if (!reporter)
129162306a36Sopenharmony_ci		return -EINVAL;
129262306a36Sopenharmony_ci
129362306a36Sopenharmony_ci	devlink = reporter->devlink;
129462306a36Sopenharmony_ci	if (!reporter->ops->dump) {
129562306a36Sopenharmony_ci		devl_unlock(devlink);
129662306a36Sopenharmony_ci		devlink_put(devlink);
129762306a36Sopenharmony_ci		return -EOPNOTSUPP;
129862306a36Sopenharmony_ci	}
129962306a36Sopenharmony_ci
130062306a36Sopenharmony_ci	if (!state->idx) {
130162306a36Sopenharmony_ci		err = devlink_health_do_dump(reporter, NULL, cb->extack);
130262306a36Sopenharmony_ci		if (err)
130362306a36Sopenharmony_ci			goto unlock;
130462306a36Sopenharmony_ci		state->dump_ts = reporter->dump_ts;
130562306a36Sopenharmony_ci	}
130662306a36Sopenharmony_ci	if (!reporter->dump_fmsg || state->dump_ts != reporter->dump_ts) {
130762306a36Sopenharmony_ci		NL_SET_ERR_MSG(cb->extack, "Dump trampled, please retry");
130862306a36Sopenharmony_ci		err = -EAGAIN;
130962306a36Sopenharmony_ci		goto unlock;
131062306a36Sopenharmony_ci	}
131162306a36Sopenharmony_ci
131262306a36Sopenharmony_ci	err = devlink_fmsg_dumpit(reporter->dump_fmsg, skb, cb,
131362306a36Sopenharmony_ci				  DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET);
131462306a36Sopenharmony_ciunlock:
131562306a36Sopenharmony_ci	devl_unlock(devlink);
131662306a36Sopenharmony_ci	devlink_put(devlink);
131762306a36Sopenharmony_ci	return err;
131862306a36Sopenharmony_ci}
131962306a36Sopenharmony_ci
132062306a36Sopenharmony_ciint devlink_nl_cmd_health_reporter_dump_clear_doit(struct sk_buff *skb,
132162306a36Sopenharmony_ci						   struct genl_info *info)
132262306a36Sopenharmony_ci{
132362306a36Sopenharmony_ci	struct devlink *devlink = info->user_ptr[0];
132462306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
132562306a36Sopenharmony_ci
132662306a36Sopenharmony_ci	reporter = devlink_health_reporter_get_from_info(devlink, info);
132762306a36Sopenharmony_ci	if (!reporter)
132862306a36Sopenharmony_ci		return -EINVAL;
132962306a36Sopenharmony_ci
133062306a36Sopenharmony_ci	if (!reporter->ops->dump)
133162306a36Sopenharmony_ci		return -EOPNOTSUPP;
133262306a36Sopenharmony_ci
133362306a36Sopenharmony_ci	devlink_health_dump_clear(reporter);
133462306a36Sopenharmony_ci	return 0;
133562306a36Sopenharmony_ci}
133662306a36Sopenharmony_ci
133762306a36Sopenharmony_ciint devlink_nl_cmd_health_reporter_test_doit(struct sk_buff *skb,
133862306a36Sopenharmony_ci					     struct genl_info *info)
133962306a36Sopenharmony_ci{
134062306a36Sopenharmony_ci	struct devlink *devlink = info->user_ptr[0];
134162306a36Sopenharmony_ci	struct devlink_health_reporter *reporter;
134262306a36Sopenharmony_ci
134362306a36Sopenharmony_ci	reporter = devlink_health_reporter_get_from_info(devlink, info);
134462306a36Sopenharmony_ci	if (!reporter)
134562306a36Sopenharmony_ci		return -EINVAL;
134662306a36Sopenharmony_ci
134762306a36Sopenharmony_ci	if (!reporter->ops->test)
134862306a36Sopenharmony_ci		return -EOPNOTSUPP;
134962306a36Sopenharmony_ci
135062306a36Sopenharmony_ci	return reporter->ops->test(reporter, info->extack);
135162306a36Sopenharmony_ci}
1352