devlink: publish params only after driver init is done
[linux-2.6-microblaze.git] / net / core / devlink.c
index 8984501..e6a015b 100644 (file)
@@ -81,6 +81,7 @@ struct devlink_dpipe_header devlink_dpipe_header_ipv6 = {
 EXPORT_SYMBOL(devlink_dpipe_header_ipv6);
 
 EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_hwmsg);
+EXPORT_TRACEPOINT_SYMBOL_GPL(devlink_hwerr);
 
 static LIST_HEAD(devlink_list);
 
@@ -932,6 +933,9 @@ static int devlink_nl_sb_pool_fill(struct sk_buff *msg, struct devlink *devlink,
        if (nla_put_u8(msg, DEVLINK_ATTR_SB_POOL_THRESHOLD_TYPE,
                       pool_info.threshold_type))
                goto nla_put_failure;
+       if (nla_put_u32(msg, DEVLINK_ATTR_SB_POOL_CELL_SIZE,
+                       pool_info.cell_size))
+               goto nla_put_failure;
 
        genlmsg_end(msg, hdr);
        return 0;
@@ -2697,6 +2701,11 @@ static const struct devlink_param devlink_param_generic[] = {
                .name = DEVLINK_PARAM_GENERIC_FW_LOAD_POLICY_NAME,
                .type = DEVLINK_PARAM_GENERIC_FW_LOAD_POLICY_TYPE,
        },
+       {
+               .id = DEVLINK_PARAM_GENERIC_ID_WOL,
+               .name = DEVLINK_PARAM_GENERIC_WOL_NAME,
+               .type = DEVLINK_PARAM_GENERIC_WOL_TYPE,
+       },
 };
 
 static int devlink_param_generic_verify(const struct devlink_param *param)
@@ -2843,11 +2852,13 @@ nla_put_failure:
 }
 
 static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink,
+                                unsigned int port_index,
                                 struct devlink_param_item *param_item,
                                 enum devlink_command cmd,
                                 u32 portid, u32 seq, int flags)
 {
        union devlink_param_value param_value[DEVLINK_PARAM_CMODE_MAX + 1];
+       bool param_value_set[DEVLINK_PARAM_CMODE_MAX + 1] = {};
        const struct devlink_param *param = param_item->param;
        struct devlink_param_gset_ctx ctx;
        struct nlattr *param_values_list;
@@ -2866,12 +2877,15 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink,
                                return -EOPNOTSUPP;
                        param_value[i] = param_item->driverinit_value;
                } else {
+                       if (!param_item->published)
+                               continue;
                        ctx.cmode = i;
                        err = devlink_param_get(devlink, param, &ctx);
                        if (err)
                                return err;
                        param_value[i] = ctx.val;
                }
+               param_value_set[i] = true;
        }
 
        hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd);
@@ -2880,6 +2894,13 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink,
 
        if (devlink_nl_put_handle(msg, devlink))
                goto genlmsg_cancel;
+
+       if (cmd == DEVLINK_CMD_PORT_PARAM_GET ||
+           cmd == DEVLINK_CMD_PORT_PARAM_NEW ||
+           cmd == DEVLINK_CMD_PORT_PARAM_DEL)
+               if (nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX, port_index))
+                       goto genlmsg_cancel;
+
        param_attr = nla_nest_start(msg, DEVLINK_ATTR_PARAM);
        if (!param_attr)
                goto genlmsg_cancel;
