diff options
Diffstat (limited to 'net/core/devlink.c')
| -rw-r--r-- | net/core/devlink.c | 1923 | 
1 files changed, 1793 insertions, 130 deletions
diff --git a/net/core/devlink.c b/net/core/devlink.c index abb0da9d7b4b..78e22cea4cc7 100644 --- a/net/core/devlink.c +++ b/net/core/devlink.c @@ -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); @@ -115,6 +116,8 @@ static struct devlink *devlink_get_from_attrs(struct net *net,  	busname = nla_data(attrs[DEVLINK_ATTR_BUS_NAME]);  	devname = nla_data(attrs[DEVLINK_ATTR_DEV_NAME]); +	lockdep_assert_held(&devlink_mutex); +  	list_for_each_entry(devlink, &devlink_list, list) {  		if (strcmp(devlink->dev->bus->name, busname) == 0 &&  		    strcmp(dev_name(devlink->dev), devname) == 0 && @@ -720,7 +723,7 @@ static int devlink_port_type_set(struct devlink *devlink,  {  	int err; -	if (devlink->ops && devlink->ops->port_type_set) { +	if (devlink->ops->port_type_set) {  		if (port_type == DEVLINK_PORT_TYPE_NOTSET)  			return -EINVAL;  		if (port_type == devlink_port->type) @@ -757,7 +760,7 @@ static int devlink_port_split(struct devlink *devlink, u32 port_index,  			      u32 count, struct netlink_ext_ack *extack)  { -	if (devlink->ops && devlink->ops->port_split) +	if (devlink->ops->port_split)  		return devlink->ops->port_split(devlink, port_index, count,  						extack);  	return -EOPNOTSUPP; @@ -783,7 +786,7 @@ static int devlink_port_unsplit(struct devlink *devlink, u32 port_index,  				struct netlink_ext_ack *extack)  { -	if (devlink->ops && devlink->ops->port_unsplit) +	if (devlink->ops->port_unsplit)  		return devlink->ops->port_unsplit(devlink, port_index, extack);  	return -EOPNOTSUPP;  } @@ -932,6 +935,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; @@ -955,7 +961,7 @@ static int devlink_nl_cmd_sb_pool_get_doit(struct sk_buff *skb,  	if (err)  		return err; -	if (!devlink->ops || !devlink->ops->sb_pool_get) +	if (!devlink->ops->sb_pool_get)  		return -EOPNOTSUPP;  	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -1011,7 +1017,7 @@ static int devlink_nl_cmd_sb_pool_get_dumpit(struct sk_buff *msg,  	mutex_lock(&devlink_mutex);  	list_for_each_entry(devlink, &devlink_list, list) {  		if (!net_eq(devlink_net(devlink), sock_net(msg->sk)) || -		    !devlink->ops || !devlink->ops->sb_pool_get) +		    !devlink->ops->sb_pool_get)  			continue;  		mutex_lock(&devlink->lock);  		list_for_each_entry(devlink_sb, &devlink->sb_list, list) { @@ -1040,7 +1046,7 @@ static int devlink_sb_pool_set(struct devlink *devlink, unsigned int sb_index,  {  	const struct devlink_ops *ops = devlink->ops; -	if (ops && ops->sb_pool_set) +	if (ops->sb_pool_set)  		return ops->sb_pool_set(devlink, sb_index, pool_index,  					size, threshold_type);  	return -EOPNOTSUPP; @@ -1145,7 +1151,7 @@ static int devlink_nl_cmd_sb_port_pool_get_doit(struct sk_buff *skb,  	if (err)  		return err; -	if (!devlink->ops || !devlink->ops->sb_port_pool_get) +	if (!devlink->ops->sb_port_pool_get)  		return -EOPNOTSUPP;  	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -1207,7 +1213,7 @@ static int devlink_nl_cmd_sb_port_pool_get_dumpit(struct sk_buff *msg,  	mutex_lock(&devlink_mutex);  	list_for_each_entry(devlink, &devlink_list, list) {  		if (!net_eq(devlink_net(devlink), sock_net(msg->sk)) || -		    !devlink->ops || !devlink->ops->sb_port_pool_get) +		    !devlink->ops->sb_port_pool_get)  			continue;  		mutex_lock(&devlink->lock);  		list_for_each_entry(devlink_sb, &devlink->sb_list, list) { @@ -1236,7 +1242,7 @@ static int devlink_sb_port_pool_set(struct devlink_port *devlink_port,  {  	const struct devlink_ops *ops = devlink_port->devlink->ops; -	if (ops && ops->sb_port_pool_set) +	if (ops->sb_port_pool_set)  		return ops->sb_port_pool_set(devlink_port, sb_index,  					     pool_index, threshold);  	return -EOPNOTSUPP; @@ -1349,7 +1355,7 @@ static int devlink_nl_cmd_sb_tc_pool_bind_get_doit(struct sk_buff *skb,  	if (err)  		return err; -	if (!devlink->ops || !devlink->ops->sb_tc_pool_bind_get) +	if (!devlink->ops->sb_tc_pool_bind_get)  		return -EOPNOTSUPP;  	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL); @@ -1433,7 +1439,7 @@ devlink_nl_cmd_sb_tc_pool_bind_get_dumpit(struct sk_buff *msg,  	mutex_lock(&devlink_mutex);  	list_for_each_entry(devlink, &devlink_list, list) {  		if (!net_eq(devlink_net(devlink), sock_net(msg->sk)) || -		    !devlink->ops || !devlink->ops->sb_tc_pool_bind_get) +		    !devlink->ops->sb_tc_pool_bind_get)  			continue;  		mutex_lock(&devlink->lock); @@ -1465,7 +1471,7 @@ static int devlink_sb_tc_pool_bind_set(struct devlink_port *devlink_port,  {  	const struct devlink_ops *ops = devlink_port->devlink->ops; -	if (ops && ops->sb_tc_pool_bind_set) +	if (ops->sb_tc_pool_bind_set)  		return ops->sb_tc_pool_bind_set(devlink_port, sb_index,  						tc_index, pool_type,  						pool_index, threshold); @@ -1513,7 +1519,7 @@ static int devlink_nl_cmd_sb_occ_snapshot_doit(struct sk_buff *skb,  	struct devlink_sb *devlink_sb = info->user_ptr[1];  	const struct devlink_ops *ops = devlink->ops; -	if (ops && ops->sb_occ_snapshot) +	if (ops->sb_occ_snapshot)  		return ops->sb_occ_snapshot(devlink, devlink_sb->index);  	return -EOPNOTSUPP;  } @@ -1525,7 +1531,7 @@ static int devlink_nl_cmd_sb_occ_max_clear_doit(struct sk_buff *skb,  	struct devlink_sb *devlink_sb = info->user_ptr[1];  	const struct devlink_ops *ops = devlink->ops; -	if (ops && ops->sb_occ_max_clear) +	if (ops->sb_occ_max_clear)  		return ops->sb_occ_max_clear(devlink, devlink_sb->index);  	return -EOPNOTSUPP;  } @@ -1588,13 +1594,9 @@ static int devlink_nl_cmd_eswitch_get_doit(struct sk_buff *skb,  					   struct genl_info *info)  {  	struct devlink *devlink = info->user_ptr[0]; -	const struct devlink_ops *ops = devlink->ops;  	struct sk_buff *msg;  	int err; -	if (!ops) -		return -EOPNOTSUPP; -  	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);  	if (!msg)  		return -ENOMEM; @@ -1619,9 +1621,6 @@ static int devlink_nl_cmd_eswitch_set_doit(struct sk_buff *skb,  	int err = 0;  	u16 mode; -	if (!ops) -		return -EOPNOTSUPP; -  	if (info->attrs[DEVLINK_ATTR_ESWITCH_MODE]) {  		if (!ops->eswitch_mode_set)  			return -EOPNOTSUPP; @@ -2656,6 +2655,27 @@ static int devlink_nl_cmd_reload(struct sk_buff *skb, struct genl_info *info)  	return devlink->ops->reload(devlink, info->extack);  } +static int devlink_nl_cmd_flash_update(struct sk_buff *skb, +				       struct genl_info *info) +{ +	struct devlink *devlink = info->user_ptr[0]; +	const char *file_name, *component; +	struct nlattr *nla_component; + +	if (!devlink->ops->flash_update) +		return -EOPNOTSUPP; + +	if (!info->attrs[DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME]) +		return -EINVAL; +	file_name = nla_data(info->attrs[DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME]); + +	nla_component = info->attrs[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT]; +	component = nla_component ? nla_data(nla_component) : NULL; + +	return devlink->ops->flash_update(devlink, file_name, component, +					  info->extack); +} +  static const struct devlink_param devlink_param_generic[] = {  	{  		.id = DEVLINK_PARAM_GENERIC_ID_INT_ERR_RESET, @@ -2843,11 +2863,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 +2888,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 +2905,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 +2931,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 +2954,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 +2998,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 +3087,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 +3096,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 +3107,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 +3115,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 +3126,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 +3140,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 +3180,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 +3214,111 @@ static int devlink_param_register_one(struct devlink *devlink,  		return -ENOMEM;  	param_item->param = param; -	list_add_tail(¶m_item->list, &devlink->param_list); -	devlink_param_notify(devlink, param_item, DEVLINK_CMD_PARAM_NEW); +	list_add_tail(¶m_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(¶m_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) @@ -3504,44 +3640,56 @@ static int devlink_nl_cmd_region_read_dumpit(struct sk_buff *skb,  					     struct netlink_callback *cb)  {  	u64 ret_offset, start_offset, end_offset = 0; -	struct nlattr *attrs[DEVLINK_ATTR_MAX + 1];  	const struct genl_ops *ops = cb->data;  	struct devlink_region *region;  	struct nlattr *chunks_attr;  	const char *region_name;  	struct devlink *devlink; +	struct nlattr **attrs;  	bool dump = true;  	void *hdr;  	int err;  	start_offset = *((u64 *)&cb->args[0]); +	attrs = kmalloc_array(DEVLINK_ATTR_MAX + 1, sizeof(*attrs), GFP_KERNEL); +	if (!attrs) +		return -ENOMEM; +  	err = nlmsg_parse(cb->nlh, GENL_HDRLEN + devlink_nl_family.hdrsize,  			  attrs, DEVLINK_ATTR_MAX, ops->policy, cb->extack);  	if (err) -		goto out; +		goto out_free; +	mutex_lock(&devlink_mutex);  	devlink = devlink_get_from_attrs(sock_net(cb->skb->sk), attrs); -	if (IS_ERR(devlink)) -		goto out; +	if (IS_ERR(devlink)) { +		err = PTR_ERR(devlink); +		goto out_dev; +	} -	mutex_lock(&devlink_mutex);  	mutex_lock(&devlink->lock);  	if (!attrs[DEVLINK_ATTR_REGION_NAME] || -	    !attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]) +	    !attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]) { +		err = -EINVAL;  		goto out_unlock; +	}  	region_name = nla_data(attrs[DEVLINK_ATTR_REGION_NAME]);  	region = devlink_region_get_by_name(devlink, region_name); -	if (!region) +	if (!region) { +		err = -EINVAL;  		goto out_unlock; +	}  	hdr = genlmsg_put(skb, NETLINK_CB(cb->skb).portid, cb->nlh->nlmsg_seq,  			  &devlink_nl_family, NLM_F_ACK | NLM_F_MULTI,  			  DEVLINK_CMD_REGION_READ); -	if (!hdr) +	if (!hdr) { +		err = -EMSGSIZE;  		goto out_unlock; +	}  	err = devlink_nl_put_handle(skb, devlink);  	if (err) @@ -3552,8 +3700,10 @@ static int devlink_nl_cmd_region_read_dumpit(struct sk_buff *skb,  		goto nla_put_failure;  	chunks_attr = nla_nest_start(skb, DEVLINK_ATTR_REGION_CHUNKS); -	if (!chunks_attr) +	if (!chunks_attr) { +		err = -EMSGSIZE;  		goto nla_put_failure; +	}  	if (attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR] &&  	    attrs[DEVLINK_ATTR_REGION_CHUNK_LEN]) { @@ -3576,8 +3726,10 @@ static int devlink_nl_cmd_region_read_dumpit(struct sk_buff *skb,  		goto nla_put_failure;  	/* Check if there was any progress done to prevent infinite loop */ -	if (ret_offset == start_offset) +	if (ret_offset == start_offset) { +		err = -EINVAL;  		goto nla_put_failure; +	}  	*((u64 *)&cb->args[0]) = ret_offset; @@ -3585,6 +3737,7 @@ static int devlink_nl_cmd_region_read_dumpit(struct sk_buff *skb,  	genlmsg_end(skb, hdr);  	mutex_unlock(&devlink->lock);  	mutex_unlock(&devlink_mutex); +	kfree(attrs);  	return skb->len; @@ -3592,8 +3745,1144 @@ nla_put_failure:  	genlmsg_cancel(skb, hdr);  out_unlock:  	mutex_unlock(&devlink->lock); +out_dev: +	mutex_unlock(&devlink_mutex); +out_free: +	kfree(attrs); +	return err; +} + +struct devlink_info_req { +	struct sk_buff *msg; +}; + +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); + +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 int devlink_info_version_put(struct devlink_info_req *req, int attr, +				    const char *version_name, +				    const char *version_value) +{ +	struct nlattr *nest; +	int err; + +	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; + +	err = nla_put_string(req->msg, DEVLINK_ATTR_INFO_VERSION_VALUE, +			     version_value); +	if (err) +		goto nla_put_failure; + +	nla_nest_end(req->msg, nest); + +	return 0; + +nla_put_failure: +	nla_nest_cancel(req->msg, nest); +	return err; +} + +int devlink_info_version_fixed_put(struct devlink_info_req *req, +				   const char *version_name, +				   const char *version_value) +{ +	return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_FIXED, +					version_name, version_value); +} +EXPORT_SYMBOL_GPL(devlink_info_version_fixed_put); + +int devlink_info_version_stored_put(struct devlink_info_req *req, +				    const char *version_name, +				    const char *version_value) +{ +	return devlink_info_version_put(req, DEVLINK_ATTR_INFO_VERSION_STORED, +					version_name, version_value); +} +EXPORT_SYMBOL_GPL(devlink_info_version_stored_put); + +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); + +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) +{ +	struct devlink_info_req req; +	void *hdr; +	int err; + +	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; + +err_cancel_msg: +	genlmsg_cancel(msg, hdr); +	return err; +} + +static int devlink_nl_cmd_info_get_doit(struct sk_buff *skb, +					struct genl_info *info) +{ +	struct devlink *devlink = info->user_ptr[0]; +	struct sk_buff *msg; +	int err; + +	if (!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; +	} + +	return genlmsg_reply(msg, info); +} + +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; + +	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; +		} + +		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); + +	cb->args[0] = idx; +	return msg->len; +} + +struct devlink_fmsg_item { +	struct list_head list; +	int attrtype; +	u8 nla_type; +	u16 len; +	int value[0]; +}; + +struct devlink_fmsg { +	struct list_head item_list; +}; + +static struct devlink_fmsg *devlink_fmsg_alloc(void) +{ +	struct devlink_fmsg *fmsg; + +	fmsg = kzalloc(sizeof(*fmsg), GFP_KERNEL); +	if (!fmsg) +		return NULL; + +	INIT_LIST_HEAD(&fmsg->item_list); + +	return fmsg; +} + +static void devlink_fmsg_free(struct devlink_fmsg *fmsg) +{ +	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); +} + +static int devlink_fmsg_nest_common(struct devlink_fmsg *fmsg, +				    int attrtype) +{ +	struct devlink_fmsg_item *item; + +	item = kzalloc(sizeof(*item), GFP_KERNEL); +	if (!item) +		return -ENOMEM; + +	item->attrtype = attrtype; +	list_add_tail(&item->list, &fmsg->item_list); + +	return 0; +} + +int devlink_fmsg_obj_nest_start(struct devlink_fmsg *fmsg) +{ +	return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_OBJ_NEST_START); +} +EXPORT_SYMBOL_GPL(devlink_fmsg_obj_nest_start); + +static int devlink_fmsg_nest_end(struct devlink_fmsg *fmsg) +{ +	return devlink_fmsg_nest_common(fmsg, DEVLINK_ATTR_FMSG_NEST_END); +} + +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) + +static int devlink_fmsg_put_name(struct devlink_fmsg *fmsg, const char *name) +{ +	struct devlink_fmsg_item *item; + +	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; +} + +int devlink_fmsg_pair_nest_start(struct devlink_fmsg *fmsg, const char *name) +{ +	int err; + +	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_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_fmsg_arr_pair_nest_start(struct devlink_fmsg *fmsg, +				     const char *name) +{ +	int err; + +	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_fmsg_arr_pair_nest_start); + +int devlink_fmsg_arr_pair_nest_end(struct devlink_fmsg *fmsg) +{ +	int err; + +	err = devlink_fmsg_nest_end(fmsg); +	if (err) +		return err; + +	err = devlink_fmsg_nest_end(fmsg); +	if (err) +		return err; + +	return 0; +} +EXPORT_SYMBOL_GPL(devlink_fmsg_arr_pair_nest_end); + +static int devlink_fmsg_put_value(struct devlink_fmsg *fmsg, +				  const void *value, u16 value_len, +				  u8 value_nla_type) +{ +	struct devlink_fmsg_item *item; + +	if (value_len > DEVLINK_FMSG_MAX_SIZE) +		return -EMSGSIZE; + +	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; +} + +int devlink_fmsg_bool_put(struct devlink_fmsg *fmsg, bool value) +{ +	return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_FLAG); +} +EXPORT_SYMBOL_GPL(devlink_fmsg_bool_put); + +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); + +int devlink_fmsg_u32_put(struct devlink_fmsg *fmsg, u32 value) +{ +	return devlink_fmsg_put_value(fmsg, &value, sizeof(value), NLA_U32); +} +EXPORT_SYMBOL_GPL(devlink_fmsg_u32_put); + +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); + +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); + +int devlink_fmsg_binary_put(struct devlink_fmsg *fmsg, const void *value, +			    u16 value_len) +{ +	return devlink_fmsg_put_value(fmsg, value, value_len, NLA_BINARY); +} +EXPORT_SYMBOL_GPL(devlink_fmsg_binary_put); + +int devlink_fmsg_bool_pair_put(struct devlink_fmsg *fmsg, const char *name, +			       bool value) +{ +	int err; + +	err = devlink_fmsg_pair_nest_start(fmsg, name); +	if (err) +		return err; + +	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_FMSG_OBJ_VALUE_DATA: +			err = devlink_fmsg_item_fill_type(item, skb); +			if (err) +				break; +			err = devlink_fmsg_item_fill_data(item, skb); +			break; +		case DEVLINK_ATTR_FMSG_OBJ_NAME: +			err = nla_put_string(skb, item->attrtype, +					     (char *) &item->value); +			break; +		default: +			err = -EINVAL; +			break; +		} +		if (!err) +			*start = ++i; +		else +			break; +	} + +	nla_nest_end(skb, fmsg_nlattr); +	return err; +} + +static int devlink_fmsg_snd(struct devlink_fmsg *fmsg, +			    struct genl_info *info, +			    enum devlink_command cmd, int flags) +{ +	struct nlmsghdr *nlh; +	struct sk_buff *skb; +	bool last = false; +	int index = 0; +	void *hdr; +	int err; + +	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, flags | NLM_F_MULTI, cmd); +		if (!hdr) { +			err = -EMSGSIZE; +			goto nla_put_failure; +		} + +		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); +		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); +	if (!nlh) { +		err = -EMSGSIZE; +		goto nla_put_failure; +	} + +	return genlmsg_reply(skb, info); + +nla_put_failure: +	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; +}; + +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); + +void +devlink_health_reporter_state_update(struct devlink_health_reporter *reporter, +				     enum devlink_health_reporter_state state) +{ +	if (WARN_ON(state != DEVLINK_HEALTH_REPORTER_STATE_HEALTHY && +		    state != DEVLINK_HEALTH_REPORTER_STATE_ERROR)) +		return; + +	if (reporter->health_state == state) +		return; + +	reporter->health_state = state; +	trace_devlink_health_reporter_state_update(reporter->devlink, +						   reporter->ops->name, state); +} +EXPORT_SYMBOL_GPL(devlink_health_reporter_state_update); + +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) +{ +	enum devlink_health_reporter_state prev_health_state; +	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++; +	prev_health_state = reporter->health_state; +	reporter->health_state = DEVLINK_HEALTH_REPORTER_STATE_ERROR; + +	/* abort if the previous error wasn't recovered */ +	if (reporter->auto_recover && +	    (prev_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_COUNT, +			      reporter->error_count, DEVLINK_ATTR_PAD)) +		goto reporter_nest_cancel; +	if (nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_RECOVER_COUNT, +			      reporter->recovery_count, DEVLINK_ATTR_PAD)) +		goto reporter_nest_cancel; +	if (reporter->ops->recover && +	    nla_put_u64_64bit(msg, DEVLINK_ATTR_HEALTH_REPORTER_GRACEFUL_PERIOD, +			      reporter->graceful_period, +			      DEVLINK_ATTR_PAD)) +		goto reporter_nest_cancel; +	if (reporter->ops->recover && +	    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;  } @@ -3622,6 +4911,11 @@ 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 }, +	[DEVLINK_ATTR_FLASH_UPDATE_FILE_NAME] = { .type = NLA_NUL_STRING }, +	[DEVLINK_ATTR_FLASH_UPDATE_COMPONENT] = { .type = NLA_NUL_STRING },  };  static const struct genl_ops devlink_nl_ops[] = { @@ -3821,6 +5115,21 @@ static const struct genl_ops devlink_nl_ops[] = {  		.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, @@ -3842,6 +5151,66 @@ static const struct genl_ops devlink_nl_ops[] = {  		.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_HEALTH_REPORTER_GET, +		.doit = devlink_nl_cmd_health_reporter_get_doit, +		.dumpit = devlink_nl_cmd_health_reporter_get_dumpit, +		.policy = devlink_nl_policy, +		.internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK, +		/* can be retrieved by unprivileged users */ +	}, +	{ +		.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, +	}, +	{ +		.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_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_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 | +				  DEVLINK_NL_FLAG_NO_LOCK, +	}, +	{ +		.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 | +				  DEVLINK_NL_FLAG_NO_LOCK, +	}, +	{ +		.cmd = DEVLINK_CMD_FLASH_UPDATE, +		.doit = devlink_nl_cmd_flash_update, +		.policy = devlink_nl_policy, +		.flags = GENL_ADMIN_PERM, +		.internal_flags = DEVLINK_NL_FLAG_NEED_DEVLINK, +	},  };  static struct genl_family devlink_nl_family __ro_after_init = { @@ -3871,6 +5240,9 @@ struct devlink *devlink_alloc(const struct devlink_ops *ops, size_t priv_size)  {  	struct devlink *devlink; +	if (WARN_ON(!ops)) +		return NULL; +  	devlink = kzalloc(sizeof(*devlink) + priv_size, GFP_KERNEL);  	if (!devlink)  		return NULL; @@ -3882,6 +5254,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;  } @@ -3891,6 +5264,7 @@ EXPORT_SYMBOL_GPL(devlink_alloc);   *	devlink_register - Register devlink instance   *   *	@devlink: devlink + *	@dev: parent device   */  int devlink_register(struct devlink *devlink, struct device *dev)  { @@ -3924,6 +5298,14 @@ EXPORT_SYMBOL_GPL(devlink_unregister);   */  void devlink_free(struct devlink *devlink)  { +	WARN_ON(!list_empty(&devlink->reporter_list)); +	WARN_ON(!list_empty(&devlink->region_list)); +	WARN_ON(!list_empty(&devlink->param_list)); +	WARN_ON(!list_empty(&devlink->resource_list)); +	WARN_ON(!list_empty(&devlink->dpipe_table_list)); +	WARN_ON(!list_empty(&devlink->sb_list)); +	WARN_ON(!list_empty(&devlink->port_list)); +  	kfree(devlink);  }  EXPORT_SYMBOL_GPL(devlink_free); @@ -3933,7 +5315,7 @@ EXPORT_SYMBOL_GPL(devlink_free);   *   *	@devlink: devlink   *	@devlink_port: devlink port - *	@port_index + *	@port_index: driver-specific numerical identifier of the port   *   *	Register devlink port with provided port index. User can use   *	any indexing, even hw-related one. devlink_port structure @@ -3954,6 +5336,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; @@ -4262,13 +5645,10 @@ EXPORT_SYMBOL_GPL(devlink_dpipe_table_unregister);   *   *	@devlink: devlink   *	@resource_name: resource's name - *	@top_hierarchy: top hierarchy - *	@reload_required: reload is required for new configuration to - *			  apply   *	@resource_size: resource's size   *	@resource_id: resource's id - *	@parent_reosurce_id: resource's parent id - *	@size params: size parameters + *	@parent_resource_id: resource's parent id + *	@size_params: size parameters   */  int devlink_resource_register(struct devlink *devlink,  			      const char *resource_name, @@ -4471,18 +5851,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; @@ -4490,20 +5875,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;  	} @@ -4515,11 +5892,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);  /** @@ -4532,36 +5946,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; @@ -4577,6 +6058,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->reload) +		return -EOPNOTSUPP; + +	return __devlink_param_driverinit_value_get(&devlink->param_list, +						    param_id, init_val); +}  EXPORT_SYMBOL_GPL(devlink_param_driverinit_value_get);  /** @@ -4594,26 +6123,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->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 @@ -4626,7 +6190,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)  { @@ -4635,11 +6198,38 @@ 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   * @@ -4755,7 +6345,7 @@ EXPORT_SYMBOL_GPL(devlink_region_shapshot_id_get);   *	Multiple snapshots can be created on a region.   *	The @snapshot_id should be obtained using the getter function.   * - *	@devlink_region: devlink region of the snapshot + *	@region: devlink region of the snapshot   *	@data_len: size of snapshot data   *	@data: snapshot data   *	@snapshot_id: snapshot id to be created @@ -4808,20 +6398,93 @@ unlock:  }  EXPORT_SYMBOL_GPL(devlink_region_snapshot_create); -static int __init devlink_module_init(void) +static void __devlink_compat_running_version(struct devlink *devlink, +					     char *buf, size_t len)  { -	return genl_register_family(&devlink_nl_family); +	const struct nlattr *nlattr; +	struct devlink_info_req req; +	struct sk_buff *msg; +	int rem, err; + +	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 *devlink; + +	dev_hold(dev); +	rtnl_unlock(); + +	mutex_lock(&devlink_mutex); +	devlink = netdev_to_devlink(dev); +	if (!devlink || !devlink->ops->info_get) +		goto unlock_list; + +	mutex_lock(&devlink->lock); +	__devlink_compat_running_version(devlink, buf, len); +	mutex_unlock(&devlink->lock); +unlock_list: +	mutex_unlock(&devlink_mutex); + +	rtnl_lock(); +	dev_put(dev);  } -static void __exit devlink_module_exit(void) +int devlink_compat_flash_update(struct net_device *dev, const char *file_name)  { -	genl_unregister_family(&devlink_nl_family); +	struct devlink *devlink; +	int ret = -EOPNOTSUPP; + +	dev_hold(dev); +	rtnl_unlock(); + +	mutex_lock(&devlink_mutex); +	devlink = netdev_to_devlink(dev); +	if (!devlink || !devlink->ops->flash_update) +		goto unlock_list; + +	mutex_lock(&devlink->lock); +	ret = devlink->ops->flash_update(devlink, file_name, NULL, NULL); +	mutex_unlock(&devlink->lock); +unlock_list: +	mutex_unlock(&devlink_mutex); + +	rtnl_lock(); +	dev_put(dev); + +	return ret;  } -module_init(devlink_module_init); -module_exit(devlink_module_exit); +static int __init devlink_init(void) +{ +	return genl_register_family(&devlink_nl_family); +} -MODULE_LICENSE("GPL v2"); -MODULE_AUTHOR("Jiri Pirko <[email protected]>"); -MODULE_DESCRIPTION("Network physical device Netlink interface"); -MODULE_ALIAS_GENL_FAMILY(DEVLINK_GENL_NAME); +subsys_initcall(devlink_init);  |