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];
+};
+
+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;
+       }
+}
+
+static void
+devlink_health_buffers_destroy(struct devlink_health_buffer **buffers_list,
+                              u64 size);
+
+static struct devlink_health_buffer **
+devlink_health_buffers_create(u64 size)
+{
+       struct devlink_health_buffer **buffers_list;
+       u64 num_of_buffers = DEVLINK_HEALTH_SIZE_TO_BUFFERS(size);
+       u64 i;
+
+       buffers_list = kcalloc(num_of_buffers,
+                              sizeof(struct devlink_health_buffer *),
+                              GFP_KERNEL);
+       if (!buffers_list)
+               return NULL;
+
+       for (i = 0; i < num_of_buffers; i++) {
+               struct devlink_health_buffer *buffer;
+               void *data;
+
+               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);
+
+       return buffers_list;
+
+buffers_cleanup:
+       devlink_health_buffers_destroy(buffers_list, --i);
+       kfree(buffers_list);
+       return NULL;
+}
+
+static void
+devlink_health_buffers_destroy(struct devlink_health_buffer **buffers_list,
+                              u64 num_of_buffers)
+{
+       u64 i;
+
+       for (i = 0; i < num_of_buffers; i++) {
+               kfree(buffers_list[i]->data);
+               kfree(buffers_list[i]);
+       }
+}
+
+void
+devlink_health_buffer_offset_inc(struct devlink_health_buffer *buffer,
+                                int len)
+{
+       buffer->offset += len;
+}
+
+/* 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_health_buffer_verify_len(struct devlink_health_buffer *buffer,
+                                    int len, int metadata_len)
+{
+       if (len > DEVLINK_HEALTH_BUFFER_DATA_SIZE)
+               return -EINVAL;
+
+       if (buffer->bytes_left < len ||
+           buffer->bytes_left_metadata < metadata_len)
+               return -ENOMEM;
+
+       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;
+}
+
+int
+devlink_health_buffer_nest_start(struct devlink_health_buffer *buffer,
+                                int attrtype)
+{
+       struct devlink_health_buffer_desc *desc;
+       int err;
+
+       err = devlink_health_buffer_verify_len(buffer, 0,
+                                              DEVLINK_HEALTH_BUFFER_NEST_SIZE);
+       if (err)
+               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;
+
+       desc = devlink_health_buffer_get_desc_from_offset(buffer);
+
+       desc->attrtype = attrtype;
+       buffer->bytes_left_metadata -= DEVLINK_HEALTH_BUFFER_NEST_SIZE;
+       devlink_health_buffer_offset_inc(buffer, sizeof(*desc));
+
+       buffer->curr_nest++;
+       buffer->max_nested_depth = max(buffer->max_nested_depth,
+                                      buffer->curr_nest);
+
+       return 0;
+}
+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,
+};
+
+static void
+devlink_health_buffer_nest_end_cancel(struct devlink_health_buffer *buffer,
+                                     enum devlink_health_buffer_nest_end_cancel nest)
+{
+       struct devlink_health_buffer_desc *desc;
+
+       WARN_ON(!buffer->curr_nest);
+       buffer->curr_nest--;
+
+       desc = devlink_health_buffer_get_desc_from_offset(buffer);
+       desc->nest_end = nest;
+       devlink_health_buffer_offset_inc(buffer, sizeof(*desc));
+}
+
+void devlink_health_buffer_nest_end(struct devlink_health_buffer *buffer)
+{
+       devlink_health_buffer_nest_end_cancel(buffer,
+                                             DEVLINK_HEALTH_BUFFER_NEST_END);
+}
+EXPORT_SYMBOL_GPL(devlink_health_buffer_nest_end);
+
+void devlink_health_buffer_nest_cancel(struct devlink_health_buffer *buffer)
+{
+       devlink_health_buffer_nest_end_cancel(buffer,
+                                             DEVLINK_HEALTH_BUFFER_NEST_CANCEL);
+}
+EXPORT_SYMBOL_GPL(devlink_health_buffer_nest_cancel);
+
+int
+devlink_health_buffer_put_object_name(struct devlink_health_buffer *buffer,
+                                     char *name)
+{
+       struct devlink_health_buffer_desc *desc;
+       int err;
+
+       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);
+
+       buffer->bytes_left_metadata -= sizeof(*desc);
+       buffer->bytes_left -= (strlen(name) + 1);
+
+       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)
+{
+       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);
+
+       buffer->bytes_left_metadata -= sizeof(*desc);
+       buffer->bytes_left -= len;
+
+       return 0;
+}
+
+int
+devlink_health_buffer_put_value_u8(struct devlink_health_buffer *buffer,
+                                  u8 value)
+{
+       int err;
+
+       err = devlink_health_buffer_put_value(buffer, NLA_U8, &value,
+                                             sizeof(value));
+       if (err)
+               return err;
+
+       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 err;
+
+       err = devlink_health_buffer_put_value(buffer, NLA_U32, &value,
+                                             sizeof(value));
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_health_buffer_put_value_u32);
+
+int
+devlink_health_buffer_put_value_u64(struct devlink_health_buffer *buffer,
+                                   u64 value)
+{
+       int err;
+
+       err = devlink_health_buffer_put_value(buffer, NLA_U64, &value,
+                                             sizeof(value));
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_health_buffer_put_value_u64);
+
+int
+devlink_health_buffer_put_value_string(struct devlink_health_buffer *buffer,
+                                      char *name)
+{
+       int err;
+
+       if (strlen(name) + 1 > DEVLINK_HEALTH_BUFFER_MAX_CHUNK)
+               return -EINVAL;
+
+       err = devlink_health_buffer_put_value(buffer, NLA_NUL_STRING, name,
+                                             strlen(name) + 1);
+       if (err)
+               return err;
+
+       return 0;
+}
+EXPORT_SYMBOL_GPL(devlink_health_buffer_put_value_string);
+
+int
+devlink_health_buffer_put_value_data(struct devlink_health_buffer *buffer,
+                                    void *data, int len)
+{
+       int err;
+
+       if (len > DEVLINK_HEALTH_BUFFER_MAX_CHUNK)
+               return -EINVAL;
+
+       err = devlink_health_buffer_put_value(buffer, NLA_BINARY, data, len);
+       if (err)
+               return err;
+
+       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 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 err;
+}
+
+static int
+devlink_health_buffer_fill_type(struct sk_buff *skb,
+                               struct devlink_health_buffer_desc *desc)
+{
+       int err = -EINVAL;
+
+       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;
+       }
+
+       return err;
+}
+
+static inline struct devlink_health_buffer_desc *
+devlink_health_buffer_get_next_desc(struct devlink_health_buffer_desc *desc)
+{
+       return (void *)&desc->value + desc->len;
+}
+
+static int
+devlink_health_buffer_prepare_skb(struct sk_buff *skb,
+                                 struct devlink_health_buffer *buffer)
+{
+       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;
+
+       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;
+                       i++;
+                       break;
+               case DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_VALUE_DATA:
+                       err = devlink_health_buffer_fill_data(skb, desc);
+                       if (err)
+                               goto nla_put_failure;
+                       err = devlink_health_buffer_fill_type(skb, desc);
+                       if (err)
+                               goto nla_put_failure;
+                       break;
+               case DEVLINK_ATTR_HEALTH_BUFFER_OBJECT_NAME:
+                       err = nla_put_string(skb, desc->attrtype,
+                                            (char *)&desc->value);
+                       if (err)
+                               goto nla_put_failure;
+                       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]);
+                       break;
+               }
+               desc = devlink_health_buffer_get_next_desc(desc);
+       }
+
+       return 0;
+
+nla_put_failure:
+       kfree(buffer_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)
+{
+       struct sk_buff *skb;
+       struct nlmsghdr *nlh;
+       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;
+
+               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)
+                       goto nla_put_failure;
+
+               err = devlink_health_buffer_prepare_skb(skb, buffers_array[i]);
+               if (err)
+                       goto nla_put_failure;
+
+               genlmsg_end(skb, hdr);
+               err = genlmsg_reply(skb, info);
+               if (err)
+                       return err;
+       }
+
+       skb = genlmsg_new(GENLMSG_DEFAULT_SIZE, GFP_KERNEL);
+       if (!skb)
+               return -ENOMEM;
+       nlh = nlmsg_put(skb, info->snd_portid, info->snd_seq,
+                       NLMSG_DONE, 0, flags | NLM_F_MULTI);
+       err = genlmsg_reply(skb, info);
+       if (err)
+               return err;
+
+       return 0;
+
+nla_put_failure:
+       err = -EIO;
+       nlmsg_free(skb);
+       return err;
+}
+
 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 },