@@ -2899,7 +2920,7 @@ static int devlink_nl_param_fill(struct sk_buff *msg, struct devlink *devlink,
                goto param_nest_cancel;
 
        for (i = 0; i <= DEVLINK_PARAM_CMODE_MAX; i++) {
-               if (!devlink_param_cmode_is_supported(param, i))
+               if (!param_value_set[i])
                        continue;
                err = devlink_nl_param_value_fill_one(msg, param->type,
                                                      i, param_value[i]);
@@ -2922,18 +2943,22 @@ genlmsg_cancel:
 }
 
 static void devlink_param_notify(struct devlink *devlink,
+                                unsigned int port_index,
                                 struct devlink_param_item *param_item,
                                 enum devlink_command cmd)
 {
        struct sk_buff *msg;
        int err;
 
-       WARN_ON(cmd != DEVLINK_CMD_PARAM_NEW && cmd != DEVLINK_CMD_PARAM_DEL);
+       WARN_ON(cmd != DEVLINK_CMD_PARAM_NEW && cmd != DEVLINK_CMD_PARAM_DEL &&
+               cmd != DEVLINK_CMD_PORT_PARAM_NEW &&
+               cmd != DEVLINK_CMD_PORT_PARAM_DEL);
 
        msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
        if (!msg)
                return;
-       err = devlink_nl_param_fill(msg, devlink, param_item, cmd, 0, 0, 0);
+       err = devlink_nl_param_fill(msg, devlink, port_index, param_item, cmd,
+                                   0, 0, 0);
        if (err) {
                nlmsg_free(msg);
                return;
@@ -2962,7 +2987,7 @@ static int devlink_nl_cmd_param_get_dumpit(struct sk_buff *msg,
                                idx++;
                                continue;
                        }
-                       err = devlink_nl_param_fill(msg, devlink, param_item,
+                       err = devlink_nl_param_fill(msg, devlink, 0, param_item,
                                                    DEVLINK_CMD_PARAM_GET,
                                                    NETLINK_CB(cb->skb).portid,
                                                    cb->nlh->nlmsg_seq,
@@ -3051,7 +3076,7 @@ devlink_param_value_get_from_info(const struct devlink_param *param,
 }
 
 static struct devlink_param_item *
-devlink_param_get_from_info(struct devlink *devlink,
+devlink_param_get_from_info(struct list_head *param_list,
                            struct genl_info *info)
 {
        char *param_name;
@@ -3060,7 +3085,7 @@ devlink_param_get_from_info(struct devlink *devlink,
                return NULL;
 
        param_name = nla_data(info->attrs[DEVLINK_ATTR_PARAM_NAME]);
-       return devlink_param_find_by_name(&devlink->param_list, param_name);
+       return devlink_param_find_by_name(param_list, param_name);
 }
 
 static int devlink_nl_cmd_param_get_doit(struct sk_buff *skb,
@@ -3071,7 +3096,7 @@ static int devlink_nl_cmd_param_get_doit(struct sk_buff *skb,
        struct sk_buff *msg;
        int err;
 
-       param_item = devlink_param_get_from_info(devlink, info);
+       param_item = devlink_param_get_from_info(&devlink->param_list, info);
        if (!param_item)
                return -EINVAL;
 
@@ -3079,7 +3104,7 @@ static int devlink_nl_cmd_param_get_doit(struct sk_buff *skb,
        if (!msg)
                return -ENOMEM;
 
-       err = devlink_nl_param_fill(msg, devlink, param_item,
+       err = devlink_nl_param_fill(msg, devlink, 0, param_item,
                                    DEVLINK_CMD_PARAM_GET,
                                    info->snd_portid, info->snd_seq, 0);
        if (err) {
@@ -3090,10 +3115,12 @@ static int devlink_nl_cmd_param_get_doit(struct sk_buff *skb,
        return genlmsg_reply(msg, info);
 }
 
-static int devlink_nl_cmd_param_set_doit(struct sk_buff *skb,
-                                        struct genl_info *info)
+static int __devlink_nl_cmd_param_set_doit(struct devlink *devlink,
+                                          unsigned int port_index,
+                                          struct list_head *param_list,
+                                          struct genl_info *info,
+                                          enum devlink_command cmd)
 {
-       struct devlink *devlink = info->user_ptr[0];
        enum devlink_param_type param_type;
        struct devlink_param_gset_ctx ctx;
        enum devlink_param_cmode cmode;
@@ -3102,7 +3129,7 @@ static int devlink_nl_cmd_param_set_doit(struct sk_buff *skb,
        union devlink_param_value value;
        int err = 0;
 
-       param_item = devlink_param_get_from_info(devlink, info);
+       param_item = devlink_param_get_from_info(param_list, info);
        if (!param_item)
                return -EINVAL;
        param = param_item->param;
@@ -3142,17 +3169,28 @@ static int devlink_nl_cmd_param_set_doit(struct sk_buff *skb,
                        return err;
        }
 
-       devlink_param_notify(devlink, param_item, DEVLINK_CMD_PARAM_NEW);
+       devlink_param_notify(devlink, port_index, param_item, cmd);
        return 0;
 }
 
+static int devlink_nl_cmd_param_set_doit(struct sk_buff *skb,
+                                        struct genl_info *info)
+{
+       struct devlink *devlink = info->user_ptr[0];
+
+       return __devlink_nl_cmd_param_set_doit(devlink, 0, &devlink->param_list,
+                                              info, DEVLINK_CMD_PARAM_NEW);
+}
+
 static int devlink_param_register_one(struct devlink *devlink,
-                                     const struct devlink_param *param)
+                                     unsigned int port_index,
+                                     struct list_head *param_list,
+                                     const struct devlink_param *param,
+                                     enum devlink_command cmd)
 {
        struct devlink_param_item *param_item;
 
-       if (devlink_param_find_by_name(&devlink->param_list,
-                                      param->name))
+       if (devlink_param_find_by_name(param_list, param->name))
                return -EEXIST;
 
        if (param->supported_cmodes == BIT(DEVLINK_PARAM_CMODE_DRIVERINIT))
@@ -3165,24 +3203,111 @@ static int devlink_param_register_one(struct devlink *devlink,
                return -ENOMEM;
        param_item->param = param;
 
-       list_add_tail(&param_item->list, &devlink->param_list);
-       devlink_param_notify(devlink, param_item, DEVLINK_CMD_PARAM_NEW);
+       list_add_tail(&param_item->list, param_list);
+       devlink_param_notify(devlink, port_index, param_item, cmd);
        return 0;
 }
 
 static void devlink_param_unregister_one(struct devlink *devlink,
-                                        const struct devlink_param *param)
+                                        unsigned int port_index,
+                                        struct list_head *param_list,
+                                        const struct devlink_param *param,
+                                        enum devlink_command cmd)
 {
        struct devlink_param_item *param_item;
 
-       param_item = devlink_param_find_by_name(&devlink->param_list,
-                                               param->name);
+       param_item = devlink_param_find_by_name(param_list, param->name);
        WARN_ON(!param_item);
-       devlink_param_notify(devlink, param_item, DEVLINK_CMD_PARAM_DEL);
+       devlink_param_notify(devlink, port_index, param_item, cmd);
        list_del(&param_item->list);
        kfree(param_item);
 }
 
+static int devlink_nl_cmd_port_param_get_dumpit(struct sk_buff *msg,
+                                               struct netlink_callback *cb)
+{
+       struct devlink_param_item *param_item;
+       struct devlink_port *devlink_port;
+       struct devlink *devlink;
+       int start = cb->args[0];
+       int idx = 0;
+       int err;
+
+       mutex_lock(&devlink_mutex);
+       list_for_each_entry(devlink, &devlink_list, list) {
+               if (!net_eq(devlink_net(devlink), sock_net(msg->sk)))
+                       continue;
+               mutex_lock(&devlink->lock);
+               list_for_each_entry(devlink_port, &devlink->port_list, list) {
+                       list_for_each_entry(param_item,
+                                           &devlink_port->param_list, list) {
+                               if (idx < start) {
+                                       idx++;
+                                       continue;
+                               }
+                               err = devlink_nl_param_fill(msg,
+                                               devlink_port->devlink,
+                                               devlink_port->index, param_item,
+                                               DEVLINK_CMD_PORT_PARAM_GET,
+                                               NETLINK_CB(cb->skb).portid,
+                                               cb->nlh->nlmsg_seq,
+                                               NLM_F_MULTI);
+                               if (err) {
+                                       mutex_unlock(&devlink->lock);
+                                       goto out;
+                               }
+                               idx++;
+                       }
+               }
+               mutex_unlock(&devlink->lock);
+       }
+out:
+       mutex_unlock(&devlink_mutex);
+
+       cb->args[0] = idx;
+       return msg->len;
+}
+
+static int devlink_nl_cmd_port_param_get_doit(struct sk_buff *skb,
+                                             struct genl_info *info)
+{
+       struct devlink_port *devlink_port = info->user_ptr[0];
+       struct devlink_param_item *param_item;
+       struct sk_buff *msg;
+       int err;
+
+       param_item = devlink_param_get_from_info(&devlink_port->param_list,
+                                                info);
+       if (!param_item)
+               return -EINVAL;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       err = devlink_nl_param_fill(msg, devlink_port->devlink,
+                                   devlink_port->index, param_item,
+                                   DEVLINK_CMD_PORT_PARAM_GET,
+                                   info->snd_portid, info->snd_seq, 0);
+       if (err) {
+               nlmsg_free(msg);
+               return err;
+       }
+
+       return genlmsg_reply(msg, info);
+}
+
+static int devlink_nl_cmd_port_param_set_doit(struct sk_buff *skb,
+                                             struct genl_info *info)
+{
+       struct devlink_port *devlink_port = info->user_ptr[0];
+
+       return __devlink_nl_cmd_param_set_doit(devlink_port->devlink,
+                                              devlink_port->index,
+                                              &devlink_port->param_list, info,
+                                              DEVLINK_CMD_PORT_PARAM_NEW);
+}
+
 static int devlink_nl_region_snapshot_id_put(struct sk_buff *msg,
                                             struct devlink *devlink,
                                             struct devlink_snapshot *snapshot)
@@ -3597,482 +3722,622 @@ out:
        return 0;
 }
 
-#define DEVLINK_HEALTH_BUFFER_SIZE (4096 - GENL_HDRLEN)
-#define DEVLINK_HEALTH_BUFFER_DATA_SIZE (DEVLINK_HEALTH_BUFFER_SIZE / 2)
-#define DEVLINK_HEALTH_SIZE_TO_BUFFERS(size) DIV_ROUND_UP(size, DEVLINK_HEALTH_BUFFER_DATA_SIZE)
-#define DEVLINK_HEALTH_BUFFER_MAX_CHUNK 1024
-
-struct devlink_health_buffer {
-       void *data;
-       u64 offset;
-       u64 bytes_left;
-       u64 bytes_left_metadata;
-       u64 max_nested_depth;
-       u64 curr_nest;
-};
-
-struct devlink_health_buffer_desc {
-       int attrtype;
-       u16 len;
-       u8 nla_type;
-       u8 nest_end;
-       int value[0];
+struct devlink_info_req {
+       struct sk_buff *msg;
 };
 
-static void
-devlink_health_buffers_reset(struct devlink_health_buffer **buffers_list,
-                            u64 num_of_buffers)
-{
-       u64 i;
-
-       for (i = 0; i < num_of_buffers; i++) {
-               memset(buffers_list[i]->data, 0, DEVLINK_HEALTH_BUFFER_SIZE);
-               buffers_list[i]->offset = 0;
-               buffers_list[i]->bytes_left = DEVLINK_HEALTH_BUFFER_DATA_SIZE;
-               buffers_list[i]->bytes_left_metadata =
-                       DEVLINK_HEALTH_BUFFER_DATA_SIZE;
-               buffers_list[i]->max_nested_depth = 0;
-               buffers_list[i]->curr_nest = 0;
-       }
+int devlink_info_driver_name_put(struct devlink_info_req *req, const char *name)
+{
+       return nla_put_string(req->msg, DEVLINK_ATTR_INFO_DRIVER_NAME, name);
 }
+EXPORT_SYMBOL_GPL(devlink_info_driver_name_put);
 
-static void
-devlink_health_buffers_destroy(struct devlink_health_buffer **buffers_list,
-                              u64 size);
+int devlink_info_serial_number_put(struct devlink_info_req *req, const char *sn)
+{
+       return nla_put_string(req->msg, DEVLINK_ATTR_INFO_SERIAL_NUMBER, sn);
+}
+EXPORT_SYMBOL_GPL(devlink_info_serial_number_put);
 
-static struct devlink_health_buffer **
-devlink_health_buffers_create(u64 size)
+static int devlink_info_version_put(struct devlink_info_req *req, int attr,
+                                   const char *version_name,
+                                   const char *version_value)
 {
-       struct devlink_health_buffer **buffers_list;
-       u64 num_of_buffers = DEVLINK_HEALTH_SIZE_TO_BUFFERS(size);
-       u64 i;
+       struct nlattr *nest;
+       int err;
 
-       buffers_list = kcalloc(num_of_buffers,
-                              sizeof(struct devlink_health_buffer *),
-                              GFP_KERNEL);
-       if (!buffers_list)
-               return NULL;
+       nest = nla_nest_start(req->msg, attr);
+       if (!nest)
+               return -EMSGSIZE;
+
+       err = nla_put_string(req->msg, DEVLINK_ATTR_INFO_VERSION_NAME,
+                            version_name);
+       if (err)
+               goto nla_put_failure;
 
-       for (i = 0; i < num_of_buffers; i++) {
-               struct devlink_health_buffer *buffer;
-               void *data;
+       err = nla_put_string(req->msg, DEVLINK_ATTR_INFO_VERSION_VALUE,
+                            version_value);
+       if (err)
+               goto nla_put_failure;
 
-               buffer = kzalloc(sizeof(*buffer), GFP_KERNEL);
-               data = kzalloc(DEVLINK_HEALTH_BUFFER_SIZE, GFP_KERNEL);
-               if (!buffer || !data) {
-                       kfree(buffer);
-                       kfree(data);
-                       goto buffers_cleanup;
-               }
-               buffers_list[i] = buffer;
-               buffer->data = data;
-       }
-       devlink_health_buffers_reset(buffers_list, num_of_buffers);
+       nla_nest_end(req->msg, nest);
 
-       return buffers_list;
+       return 0;
 
-buffers_cleanup:
-       devlink_health_buffers_destroy(buffers_list, --i);
-       kfree(buffers_list);
-       return NULL;
+nla_put_failure:
+       nla_nest_cancel(req->msg, nest);
+       return err;
 }
 
-static void
-devlink_health_buffers_destroy(struct devlink_health_buffer **buffers_list,
-                              u64 num_of_buffers)
+int devlink_info_version_fixed_put(struct devlink_info_req *req,
+                                  const char *version_name,
+                                  const char *version_value)
 {
-       u64 i;
-
-       for (i = 0; i < num_of_buffers; i++) {
-               kfree(buffers_list[i]->data);
-               kfree(buffers_list[i]);
-       }
+       return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_FIXED,
+                                       version_name, version_value);
 }
+EXPORT_SYMBOL_GPL(devlink_info_version_fixed_put);
 
-void
-devlink_health_buffer_offset_inc(struct devlink_health_buffer *buffer,
-                                int len)
+int devlink_info_version_stored_put(struct devlink_info_req *req,
+                                   const char *version_name,
+                                   const char *version_value)
 {
-       buffer->offset += len;
+       return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_STORED,
+                                       version_name, version_value);
 }
+EXPORT_SYMBOL_GPL(devlink_info_version_stored_put);
 
-/* In order to store a nest, need two descriptors, for start and end */
-#define DEVLINK_HEALTH_BUFFER_NEST_SIZE (sizeof(struct devlink_health_buffer_desc) * 2)
+int devlink_info_version_running_put(struct devlink_info_req *req,
+                                    const char *version_name,
+                                    const char *version_value)
+{
+       return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_RUNNING,
+                                       version_name, version_value);
+}
+EXPORT_SYMBOL_GPL(devlink_info_version_running_put);
 
-int devlink_health_buffer_verify_len(struct devlink_health_buffer *buffer,
-                                    int len, int metadata_len)
+static int
+devlink_nl_info_fill(struct sk_buff *msg, struct devlink *devlink,
+                    enum devlink_command cmd, u32 portid,
+                    u32 seq, int flags, struct netlink_ext_ack *extack)
 {
-       if (len > DEVLINK_HEALTH_BUFFER_DATA_SIZE)
-               return -EINVAL;
+       struct devlink_info_req req;
+       void *hdr;
+       int err;
 
-       if (buffer->bytes_left < len ||
-           buffer->bytes_left_metadata < metadata_len)
-               return -ENOMEM;
+       hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd);
+       if (!hdr)
+               return -EMSGSIZE;
 
+       err = -EMSGSIZE;
+       if (devlink_nl_put_handle(msg, devlink))
+               goto err_cancel_msg;
+
+       req.msg = msg;
+       err = devlink->ops->info_get(devlink, &req, extack);
+       if (err)
+               goto err_cancel_msg;
+
+       genlmsg_end(msg, hdr);
        return 0;
-}
 
-static struct devlink_health_buffer_desc *
-devlink_health_buffer_get_desc_from_offset(struct devlink_health_buffer *buffer)
-{
-       return buffer->data + buffer->offset;
+err_cancel_msg:
+       genlmsg_cancel(msg, hdr);
+       return err;
 }
 
-int
-devlink_health_buffer_nest_start(struct devlink_health_buffer *buffer,
-                                int attrtype)
+static int devlink_nl_cmd_info_get_doit(struct sk_buff *skb,
+                                       struct genl_info *info)
 {
-       struct devlink_health_buffer_desc *desc;
+       struct devlink *devlink = info->user_ptr[0];
+       struct sk_buff *msg;
        int err;
 
-       err = devlink_health_buffer_verify_len(buffer, 0,
-                                              DEVLINK_HEALTH_BUFFER_NEST_SIZE);
-       if (err)
+       if (!devlink->ops || !devlink->ops->info_get)
+               return -EOPNOTSUPP;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       err = devlink_nl_info_fill(msg, devlink, DEVLINK_CMD_INFO_GET,
+                                  info->snd_portid, info->snd_seq, 0,
+                                  info->extack);
+       if (err) {
+               nlmsg_free(msg);
                return err;
+       }
 
-       if (attrtype != DEVLINK_ATTR_HEALTH_BUFFER_OBJECT &&
-           attrtype != DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_PAIR &&
-           attrtype != DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE &&
-           attrtype != DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_ARRAY)
-               return -EINVAL;
+       return genlmsg_reply(msg, info);
+}
 
-       desc = devlink_health_buffer_get_desc_from_offset(buffer);
+static int devlink_nl_cmd_info_get_dumpit(struct sk_buff *msg,
+                                         struct netlink_callback *cb)
+{
+       struct devlink *devlink;
+       int start = cb->args[0];
+       int idx = 0;
+       int err;
 
-       desc->attrtype = attrtype;
-       buffer->bytes_left_metadata -= DEVLINK_HEALTH_BUFFER_NEST_SIZE;
-       devlink_health_buffer_offset_inc(buffer, sizeof(*desc));
+       mutex_lock(&devlink_mutex);
+       list_for_each_entry(devlink, &devlink_list, list) {
+               if (!net_eq(devlink_net(devlink), sock_net(msg->sk)))
+                       continue;
+               if (idx < start) {
+                       idx++;
+                       continue;
+               }
 
-       buffer->curr_nest++;
-       buffer->max_nested_depth = max(buffer->max_nested_depth,
-                                      buffer->curr_nest);
+               mutex_lock(&devlink->lock);
+               err = devlink_nl_info_fill(msg, devlink, DEVLINK_CMD_INFO_GET,
+                                          NETLINK_CB(cb->skb).portid,
+                                          cb->nlh->nlmsg_seq, NLM_F_MULTI,
+                                          cb->extack);
+               mutex_unlock(&devlink->lock);
+               if (err)
+                       break;
+               idx++;
+       }
+       mutex_unlock(&devlink_mutex);
 
-       return 0;
+       cb->args[0] = idx;
+       return msg->len;
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_nest_start);
 
-enum devlink_health_buffer_nest_end_cancel {
-       DEVLINK_HEALTH_BUFFER_NEST_END = 1,
-       DEVLINK_HEALTH_BUFFER_NEST_CANCEL,
+struct devlink_fmsg_item {
+       struct list_head list;
+       int attrtype;
+       u8 nla_type;
+       u16 len;
+       int value[0];
 };
 
-static void
-devlink_health_buffer_nest_end_cancel(struct devlink_health_buffer *buffer,
-                                     enum devlink_health_buffer_nest_end_cancel nest)
+struct devlink_fmsg {
+       struct list_head item_list;
+};
+
+static struct devlink_fmsg *devlink_fmsg_alloc(void)
 {
-       struct devlink_health_buffer_desc *desc;
+       struct devlink_fmsg *fmsg;
 
-       WARN_ON(!buffer->curr_nest);
-       buffer->curr_nest--;
+       fmsg = kzalloc(sizeof(*fmsg), GFP_KERNEL);
+       if (!fmsg)
+               return NULL;
 
-       desc = devlink_health_buffer_get_desc_from_offset(buffer);
-       desc->nest_end = nest;
-       devlink_health_buffer_offset_inc(buffer, sizeof(*desc));
-}
+       INIT_LIST_HEAD(&fmsg->item_list);
 
-void devlink_health_buffer_nest_end(struct devlink_health_buffer *buffer)
-{
-       devlink_health_buffer_nest_end_cancel(buffer,
-                                             DEVLINK_HEALTH_BUFFER_NEST_END);
+       return fmsg;
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_nest_end);
 
-void devlink_health_buffer_nest_cancel(struct devlink_health_buffer *buffer)
+static void devlink_fmsg_free(struct devlink_fmsg *fmsg)
 {
-       devlink_health_buffer_nest_end_cancel(buffer,
-                                             DEVLINK_HEALTH_BUFFER_NEST_CANCEL);
+       struct devlink_fmsg_item *item, *tmp;
+
+       list_for_each_entry_safe(item, tmp, &fmsg->item_list, list) {
+               list_del(&item->list);
+               kfree(item);
+       }
+       kfree(fmsg);
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_nest_cancel);
 
-int
-devlink_health_buffer_put_object_name(struct devlink_health_buffer *buffer,
-                                     char *name)
+static int devlink_fmsg_nest_common(struct devlink_fmsg *fmsg,
+                                   int attrtype)
 {
-       struct devlink_health_buffer_desc *desc;
-       int err;
+       struct devlink_fmsg_item *item;
 
-       err = devlink_health_buffer_verify_len(buffer, strlen(name) + 1,
-                                              sizeof(*desc));
-       if (err)
-               return err;
-
-       desc = devlink_health_buffer_get_desc_from_offset(buffer);
-       desc->attrtype = DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_NAME;
-       desc->nla_type = NLA_NUL_STRING;
-       desc->len = strlen(name) + 1;
-       memcpy(&desc->value, name, desc->len);
-       devlink_health_buffer_offset_inc(buffer, sizeof(*desc) + desc->len);
+       item = kzalloc(sizeof(*item), GFP_KERNEL);
+       if (!item)
+               return -ENOMEM;
 
-       buffer->bytes_left_metadata -= sizeof(*desc);
-       buffer->bytes_left -= (strlen(name) + 1);
+       item->attrtype = attrtype;
+       list_add_tail(&item->list, &fmsg->item_list);
 
        return 0;
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_put_object_name);
 
-static int
-devlink_health_buffer_put_value(struct devlink_health_buffer *buffer,
-                               u8 nla_type, void *value, int len)
+int devlink_fmsg_obj_nest_start(struct devlink_fmsg *fmsg)
 {
-       struct devlink_health_buffer_desc *desc;
-       int err;
-
-       err = devlink_health_buffer_verify_len(buffer, len, sizeof(*desc));
-       if (err)
-               return err;
-
-       desc = devlink_health_buffer_get_desc_from_offset(buffer);
-       desc->attrtype = DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_DATA;
-       desc->nla_type = nla_type;
-       desc->len = len;
-       memcpy(&desc->value, value, len);
-       devlink_health_buffer_offset_inc(buffer, sizeof(*desc) + desc->len);
+       return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_OBJ_NEST_START);
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_start);
 
-       buffer->bytes_left_metadata -= sizeof(*desc);
-       buffer->bytes_left -= len;
+static int devlink_fmsg_nest_end(struct devlink_fmsg *fmsg)
+{
+       return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_NEST_END);
+}
 
-       return 0;
+int devlink_fmsg_obj_nest_end(struct devlink_fmsg *fmsg)
+{
+       return devlink_fmsg_nest_end(fmsg);
 }
+EXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_end);
+
+#define DEVLINK_FMSG_MAX_SIZE (GENLMSG_DEFAULT_SIZE - GENL_HDRLEN - NLA_HDRLEN)
 
-int
-devlink_health_buffer_put_value_u8(struct devlink_health_buffer *buffer,
-                                  u8 value)
+static int devlink_fmsg_put_name(struct devlink_fmsg *fmsg, const char *name)
 {
-       int err;
+       struct devlink_fmsg_item *item;
 
-       err = devlink_health_buffer_put_value(buffer, NLA_U8, &value,
-                                             sizeof(value));
-       if (err)
-               return err;
+       if (strlen(name) + 1 > DEVLINK_FMSG_MAX_SIZE)
+               return -EMSGSIZE;
+
+       item = kzalloc(sizeof(*item) + strlen(name) + 1, GFP_KERNEL);
+       if (!item)
+               return -ENOMEM;
+
+       item->nla_type = NLA_NUL_STRING;
+       item->len = strlen(name) + 1;
+       item->attrtype = DEVLINK_ATTR_FMSG_OBJ_NAME;
+       memcpy(&item->value, name, item->len);
+       list_add_tail(&item->list, &fmsg->item_list);
 
        return 0;
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_put_value_u8);
 
-int
-devlink_health_buffer_put_value_u32(struct devlink_health_buffer *buffer,
-                                   u32 value)
+int devlink_fmsg_pair_nest_start(struct devlink_fmsg *fmsg, const char *name)
 {
        int err;
 
-       err = devlink_health_buffer_put_value(buffer, NLA_U32, &value,
-                                             sizeof(value));
+       err = devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_PAIR_NEST_START);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_put_name(fmsg, name);
        if (err)
                return err;
 
        return 0;
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_put_value_u32);
+EXPORT_SYMBOL_GPL(devlink_fmsg_pair_nest_start);
+
+int devlink_fmsg_pair_nest_end(struct devlink_fmsg *fmsg)
+{
+       return devlink_fmsg_nest_end(fmsg);
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_pair_nest_end);
 
-int
-devlink_health_buffer_put_value_u64(struct devlink_health_buffer *buffer,
-                                   u64 value)
+int devlink_fmsg_arr_pair_nest_start(struct devlink_fmsg *fmsg,
+                                    const char *name)
 {
        int err;
 
-       err = devlink_health_buffer_put_value(buffer, NLA_U64, &value,
-                                             sizeof(value));
+       err = devlink_fmsg_pair_nest_start(fmsg, name);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_ARR_NEST_START);
        if (err)
                return err;
 
        return 0;
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_put_value_u64);
+EXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_start);
 
-int
-devlink_health_buffer_put_value_string(struct devlink_health_buffer *buffer,
-                                      char *name)
+int devlink_fmsg_arr_pair_nest_end(struct devlink_fmsg *fmsg)
 {
        int err;
 
-       if (strlen(name) + 1 > DEVLINK_HEALTH_BUFFER_MAX_CHUNK)
-               return -EINVAL;
+       err = devlink_fmsg_nest_end(fmsg);
+       if (err)
+               return err;
 
-       err = devlink_health_buffer_put_value(buffer, NLA_NUL_STRING, name,
-                                             strlen(name) + 1);
+       err = devlink_fmsg_nest_end(fmsg);
        if (err)
                return err;
 
        return 0;
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_put_value_string);
+EXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_end);
 
-int
-devlink_health_buffer_put_value_data(struct devlink_health_buffer *buffer,
-                                    void *data, int len)
+static int devlink_fmsg_put_value(struct devlink_fmsg *fmsg,
+                                 const void *value, u16 value_len,
+                                 u8 value_nla_type)
 {
-       int err;
+       struct devlink_fmsg_item *item;
 
-       if (len > DEVLINK_HEALTH_BUFFER_MAX_CHUNK)
-               return -EINVAL;
+       if (value_len > DEVLINK_FMSG_MAX_SIZE)
+               return -EMSGSIZE;
 
-       err = devlink_health_buffer_put_value(buffer, NLA_BINARY, data, len);
-       if (err)
-               return err;
+       item = kzalloc(sizeof(*item) + value_len, GFP_KERNEL);
+       if (!item)
+               return -ENOMEM;
+
+       item->nla_type = value_nla_type;
+       item->len = value_len;
+       item->attrtype = DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA;
+       memcpy(&item->value, value, item->len);
+       list_add_tail(&item->list, &fmsg->item_list);
 
        return 0;
 }
-EXPORT_SYMBOL_GPL(devlink_health_buffer_put_value_data);
 
-static int
-devlink_health_buffer_fill_data(struct sk_buff *skb,
-                               struct devlink_health_buffer_desc *desc)
+int devlink_fmsg_bool_put(struct devlink_fmsg *fmsg, bool value)
 {
-       int err = -EINVAL;
-
-       switch (desc->nla_type) {
-       case NLA_U8:
-               err = nla_put_u8(skb, DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_DATA,
-                                *(u8 *)desc->value);
-               break;
-       case NLA_U32:
-               err = nla_put_u32(skb, DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_DATA,
-                                 *(u32 *)desc->value);
-               break;
-       case NLA_U64:
-               err = nla_put_u64_64bit(skb,
-                                       DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_DATA,
-                                       *(u64 *)desc->value, DEVLINK_ATTR_PAD);
-               break;
-       case NLA_NUL_STRING:
-               err = nla_put_string(skb,
-                                    DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_DATA,
-                                    (char *)&desc->value);
-               break;
-       case NLA_BINARY:
-               err = nla_put(skb, DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_DATA,
-                             desc->len, (void *)&desc->value);
-               break;
-       }
+       return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_FLAG);
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_bool_put);
 
-       return err;
+int devlink_fmsg_u8_put(struct devlink_fmsg *fmsg, u8 value)
+{
+       return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U8);
 }
+EXPORT_SYMBOL_GPL(devlink_fmsg_u8_put);
 
-static int
-devlink_health_buffer_fill_type(struct sk_buff *skb,
-                               struct devlink_health_buffer_desc *desc)
+int devlink_fmsg_u32_put(struct devlink_fmsg *fmsg, u32 value)
 {
-       int err = -EINVAL;
+       return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U32);
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_u32_put);
 
-       switch (desc->nla_type) {
-       case NLA_U8:
-               err = nla_put_u8(skb, DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_TYPE,
-                                NLA_U8);
-               break;
-       case NLA_U32:
-               err = nla_put_u8(skb, DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_TYPE,
-                                NLA_U32);
-               break;
-       case NLA_U64:
-               err = nla_put_u8(skb, DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_TYPE,
-                                NLA_U64);
-               break;
-       case NLA_NUL_STRING:
-               err = nla_put_u8(skb, DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_TYPE,
-                                NLA_NUL_STRING);
-               break;
-       case NLA_BINARY:
-               err = nla_put_u8(skb, DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_TYPE,
-                                NLA_BINARY);
-               break;
-       }
+int devlink_fmsg_u64_put(struct devlink_fmsg *fmsg, u64 value)
+{
+       return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U64);
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_u64_put);
 
-       return err;
+int devlink_fmsg_string_put(struct devlink_fmsg *fmsg, const char *value)
+{
+       return devlink_fmsg_put_value(fmsg, value, strlen(value) + 1,
+                                     NLA_NUL_STRING);
 }
+EXPORT_SYMBOL_GPL(devlink_fmsg_string_put);
 
-static inline struct devlink_health_buffer_desc *
-devlink_health_buffer_get_next_desc(struct devlink_health_buffer_desc *desc)
+int devlink_fmsg_binary_put(struct devlink_fmsg *fmsg, const void *value,
+                           u16 value_len)
 {
-       return (void *)&desc->value + desc->len;
+       return devlink_fmsg_put_value(fmsg, value, value_len, NLA_BINARY);
 }
+EXPORT_SYMBOL_GPL(devlink_fmsg_binary_put);
 
-static int
-devlink_health_buffer_prepare_skb(struct sk_buff *skb,
-                                 struct devlink_health_buffer *buffer)
+int devlink_fmsg_bool_pair_put(struct devlink_fmsg *fmsg, const char *name,
+                              bool value)
 {
-       struct devlink_health_buffer_desc *last_desc, *desc;
-       struct nlattr **buffer_nlattr;
        int err;
-       int i = 0;
 
-       buffer_nlattr = kcalloc(buffer->max_nested_depth,
-                               sizeof(*buffer_nlattr), GFP_KERNEL);
-       if (!buffer_nlattr)
-               return -EINVAL;
+       err = devlink_fmsg_pair_nest_start(fmsg, name);
+       if (err)
+               return err;
 
-       last_desc = devlink_health_buffer_get_desc_from_offset(buffer);
-       desc = buffer->data;
-       while (desc != last_desc) {
-               switch (desc->attrtype) {
-               case DEVLINK_ATTR_HEALTH_BUFFER_OBJECT:
-               case DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_PAIR:
-               case DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE:
-               case DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_ARRAY:
-                       buffer_nlattr[i] = nla_nest_start(skb, desc->attrtype);
-                       if (!buffer_nlattr[i])
-                               goto nla_put_failure;
+       err = devlink_fmsg_bool_put(fmsg, value);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_pair_nest_end(fmsg);
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_bool_pair_put);
+
+int devlink_fmsg_u8_pair_put(struct devlink_fmsg *fmsg, const char *name,
+                            u8 value)
+{
+       int err;
+
+       err = devlink_fmsg_pair_nest_start(fmsg, name);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_u8_put(fmsg, value);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_pair_nest_end(fmsg);
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_u8_pair_put);
+
+int devlink_fmsg_u32_pair_put(struct devlink_fmsg *fmsg, const char *name,
+                             u32 value)
+{
+       int err;
+
+       err = devlink_fmsg_pair_nest_start(fmsg, name);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_u32_put(fmsg, value);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_pair_nest_end(fmsg);
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_u32_pair_put);
+
+int devlink_fmsg_u64_pair_put(struct devlink_fmsg *fmsg, const char *name,
+                             u64 value)
+{
+       int err;
+
+       err = devlink_fmsg_pair_nest_start(fmsg, name);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_u64_put(fmsg, value);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_pair_nest_end(fmsg);
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_u64_pair_put);
+
+int devlink_fmsg_string_pair_put(struct devlink_fmsg *fmsg, const char *name,
+                                const char *value)
+{
+       int err;
+
+       err = devlink_fmsg_pair_nest_start(fmsg, name);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_string_put(fmsg, value);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_pair_nest_end(fmsg);
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_string_pair_put);
+
+int devlink_fmsg_binary_pair_put(struct devlink_fmsg *fmsg, const char *name,
+                                const void *value, u16 value_len)
+{
+       int err;
+
+       err = devlink_fmsg_pair_nest_start(fmsg, name);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_binary_put(fmsg, value, value_len);
+       if (err)
+               return err;
+
+       err = devlink_fmsg_pair_nest_end(fmsg);
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_fmsg_binary_pair_put);
+
+static int
+devlink_fmsg_item_fill_type(struct devlink_fmsg_item *msg, struct sk_buff *skb)
+{
+       switch (msg->nla_type) {
+       case NLA_FLAG:
+       case NLA_U8:
+       case NLA_U32:
+       case NLA_U64:
+       case NLA_NUL_STRING:
+       case NLA_BINARY:
+               return nla_put_u8(skb, DEVLINK_ATTR_FMSG_OBJ_VALUE_TYPE,
+                                 msg->nla_type);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int
+devlink_fmsg_item_fill_data(struct devlink_fmsg_item *msg, struct sk_buff *skb)
+{
+       int attrtype = DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA;
+       u8 tmp;
+
+       switch (msg->nla_type) {
+       case NLA_FLAG:
+               /* Always provide flag data, regardless of its value */
+               tmp = *(bool *) msg->value;
+
+               return nla_put_u8(skb, attrtype, tmp);
+       case NLA_U8:
+               return nla_put_u8(skb, attrtype, *(u8 *) msg->value);
+       case NLA_U32:
+               return nla_put_u32(skb, attrtype, *(u32 *) msg->value);
+       case NLA_U64:
+               return nla_put_u64_64bit(skb, attrtype, *(u64 *) msg->value,
+                                        DEVLINK_ATTR_PAD);
+       case NLA_NUL_STRING:
+               return nla_put_string(skb, attrtype, (char *) &msg->value);
+       case NLA_BINARY:
+               return nla_put(skb, attrtype, msg->len, (void *) &msg->value);
+       default:
+               return -EINVAL;
+       }
+}
+
+static int
+devlink_fmsg_prepare_skb(struct devlink_fmsg *fmsg, struct sk_buff *skb,
+                        int *start)
+{
+       struct devlink_fmsg_item *item;
+       struct nlattr *fmsg_nlattr;
+       int i = 0;
+       int err;
+
+       fmsg_nlattr = nla_nest_start(skb, DEVLINK_ATTR_FMSG);
+       if (!fmsg_nlattr)
+               return -EMSGSIZE;
+
+       list_for_each_entry(item, &fmsg->item_list, list) {
+               if (i < *start) {
                        i++;
+                       continue;
+               }
+
+               switch (item->attrtype) {
+               case DEVLINK_ATTR_FMSG_OBJ_NEST_START:
+               case DEVLINK_ATTR_FMSG_PAIR_NEST_START:
+               case DEVLINK_ATTR_FMSG_ARR_NEST_START:
+               case DEVLINK_ATTR_FMSG_NEST_END:
+                       err = nla_put_flag(skb, item->attrtype);
                        break;
-               case DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_DATA:
-                       err = devlink_health_buffer_fill_data(skb, desc);
+               case DEVLINK_ATTR_FMSG_OBJ_VALUE_DATA:
+                       err = devlink_fmsg_item_fill_type(item, skb);
                        if (err)
-                               goto nla_put_failure;
-                       err = devlink_health_buffer_fill_type(skb, desc);
-                       if (err)
-                               goto nla_put_failure;
+                               break;
+                       err = devlink_fmsg_item_fill_data(item, skb);
                        break;
-               case DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_NAME:
-                       err = nla_put_string(skb, desc->attrtype,
-                                            (char *)&desc->value);
-                       if (err)
-                               goto nla_put_failure;
+               case DEVLINK_ATTR_FMSG_OBJ_NAME:
+                       err = nla_put_string(skb, item->attrtype,
+                                            (char *) &item->value);
                        break;
                default:
-                       WARN_ON(!desc->nest_end);
-                       WARN_ON(i <= 0);
-                       if (desc->nest_end == DEVLINK_HEALTH_BUFFER_NEST_END)
-                               nla_nest_end(skb, buffer_nlattr[--i]);
-                       else
-                               nla_nest_cancel(skb, buffer_nlattr[--i]);
+                       err = -EINVAL;
                        break;
                }
-               desc = devlink_health_buffer_get_next_desc(desc);
+               if (!err)
+                       *start = ++i;
+               else
+                       break;
        }
 
-       return 0;
-
-nla_put_failure:
-       kfree(buffer_nlattr);
+       nla_nest_end(skb, fmsg_nlattr);
        return err;
 }
 
-static int
-devlink_health_buffer_snd(struct genl_info *info,
-                         enum devlink_command cmd, int flags,
-                         struct devlink_health_buffer **buffers_array,
-                         u64 num_of_buffers)
+static int devlink_fmsg_snd(struct devlink_fmsg *fmsg,
+                           struct genl_info *info,
+                           enum devlink_command cmd, int flags)
 {
-       struct sk_buff *skb;
        struct nlmsghdr *nlh;
+       struct sk_buff *skb;
+       bool last = false;
+       int index = 0;
        void *hdr;
        int err;
-       u64 i;
 
-       for (i = 0; i < num_of_buffers; i++) {
-               /* Skip buffer if driver did not fill it up with any data */
-               if (!buffers_array[i]->offset)
-                       continue;
+       while (!last) {
+               int tmp_index = index;
 
                skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
                if (!skb)
                        return -ENOMEM;
 
                hdr = genlmsg_put(skb, info->snd_portid, info->snd_seq,
-                                 &devlink_nl_family, NLM_F_MULTI, cmd);
-               if (!hdr)
+                                 &devlink_nl_family, flags | NLM_F_MULTI, cmd);
+               if (!hdr) {
+                       err = -EMSGSIZE;
                        goto nla_put_failure;
+               }
 
-               err = devlink_health_buffer_prepare_skb(skb, buffers_array[i]);
-               if (err)
+               err = devlink_fmsg_prepare_skb(fmsg, skb, &index);
+               if (!err)
+                       last = true;
+               else if (err != -EMSGSIZE || tmp_index == index)
                        goto nla_put_failure;
 
                genlmsg_end(skb, hdr);
@@ -4086,6 +4351,10 @@ devlink_health_buffer_snd(struct genl_info *info,
                return -ENOMEM;
        nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq,
                        NLMSG_DONE, 0, flags | NLM_F_MULTI);
+       if (!nlh) {
+               err = -EMSGSIZE;
+               goto nla_put_failure;
+       }
        err = genlmsg_reply(skb, info);
        if (err)
                return err;
@@ -4093,11 +4362,486 @@ devlink_health_buffer_snd(struct genl_info *info,
        return 0;
 
 nla_put_failure:
-       err = -EIO;
        nlmsg_free(skb);
        return err;
 }
 
+struct devlink_health_reporter {
+       struct list_head list;
+       void *priv;
+       const struct devlink_health_reporter_ops *ops;
+       struct devlink *devlink;
+       struct devlink_fmsg *dump_fmsg;
+       struct mutex dump_lock; /* lock parallel read/write from dump buffers */
+       u64 graceful_period;
+       bool auto_recover;
+       u8 health_state;
+       u64 dump_ts;
+       u64 error_count;
+       u64 recovery_count;
+       u64 last_recovery_ts;
+};
+
+enum devlink_health_reporter_state {
+       DEVLINK_HEALTH_REPORTER_STATE_HEALTHY,
+       DEVLINK_HEALTH_REPORTER_STATE_ERROR,
+};
+
+void *
+devlink_health_reporter_priv(struct devlink_health_reporter *reporter)
+{
+       return reporter->priv;
+}
+EXPORT_SYMBOL_GPL(devlink_health_reporter_priv);
+
+static struct devlink_health_reporter *
+devlink_health_reporter_find_by_name(struct devlink *devlink,
+                                    const char *reporter_name)
+{
+       struct devlink_health_reporter *reporter;
+
+       list_for_each_entry(reporter, &devlink->reporter_list, list)
+               if (!strcmp(reporter->ops->name, reporter_name))
+                       return reporter;
+       return NULL;
+}
+
+/**
+ *     devlink_health_reporter_create - create devlink health reporter
+ *
+ *     @devlink: devlink
+ *     @ops: ops
+ *     @graceful_period: to avoid recovery loops, in msecs
+ *     @auto_recover: auto recover when error occurs
+ *     @priv: priv
+ */
+struct devlink_health_reporter *
+devlink_health_reporter_create(struct devlink *devlink,
+                              const struct devlink_health_reporter_ops *ops,
+                              u64 graceful_period, bool auto_recover,
+                              void *priv)
+{
+       struct devlink_health_reporter *reporter;
+
+       mutex_lock(&devlink->lock);
+       if (devlink_health_reporter_find_by_name(devlink, ops->name)) {
+               reporter = ERR_PTR(-EEXIST);
+               goto unlock;
+       }
+
+       if (WARN_ON(auto_recover && !ops->recover) ||
+           WARN_ON(graceful_period && !ops->recover)) {
+               reporter = ERR_PTR(-EINVAL);
+               goto unlock;
+       }
+
+       reporter = kzalloc(sizeof(*reporter), GFP_KERNEL);
+       if (!reporter) {
+               reporter = ERR_PTR(-ENOMEM);
+               goto unlock;
+       }
+
+       reporter->priv = priv;
+       reporter->ops = ops;
+       reporter->devlink = devlink;
+       reporter->graceful_period = graceful_period;
+       reporter->auto_recover = auto_recover;
+       mutex_init(&reporter->dump_lock);
+       list_add_tail(&reporter->list, &devlink->reporter_list);
+unlock:
+       mutex_unlock(&devlink->lock);
+       return reporter;
+}
+EXPORT_SYMBOL_GPL(devlink_health_reporter_create);
+
+/**
+ *     devlink_health_reporter_destroy - destroy devlink health reporter
+ *
+ *     @reporter: devlink health reporter to destroy
+ */
+void
+devlink_health_reporter_destroy(struct devlink_health_reporter *reporter)
+{
+       mutex_lock(&reporter->devlink->lock);
+       list_del(&reporter->list);
+       mutex_unlock(&reporter->devlink->lock);
+       if (reporter->dump_fmsg)
+               devlink_fmsg_free(reporter->dump_fmsg);
+       kfree(reporter);
+}
+EXPORT_SYMBOL_GPL(devlink_health_reporter_destroy);
+
+static int
+devlink_health_reporter_recover(struct devlink_health_reporter *reporter,
+                               void *priv_ctx)
+{
+       int err;
+
+       if (!reporter->ops->recover)
+               return -EOPNOTSUPP;
+
+       err = reporter->ops->recover(reporter, priv_ctx);
+       if (err)
+               return err;
+
+       reporter->recovery_count++;
+       reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_HEALTHY;
+       reporter->last_recovery_ts = jiffies;
+
+       return 0;
+}
+
+static void
+devlink_health_dump_clear(struct devlink_health_reporter *reporter)
+{
+       if (!reporter->dump_fmsg)
+               return;
+       devlink_fmsg_free(reporter->dump_fmsg);
+       reporter->dump_fmsg = NULL;
+}
+
+static int devlink_health_do_dump(struct devlink_health_reporter *reporter,
+                                 void *priv_ctx)
+{
+       int err;
+
+       if (!reporter->ops->dump)
+               return 0;
+
+       if (reporter->dump_fmsg)
+               return 0;
+
+       reporter->dump_fmsg = devlink_fmsg_alloc();
+       if (!reporter->dump_fmsg) {
+               err = -ENOMEM;
+               return err;
+       }
+
+       err = devlink_fmsg_obj_nest_start(reporter->dump_fmsg);
+       if (err)
+               goto dump_err;
+
+       err = reporter->ops->dump(reporter, reporter->dump_fmsg,
+                                 priv_ctx);
+       if (err)
+               goto dump_err;
+
+       err = devlink_fmsg_obj_nest_end(reporter->dump_fmsg);
+       if (err)
+               goto dump_err;
+
+       reporter->dump_ts = jiffies;
+
+       return 0;
+
+dump_err:
+       devlink_health_dump_clear(reporter);
+       return err;
+}
+
+int devlink_health_report(struct devlink_health_reporter *reporter,
+                         const char *msg, void *priv_ctx)
+{
+       struct devlink *devlink = reporter->devlink;
+
+       /* write a log message of the current error */
+       WARN_ON(!msg);
+       trace_devlink_health_report(devlink, reporter->ops->name, msg);
+       reporter->error_count++;
+
+       /* abort if the previous error wasn't recovered */
+       if (reporter->auto_recover &&
+           (reporter->health_state != DEVLINK_HEALTH_REPORTER_STATE_HEALTHY ||
+            jiffies - reporter->last_recovery_ts <
+            msecs_to_jiffies(reporter->graceful_period))) {
+               trace_devlink_health_recover_aborted(devlink,
+                                                    reporter->ops->name,
+                                                    reporter->health_state,
+                                                    jiffies -
+                                                    reporter->last_recovery_ts);
+               return -ECANCELED;
+       }
+
+       reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_ERROR;
+
+       mutex_lock(&reporter->dump_lock);
+       /* store current dump of current error, for later analysis */
+       devlink_health_do_dump(reporter, priv_ctx);
+       mutex_unlock(&reporter->dump_lock);
+
+       if (reporter->auto_recover)
+               return devlink_health_reporter_recover(reporter, priv_ctx);
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_health_report);
+
+static struct devlink_health_reporter *
+devlink_health_reporter_get_from_info(struct devlink *devlink,
+                                     struct genl_info *info)
+{
+       char *reporter_name;
+
+       if (!info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_NAME])
+               return NULL;
+
+       reporter_name =
+               nla_data(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_NAME]);
+       return devlink_health_reporter_find_by_name(devlink, reporter_name);
+}
+
+static int
+devlink_nl_health_reporter_fill(struct sk_buff *msg,
+                               struct devlink *devlink,
+                               struct devlink_health_reporter *reporter,
+                               enum devlink_command cmd, u32 portid,
+                               u32 seq, int flags)
+{
+       struct nlattr *reporter_attr;
+       void *hdr;
+
+       hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd);
+       if (!hdr)
+               return -EMSGSIZE;
+
+       if (devlink_nl_put_handle(msg, devlink))
+               goto genlmsg_cancel;
+
+       reporter_attr = nla_nest_start(msg, DEVLINK_ATTR_HEALTH_REPORTER);
+       if (!reporter_attr)
+               goto genlmsg_cancel;
+       if (nla_put_string(msg, DEVLINK_ATTR_HEALTH_REPORTER_NAME,
+                          reporter->ops->name))
+               goto reporter_nest_cancel;
+       if (nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_STATE,
+                      reporter->health_state))
+               goto reporter_nest_cancel;
+       if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_ERR,
+                             reporter->error_count, DEVLINK_ATTR_PAD))
+               goto reporter_nest_cancel;
+       if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_RECOVER,
+                             reporter->recovery_count, DEVLINK_ATTR_PAD))
+               goto reporter_nest_cancel;
+       if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD,
+                             reporter->graceful_period,
+                             DEVLINK_ATTR_PAD))
+               goto reporter_nest_cancel;
+       if (nla_put_u8(msg, DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER,
+                      reporter->auto_recover))
+               goto reporter_nest_cancel;
+       if (reporter->dump_fmsg &&
+           nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_DUMP_TS,
+                             jiffies_to_msecs(reporter->dump_ts),
+                             DEVLINK_ATTR_PAD))
+               goto reporter_nest_cancel;
+
+       nla_nest_end(msg, reporter_attr);
+       genlmsg_end(msg, hdr);
+       return 0;
+
+reporter_nest_cancel:
+       nla_nest_end(msg, reporter_attr);
+genlmsg_cancel:
+       genlmsg_cancel(msg, hdr);
+       return -EMSGSIZE;
+}
+
+static int devlink_nl_cmd_health_reporter_get_doit(struct sk_buff *skb,
+                                                  struct genl_info *info)
+{
+       struct devlink *devlink = info->user_ptr[0];
+       struct devlink_health_reporter *reporter;
+       struct sk_buff *msg;
+       int err;
+
+       reporter = devlink_health_reporter_get_from_info(devlink, info);
+       if (!reporter)
+               return -EINVAL;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return -ENOMEM;
+
+       err = devlink_nl_health_reporter_fill(msg, devlink, reporter,
+                                             DEVLINK_CMD_HEALTH_REPORTER_GET,
+                                             info->snd_portid, info->snd_seq,
+                                             0);
+       if (err) {
+               nlmsg_free(msg);
+               return err;
+       }
+
+       return genlmsg_reply(msg, info);
+}
+
+static int
+devlink_nl_cmd_health_reporter_get_dumpit(struct sk_buff *msg,
+                                         struct netlink_callback *cb)
+{
+       struct devlink_health_reporter *reporter;
+       struct devlink *devlink;
+       int start = cb->args[0];
+       int idx = 0;
+       int err;
+
+       mutex_lock(&devlink_mutex);
+       list_for_each_entry(devlink, &devlink_list, list) {
+               if (!net_eq(devlink_net(devlink), sock_net(msg->sk)))
+                       continue;
+               mutex_lock(&devlink->lock);
+               list_for_each_entry(reporter, &devlink->reporter_list,
+                                   list) {
+                       if (idx < start) {
+                               idx++;
+                               continue;
+                       }
+                       err = devlink_nl_health_reporter_fill(msg, devlink,
+                                                             reporter,
+                                                             DEVLINK_CMD_HEALTH_REPORTER_GET,
+                                                             NETLINK_CB(cb->skb).portid,
+                                                             cb->nlh->nlmsg_seq,
+                                                             NLM_F_MULTI);
+                       if (err) {
+                               mutex_unlock(&devlink->lock);
+                               goto out;
+                       }
+                       idx++;
+               }
+               mutex_unlock(&devlink->lock);
+       }
+out:
+       mutex_unlock(&devlink_mutex);
+
+       cb->args[0] = idx;
+       return msg->len;
+}
+
+static int
+devlink_nl_cmd_health_reporter_set_doit(struct sk_buff *skb,
+                                       struct genl_info *info)
+{
+       struct devlink *devlink = info->user_ptr[0];
+       struct devlink_health_reporter *reporter;
+
+       reporter = devlink_health_reporter_get_from_info(devlink, info);
+       if (!reporter)
+               return -EINVAL;
+
+       if (!reporter->ops->recover &&
+           (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] ||
+            info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]))
+               return -EOPNOTSUPP;
+
+       if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD])
+               reporter->graceful_period =
+                       nla_get_u64(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD]);
+
+       if (info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER])
+               reporter->auto_recover =
+                       nla_get_u8(info->attrs[DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER]);
+
+       return 0;
+}
+
+static int devlink_nl_cmd_health_reporter_recover_doit(struct sk_buff *skb,
+                                                      struct genl_info *info)
+{
+       struct devlink *devlink = info->user_ptr[0];
+       struct devlink_health_reporter *reporter;
+
+       reporter = devlink_health_reporter_get_from_info(devlink, info);
+       if (!reporter)
+               return -EINVAL;
+
+       return devlink_health_reporter_recover(reporter, NULL);
+}
+
+static int devlink_nl_cmd_health_reporter_diagnose_doit(struct sk_buff *skb,
+                                                       struct genl_info *info)
+{
+       struct devlink *devlink = info->user_ptr[0];
+       struct devlink_health_reporter *reporter;
+       struct devlink_fmsg *fmsg;
+       int err;
+
+       reporter = devlink_health_reporter_get_from_info(devlink, info);
+       if (!reporter)
+               return -EINVAL;
+
+       if (!reporter->ops->diagnose)
+               return -EOPNOTSUPP;
+
+       fmsg = devlink_fmsg_alloc();
+       if (!fmsg)
+               return -ENOMEM;
+
+       err = devlink_fmsg_obj_nest_start(fmsg);
+       if (err)
+               goto out;
+
+       err = reporter->ops->diagnose(reporter, fmsg);
+       if (err)
+               goto out;
+
+       err = devlink_fmsg_obj_nest_end(fmsg);
+       if (err)
+               goto out;
+
+       err = devlink_fmsg_snd(fmsg, info,
+                              DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE, 0);
+
+out:
+       devlink_fmsg_free(fmsg);
+       return err;
+}
+
+static int devlink_nl_cmd_health_reporter_dump_get_doit(struct sk_buff *skb,
+                                                       struct genl_info *info)
+{
+       struct devlink *devlink = info->user_ptr[0];
+       struct devlink_health_reporter *reporter;
+       int err;
+
+       reporter = devlink_health_reporter_get_from_info(devlink, info);
+       if (!reporter)
+               return -EINVAL;
+
+       if (!reporter->ops->dump)
+               return -EOPNOTSUPP;
+
+       mutex_lock(&reporter->dump_lock);
+       err = devlink_health_do_dump(reporter, NULL);
+       if (err)
+               goto out;
+
+       err = devlink_fmsg_snd(reporter->dump_fmsg, info,
+                              DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET, 0);
+
+out:
+       mutex_unlock(&reporter->dump_lock);
+       return err;
+}
+
+static int
+devlink_nl_cmd_health_reporter_dump_clear_doit(struct sk_buff *skb,
+                                              struct genl_info *info)
+{
+       struct devlink *devlink = info->user_ptr[0];
+       struct devlink_health_reporter *reporter;
+
+       reporter = devlink_health_reporter_get_from_info(devlink, info);
+       if (!reporter)
+               return -EINVAL;
+
+       if (!reporter->ops->dump)
+               return -EOPNOTSUPP;
+
+       mutex_lock(&reporter->dump_lock);
+       devlink_health_dump_clear(reporter);
+       mutex_unlock(&reporter->dump_lock);
+       return 0;
+}
+
 static const struct nla_policy devlink_nl_policy[DEVLINK_ATTR_MAX + 1] = {
        [DEVLINK_ATTR_BUS_NAME] = { .type = NLA_NUL_STRING },
        [DEVLINK_ATTR_DEV_NAME] = { .type = NLA_NUL_STRING },
@@ -4123,6 +4867,9 @@ static const struct nla_policy devlink_nl_policy[DEVLINK_ATTR_MAX + 1] = {
        [DEVLINK_ATTR_PARAM_VALUE_CMODE] = { .type = NLA_U8 },
        [DEVLINK_ATTR_REGION_NAME] = { .type = NLA_NUL_STRING },
        [DEVLINK_ATTR_REGION_SNAPSHOT_ID] = { .type = NLA_U32 },
+       [DEVLINK_ATTR_HEALTH_REPORTER_NAME] = { .type = NLA_NUL_STRING },
+       [DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD] = { .type = NLA_U64 },
+       [DEVLINK_ATTR_HEALTH_REPORTER_AUTO_RECOVER] = { .type = NLA_U8 },
 };
 
 static const struct genl_ops devlink_nl_ops[] = {
@@ -4292,56 +5039,124 @@ static const struct genl_ops devlink_nl_ops[] = {
                .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
        },
        {
-               .cmd = DEVLINK_CMD_RESOURCE_DUMP,
-               .doit = devlink_nl_cmd_resource_dump,
+               .cmd = DEVLINK_CMD_RESOURCE_DUMP,
+               .doit = devlink_nl_cmd_resource_dump,
+               .policy = devlink_nl_policy,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+               /* can be retrieved by unprivileged users */
+       },
+       {
+               .cmd = DEVLINK_CMD_RELOAD,
+               .doit = devlink_nl_cmd_reload,
+               .policy = devlink_nl_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK |
+                                 DEVLINK_NL_FLAG_NO_LOCK,
+       },
+       {
+               .cmd = DEVLINK_CMD_PARAM_GET,
+               .doit = devlink_nl_cmd_param_get_doit,
+               .dumpit = devlink_nl_cmd_param_get_dumpit,
+               .policy = devlink_nl_policy,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+               /* can be retrieved by unprivileged users */
+       },
+       {
+               .cmd = DEVLINK_CMD_PARAM_SET,
+               .doit = devlink_nl_cmd_param_set_doit,
+               .policy = devlink_nl_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+       },
+       {
+               .cmd = DEVLINK_CMD_PORT_PARAM_GET,
+               .doit = devlink_nl_cmd_port_param_get_doit,
+               .dumpit = devlink_nl_cmd_port_param_get_dumpit,
+               .policy = devlink_nl_policy,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_PORT,
+               /* can be retrieved by unprivileged users */
+       },
+       {
+               .cmd = DEVLINK_CMD_PORT_PARAM_SET,
+               .doit = devlink_nl_cmd_port_param_set_doit,
+               .policy = devlink_nl_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_PORT,
+       },
+       {
+               .cmd = DEVLINK_CMD_REGION_GET,
+               .doit = devlink_nl_cmd_region_get_doit,
+               .dumpit = devlink_nl_cmd_region_get_dumpit,
+               .policy = devlink_nl_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+       },
+       {
+               .cmd = DEVLINK_CMD_REGION_DEL,
+               .doit = devlink_nl_cmd_region_del,
+               .policy = devlink_nl_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+       },
+       {
+               .cmd = DEVLINK_CMD_REGION_READ,
+               .dumpit = devlink_nl_cmd_region_read_dumpit,
+               .policy = devlink_nl_policy,
+               .flags = GENL_ADMIN_PERM,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+       },
+       {
+               .cmd = DEVLINK_CMD_INFO_GET,
+               .doit = devlink_nl_cmd_info_get_doit,
+               .dumpit = devlink_nl_cmd_info_get_dumpit,
                .policy = devlink_nl_policy,
                .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
                /* can be retrieved by unprivileged users */
        },
        {
-               .cmd = DEVLINK_CMD_RELOAD,
-               .doit = devlink_nl_cmd_reload,
+               .cmd = DEVLINK_CMD_HEALTH_REPORTER_GET,
+               .doit = devlink_nl_cmd_health_reporter_get_doit,
+               .dumpit = devlink_nl_cmd_health_reporter_get_dumpit,
                .policy = devlink_nl_policy,
-               .flags = GENL_ADMIN_PERM,
-               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK |
-                                 DEVLINK_NL_FLAG_NO_LOCK,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+               /* can be retrieved by unprivileged users */
        },
        {
-               .cmd = DEVLINK_CMD_PARAM_GET,
-               .doit = devlink_nl_cmd_param_get_doit,
-               .dumpit = devlink_nl_cmd_param_get_dumpit,
+               .cmd = DEVLINK_CMD_HEALTH_REPORTER_SET,
+               .doit = devlink_nl_cmd_health_reporter_set_doit,
                .policy = devlink_nl_policy,
+               .flags = GENL_ADMIN_PERM,
                .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
-               /* can be retrieved by unprivileged users */
        },
        {
-               .cmd = DEVLINK_CMD_PARAM_SET,
-               .doit = devlink_nl_cmd_param_set_doit,
+               .cmd = DEVLINK_CMD_HEALTH_REPORTER_RECOVER,
+               .doit = devlink_nl_cmd_health_reporter_recover_doit,
                .policy = devlink_nl_policy,
                .flags = GENL_ADMIN_PERM,
                .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
        },
        {
-               .cmd = DEVLINK_CMD_REGION_GET,
-               .doit = devlink_nl_cmd_region_get_doit,
-               .dumpit = devlink_nl_cmd_region_get_dumpit,
+               .cmd = DEVLINK_CMD_HEALTH_REPORTER_DIAGNOSE,
+               .doit = devlink_nl_cmd_health_reporter_diagnose_doit,
                .policy = devlink_nl_policy,
                .flags = GENL_ADMIN_PERM,
                .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
        },
        {
-               .cmd = DEVLINK_CMD_REGION_DEL,
-               .doit = devlink_nl_cmd_region_del,
+               .cmd = DEVLINK_CMD_HEALTH_REPORTER_DUMP_GET,
+               .doit = devlink_nl_cmd_health_reporter_dump_get_doit,
                .policy = devlink_nl_policy,
                .flags = GENL_ADMIN_PERM,
-               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK |
+                                 DEVLINK_NL_FLAG_NO_LOCK,
        },
        {
-               .cmd = DEVLINK_CMD_REGION_READ,
-               .dumpit = devlink_nl_cmd_region_read_dumpit,
+               .cmd = DEVLINK_CMD_HEALTH_REPORTER_DUMP_CLEAR,
+               .doit = devlink_nl_cmd_health_reporter_dump_clear_doit,
                .policy = devlink_nl_policy,
                .flags = GENL_ADMIN_PERM,
-               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK,
+               .internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK |
+                                 DEVLINK_NL_FLAG_NO_LOCK,
        },
 };
 
@@ -4383,6 +5198,7 @@ struct devlink *devlink_alloc(const struct devlink_ops *ops, size_t priv_size)
        INIT_LIST_HEAD(&devlink->resource_list);
        INIT_LIST_HEAD(&devlink->param_list);
        INIT_LIST_HEAD(&devlink->region_list);
+       INIT_LIST_HEAD(&devlink->reporter_list);
        mutex_init(&devlink->lock);
        return devlink;
 }
@@ -4455,6 +5271,7 @@ int devlink_port_register(struct devlink *devlink,
        devlink_port->index = port_index;
        devlink_port->registered = true;
        list_add_tail(&devlink_port->list, &devlink->port_list);
+       INIT_LIST_HEAD(&devlink_port->param_list);
        mutex_unlock(&devlink->lock);
        devlink_port_notify(devlink_port, DEVLINK_CMD_PORT_NEW);
        return 0;
@@ -4972,18 +5789,23 @@ out:
 }
 EXPORT_SYMBOL_GPL(devlink_resource_occ_get_unregister);
 
-/**
- *     devlink_params_register - register configuration parameters
- *
- *     @devlink: devlink
- *     @params: configuration parameters array
- *     @params_count: number of parameters provided
- *
- *     Register the configuration parameters supported by the driver.
- */
-int devlink_params_register(struct devlink *devlink,
-                           const struct devlink_param *params,
-                           size_t params_count)
+static int devlink_param_verify(const struct devlink_param *param)
+{
+       if (!param || !param->name || !param->supported_cmodes)
+               return -EINVAL;
+       if (param->generic)
+               return devlink_param_generic_verify(param);
+       else
+               return devlink_param_driver_verify(param);
+}
+
+static int __devlink_params_register(struct devlink *devlink,
+                                    unsigned int port_index,
+                                    struct list_head *param_list,
+                                    const struct devlink_param *params,
+                                    size_t params_count,
+                                    enum devlink_command reg_cmd,
+                                    enum devlink_command unreg_cmd)
 {
        const struct devlink_param *param = params;
        int i;
@@ -4991,20 +5813,12 @@ int devlink_params_register(struct devlink *devlink,
 
        mutex_lock(&devlink->lock);
        for (i = 0; i < params_count; i++, param++) {
-               if (!param || !param->name || !param->supported_cmodes) {
-                       err = -EINVAL;
+               err = devlink_param_verify(param);
+               if (err)
                        goto rollback;
-               }
-               if (param->generic) {
-                       err = devlink_param_generic_verify(param);
-                       if (err)
-                               goto rollback;
-               } else {
-                       err = devlink_param_driver_verify(param);
-                       if (err)
-                               goto rollback;
-               }
-               err = devlink_param_register_one(devlink, param);
+
+               err = devlink_param_register_one(devlink, port_index,
+                                                param_list, param, reg_cmd);
                if (err)
                        goto rollback;
        }
@@ -5016,11 +5830,48 @@ rollback:
        if (!i)
                goto unlock;
        for (param--; i > 0; i--, param--)
-               devlink_param_unregister_one(devlink, param);
+               devlink_param_unregister_one(devlink, port_index, param_list,
+                                            param, unreg_cmd);
 unlock:
        mutex_unlock(&devlink->lock);
        return err;
 }
+
+static void __devlink_params_unregister(struct devlink *devlink,
+                                       unsigned int port_index,
+                                       struct list_head *param_list,
+                                       const struct devlink_param *params,
+                                       size_t params_count,
+                                       enum devlink_command cmd)
+{
+       const struct devlink_param *param = params;
+       int i;
+
+       mutex_lock(&devlink->lock);
+       for (i = 0; i < params_count; i++, param++)
+               devlink_param_unregister_one(devlink, 0, param_list, param,
+                                            cmd);
+       mutex_unlock(&devlink->lock);
+}
+
+/**
+ *     devlink_params_register - register configuration parameters
+ *
+ *     @devlink: devlink
+ *     @params: configuration parameters array
+ *     @params_count: number of parameters provided
+ *
+ *     Register the configuration parameters supported by the driver.
+ */
+int devlink_params_register(struct devlink *devlink,
+                           const struct devlink_param *params,
+                           size_t params_count)
+{
+       return __devlink_params_register(devlink, 0, &devlink->param_list,
+                                        params, params_count,
+                                        DEVLINK_CMD_PARAM_NEW,
+                                        DEVLINK_CMD_PARAM_DEL);
+}
 EXPORT_SYMBOL_GPL(devlink_params_register);
 
 /**
@@ -5033,36 +5884,103 @@ void devlink_params_unregister(struct devlink *devlink,
                               const struct devlink_param *params,
                               size_t params_count)
 {
-       const struct devlink_param *param = params;
-       int i;
-
-       mutex_lock(&devlink->lock);
-       for (i = 0; i < params_count; i++, param++)
-               devlink_param_unregister_one(devlink, param);
-       mutex_unlock(&devlink->lock);
+       return __devlink_params_unregister(devlink, 0, &devlink->param_list,
+                                          params, params_count,
+                                          DEVLINK_CMD_PARAM_DEL);
 }
 EXPORT_SYMBOL_GPL(devlink_params_unregister);
 
 /**
- *     devlink_param_driverinit_value_get - get configuration parameter
- *                                          value for driver initializing
+ *     devlink_params_publish - publish configuration parameters
  *
  *     @devlink: devlink
- *     @param_id: parameter ID
- *     @init_val: value of parameter in driverinit configuration mode
  *
- *     This function should be used by the driver to get driverinit
- *     configuration for initialization after reload command.
+ *     Publish previously registered configuration parameters.
  */
-int devlink_param_driverinit_value_get(struct devlink *devlink, u32 param_id,
-                                      union devlink_param_value *init_val)
+void devlink_params_publish(struct devlink *devlink)
 {
        struct devlink_param_item *param_item;
 
-       if (!devlink->ops || !devlink->ops->reload)
-               return -EOPNOTSUPP;
+       list_for_each_entry(param_item, &devlink->param_list, list) {
+               if (param_item->published)
+                       continue;
+               param_item->published = true;
+               devlink_param_notify(devlink, 0, param_item,
+                                    DEVLINK_CMD_PARAM_NEW);
+       }
+}
+EXPORT_SYMBOL_GPL(devlink_params_publish);
 
-       param_item = devlink_param_find_by_id(&devlink->param_list, param_id);
+/**
+ *     devlink_params_unpublish - unpublish configuration parameters
+ *
+ *     @devlink: devlink
+ *
+ *     Unpublish previously registered configuration parameters.
+ */
+void devlink_params_unpublish(struct devlink *devlink)
+{
+       struct devlink_param_item *param_item;
+
+       list_for_each_entry(param_item, &devlink->param_list, list) {
+               if (!param_item->published)
+                       continue;
+               param_item->published = false;
+               devlink_param_notify(devlink, 0, param_item,
+                                    DEVLINK_CMD_PARAM_DEL);
+       }
+}
+EXPORT_SYMBOL_GPL(devlink_params_unpublish);
+
+/**
+ *     devlink_port_params_register - register port configuration parameters
+ *
+ *     @devlink_port: devlink port
+ *     @params: configuration parameters array
+ *     @params_count: number of parameters provided
+ *
+ *     Register the configuration parameters supported by the port.
+ */
+int devlink_port_params_register(struct devlink_port *devlink_port,
+                                const struct devlink_param *params,
+                                size_t params_count)
+{
+       return __devlink_params_register(devlink_port->devlink,
+                                        devlink_port->index,
+                                        &devlink_port->param_list, params,
+                                        params_count,
+                                        DEVLINK_CMD_PORT_PARAM_NEW,
+                                        DEVLINK_CMD_PORT_PARAM_DEL);
+}
+EXPORT_SYMBOL_GPL(devlink_port_params_register);
+
+/**
+ *     devlink_port_params_unregister - unregister port configuration
+ *     parameters
+ *
+ *     @devlink_port: devlink port
+ *     @params: configuration parameters array
+ *     @params_count: number of parameters provided
+ */
+void devlink_port_params_unregister(struct devlink_port *devlink_port,
+                                   const struct devlink_param *params,
+                                   size_t params_count)
+{
+       return __devlink_params_unregister(devlink_port->devlink,
+                                          devlink_port->index,
+                                          &devlink_port->param_list,
+                                          params, params_count,
+                                          DEVLINK_CMD_PORT_PARAM_DEL);
+}
+EXPORT_SYMBOL_GPL(devlink_port_params_unregister);
+
+static int
+__devlink_param_driverinit_value_get(struct list_head *param_list, u32 param_id,
+                                    union devlink_param_value *init_val)
+{
+       struct devlink_param_item *param_item;
+
+       param_item = devlink_param_find_by_id(param_list, param_id);
        if (!param_item)
                return -EINVAL;
 
@@ -5078,6 +5996,54 @@ int devlink_param_driverinit_value_get(struct devlink *devlink, u32 param_id,
 
        return 0;
 }
+
+static int
+__devlink_param_driverinit_value_set(struct devlink *devlink,
+                                    unsigned int port_index,
+                                    struct list_head *param_list, u32 param_id,
+                                    union devlink_param_value init_val,
+                                    enum devlink_command cmd)
+{
+       struct devlink_param_item *param_item;
+
+       param_item = devlink_param_find_by_id(param_list, param_id);
+       if (!param_item)
+               return -EINVAL;
+
+       if (!devlink_param_cmode_is_supported(param_item->param,
+                                             DEVLINK_PARAM_CMODE_DRIVERINIT))
+               return -EOPNOTSUPP;
+
+       if (param_item->param->type == DEVLINK_PARAM_TYPE_STRING)
+               strcpy(param_item->driverinit_value.vstr, init_val.vstr);
+       else
+               param_item->driverinit_value = init_val;
+       param_item->driverinit_value_valid = true;
+
+       devlink_param_notify(devlink, port_index, param_item, cmd);
+       return 0;
+}
+
+/**
+ *     devlink_param_driverinit_value_get - get configuration parameter
+ *                                          value for driver initializing
+ *
+ *     @devlink: devlink
+ *     @param_id: parameter ID
+ *     @init_val: value of parameter in driverinit configuration mode
+ *
+ *     This function should be used by the driver to get driverinit
+ *     configuration for initialization after reload command.
+ */
+int devlink_param_driverinit_value_get(struct devlink *devlink, u32 param_id,
+                                      union devlink_param_value *init_val)
+{
+       if (!devlink->ops || !devlink->ops->reload)
+               return -EOPNOTSUPP;
+
+       return __devlink_param_driverinit_value_get(&devlink->param_list,
+                                                   param_id, init_val);
+}
 EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_get);
 
 /**
@@ -5095,26 +6061,61 @@ EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_get);
 int devlink_param_driverinit_value_set(struct devlink *devlink, u32 param_id,
                                       union devlink_param_value init_val)
 {
-       struct devlink_param_item *param_item;
+       return __devlink_param_driverinit_value_set(devlink, 0,
+                                                   &devlink->param_list,
+                                                   param_id, init_val,
+                                                   DEVLINK_CMD_PARAM_NEW);
+}
+EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_set);
 
-       param_item = devlink_param_find_by_id(&devlink->param_list, param_id);
-       if (!param_item)
-               return -EINVAL;
+/**
+ *     devlink_port_param_driverinit_value_get - get configuration parameter
+ *                                             value for driver initializing
+ *
+ *     @devlink_port: devlink_port
+ *     @param_id: parameter ID
+ *     @init_val: value of parameter in driverinit configuration mode
+ *
+ *     This function should be used by the driver to get driverinit
+ *     configuration for initialization after reload command.
+ */
+int devlink_port_param_driverinit_value_get(struct devlink_port *devlink_port,
+                                           u32 param_id,
+                                           union devlink_param_value *init_val)
+{
+       struct devlink *devlink = devlink_port->devlink;
 
-       if (!devlink_param_cmode_is_supported(param_item->param,
-                                             DEVLINK_PARAM_CMODE_DRIVERINIT))
+       if (!devlink->ops || !devlink->ops->reload)
                return -EOPNOTSUPP;
 
-       if (param_item->param->type == DEVLINK_PARAM_TYPE_STRING)
-               strcpy(param_item->driverinit_value.vstr, init_val.vstr);
-       else
-               param_item->driverinit_value = init_val;
-       param_item->driverinit_value_valid = true;
+       return __devlink_param_driverinit_value_get(&devlink_port->param_list,
+                                                   param_id, init_val);
+}
+EXPORT_SYMBOL_GPL(devlink_port_param_driverinit_value_get);
 
-       devlink_param_notify(devlink, param_item, DEVLINK_CMD_PARAM_NEW);
-       return 0;
+/**
+ *     devlink_port_param_driverinit_value_set - set value of configuration
+ *                                               parameter for driverinit
+ *                                               configuration mode
+ *
+ *     @devlink_port: devlink_port
+ *     @param_id: parameter ID
+ *     @init_val: value of parameter to set for driverinit configuration mode
+ *
+ *     This function should be used by the driver to set driverinit
+ *     configuration mode default value.
+ */
+int devlink_port_param_driverinit_value_set(struct devlink_port *devlink_port,
+                                           u32 param_id,
+                                           union devlink_param_value init_val)
+{
+       return __devlink_param_driverinit_value_set(devlink_port->devlink,
+                                                   devlink_port->index,
+                                                   &devlink_port->param_list,
+                                                   param_id, init_val,
+                                                   DEVLINK_CMD_PORT_PARAM_NEW);
 }
-EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_set);
+EXPORT_SYMBOL_GPL(devlink_port_param_driverinit_value_set);
 
 /**
  *     devlink_param_value_changed - notify devlink on a parameter's value
@@ -5127,7 +6128,6 @@ EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_set);
  *     This function should be used by the driver to notify devlink on value
  *     change, excluding driverinit configuration mode.
  *     For driverinit configuration mode driver should use the function
- *     devlink_param_driverinit_value_set() instead.
  */
 void devlink_param_value_changed(struct devlink *devlink, u32 param_id)
 {
@@ -5136,10 +6136,37 @@ void devlink_param_value_changed(struct devlink *devlink, u32 param_id)
        param_item = devlink_param_find_by_id(&devlink->param_list, param_id);
        WARN_ON(!param_item);
 
-       devlink_param_notify(devlink, param_item, DEVLINK_CMD_PARAM_NEW);
+       devlink_param_notify(devlink, 0, param_item, DEVLINK_CMD_PARAM_NEW);
 }
 EXPORT_SYMBOL_GPL(devlink_param_value_changed);
 
+/**
+ *     devlink_port_param_value_changed - notify devlink on a parameter's value
+ *                                      change. Should be called by the driver
+ *                                      right after the change.
+ *
+ *     @devlink_port: devlink_port
+ *     @param_id: parameter ID
+ *
+ *     This function should be used by the driver to notify devlink on value
+ *     change, excluding driverinit configuration mode.
+ *     For driverinit configuration mode driver should use the function
+ *     devlink_port_param_driverinit_value_set() instead.
+ */
+void devlink_port_param_value_changed(struct devlink_port *devlink_port,
+                                     u32 param_id)
+{
+       struct devlink_param_item *param_item;
+
+       param_item = devlink_param_find_by_id(&devlink_port->param_list,
+                                             param_id);
+       WARN_ON(!param_item);
+
+       devlink_param_notify(devlink_port->devlink, devlink_port->index,
+                            param_item, DEVLINK_CMD_PORT_PARAM_NEW);
+}
+EXPORT_SYMBOL_GPL(devlink_port_param_value_changed);
+
 /**
  *     devlink_param_value_str_fill - Safely fill-up the string preventing
  *                                    from overflow of the preallocated buffer
@@ -5309,6 +6336,69 @@ unlock:
 }
 EXPORT_SYMBOL_GPL(devlink_region_snapshot_create);
 
+static void __devlink_compat_running_version(struct devlink *devlink,
+                                            char *buf, size_t len)
+{
+       const struct nlattr *nlattr;
+       struct devlink_info_req req;
+       struct sk_buff *msg;
+       int rem, err;
+
+       if (!devlink->ops->info_get)
+               return;
+
+       msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!msg)
+               return;
+
+       req.msg = msg;
+       err = devlink->ops->info_get(devlink, &req, NULL);
+       if (err)
+               goto free_msg;
+
+       nla_for_each_attr(nlattr, (void *)msg->data, msg->len, rem) {
+               const struct nlattr *kv;
+               int rem_kv;
+
+               if (nla_type(nlattr) != DEVLINK_ATTR_INFO_VERSION_RUNNING)
+                       continue;
+
+               nla_for_each_nested(kv, nlattr, rem_kv) {
+                       if (nla_type(kv) != DEVLINK_ATTR_INFO_VERSION_VALUE)
+                               continue;
+
+                       strlcat(buf, nla_data(kv), len);
+                       strlcat(buf, " ", len);
+               }
+       }
+free_msg:
+       nlmsg_free(msg);
+}
+
+void devlink_compat_running_version(struct net_device *dev,
+                                   char *buf, size_t len)
+{
+       struct devlink_port *devlink_port;
+       struct devlink *devlink;
+
+       mutex_lock(&devlink_mutex);
+       list_for_each_entry(devlink, &devlink_list, list) {
+               mutex_lock(&devlink->lock);
+               list_for_each_entry(devlink_port, &devlink->port_list, list) {
+                       if (devlink_port->type == DEVLINK_PORT_TYPE_ETH ||
+                           devlink_port->type_dev == dev) {
+                               __devlink_compat_running_version(devlink,
+                                                                buf, len);
+                               mutex_unlock(&devlink->lock);
+                               goto out;
+                       }
+               }
+               mutex_unlock(&devlink->lock);
+       }
+out:
+       mutex_unlock(&devlink_mutex);
+}
+
 static int __init devlink_module_init(void)
 {
        return genl_register_family(&devlink_nl_family);