mirror of
				https://github.com/torvalds/linux.git
				synced 2025-11-04 02:30:34 +02:00 
			
		
		
		
	Introduce a helper devlink_nl_notify_send() so each object notification function does not have to call genlmsg_multicast_netns() with the same arguments. Signed-off-by: Jiri Pirko <jiri@nvidia.com> Signed-off-by: Paolo Abeni <pabeni@redhat.com>
		
			
				
	
	
		
			1261 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			1261 lines
		
	
	
	
		
			31 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
// SPDX-License-Identifier: GPL-2.0-or-later
 | 
						|
/*
 | 
						|
 * Copyright (c) 2016 Mellanox Technologies. All rights reserved.
 | 
						|
 * Copyright (c) 2016 Jiri Pirko <jiri@mellanox.com>
 | 
						|
 */
 | 
						|
 | 
						|
#include "devl_internal.h"
 | 
						|
 | 
						|
struct devlink_region {
 | 
						|
	struct devlink *devlink;
 | 
						|
	struct devlink_port *port;
 | 
						|
	struct list_head list;
 | 
						|
	union {
 | 
						|
		const struct devlink_region_ops *ops;
 | 
						|
		const struct devlink_port_region_ops *port_ops;
 | 
						|
	};
 | 
						|
	struct mutex snapshot_lock; /* protects snapshot_list,
 | 
						|
				     * max_snapshots and cur_snapshots
 | 
						|
				     * consistency.
 | 
						|
				     */
 | 
						|
	struct list_head snapshot_list;
 | 
						|
	u32 max_snapshots;
 | 
						|
	u32 cur_snapshots;
 | 
						|
	u64 size;
 | 
						|
};
 | 
						|
 | 
						|
struct devlink_snapshot {
 | 
						|
	struct list_head list;
 | 
						|
	struct devlink_region *region;
 | 
						|
	u8 *data;
 | 
						|
	u32 id;
 | 
						|
};
 | 
						|
 | 
						|
static struct devlink_region *
 | 
						|
devlink_region_get_by_name(struct devlink *devlink, const char *region_name)
 | 
						|
{
 | 
						|
	struct devlink_region *region;
 | 
						|
 | 
						|
	list_for_each_entry(region, &devlink->region_list, list)
 | 
						|
		if (!strcmp(region->ops->name, region_name))
 | 
						|
			return region;
 | 
						|
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static struct devlink_region *
 | 
						|
devlink_port_region_get_by_name(struct devlink_port *port,
 | 
						|
				const char *region_name)
 | 
						|
{
 | 
						|
	struct devlink_region *region;
 | 
						|
 | 
						|
	list_for_each_entry(region, &port->region_list, list)
 | 
						|
		if (!strcmp(region->ops->name, region_name))
 | 
						|
			return region;
 | 
						|
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static struct devlink_snapshot *
 | 
						|
devlink_region_snapshot_get_by_id(struct devlink_region *region, u32 id)
 | 
						|
{
 | 
						|
	struct devlink_snapshot *snapshot;
 | 
						|
 | 
						|
	list_for_each_entry(snapshot, ®ion->snapshot_list, list)
 | 
						|
		if (snapshot->id == id)
 | 
						|
			return snapshot;
 | 
						|
 | 
						|
	return NULL;
 | 
						|
}
 | 
						|
 | 
						|
static int devlink_nl_region_snapshot_id_put(struct sk_buff *msg,
 | 
						|
					     struct devlink *devlink,
 | 
						|
					     struct devlink_snapshot *snapshot)
 | 
						|
{
 | 
						|
	struct nlattr *snap_attr;
 | 
						|
	int err;
 | 
						|
 | 
						|
	snap_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_REGION_SNAPSHOT);
 | 
						|
	if (!snap_attr)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	err = nla_put_u32(msg, DEVLINK_ATTR_REGION_SNAPSHOT_ID, snapshot->id);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	nla_nest_end(msg, snap_attr);
 | 
						|
	return 0;
 | 
						|
 | 
						|
nla_put_failure:
 | 
						|
	nla_nest_cancel(msg, snap_attr);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static int devlink_nl_region_snapshots_id_put(struct sk_buff *msg,
 | 
						|
					      struct devlink *devlink,
 | 
						|
					      struct devlink_region *region)
 | 
						|
{
 | 
						|
	struct devlink_snapshot *snapshot;
 | 
						|
	struct nlattr *snapshots_attr;
 | 
						|
	int err;
 | 
						|
 | 
						|
	snapshots_attr = nla_nest_start_noflag(msg,
 | 
						|
					       DEVLINK_ATTR_REGION_SNAPSHOTS);
 | 
						|
	if (!snapshots_attr)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	list_for_each_entry(snapshot, ®ion->snapshot_list, list) {
 | 
						|
		err = devlink_nl_region_snapshot_id_put(msg, devlink, snapshot);
 | 
						|
		if (err)
 | 
						|
			goto nla_put_failure;
 | 
						|
	}
 | 
						|
 | 
						|
	nla_nest_end(msg, snapshots_attr);
 | 
						|
	return 0;
 | 
						|
 | 
						|
nla_put_failure:
 | 
						|
	nla_nest_cancel(msg, snapshots_attr);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static int devlink_nl_region_fill(struct sk_buff *msg, struct devlink *devlink,
 | 
						|
				  enum devlink_command cmd, u32 portid,
 | 
						|
				  u32 seq, int flags,
 | 
						|
				  struct devlink_region *region)
 | 
						|
{
 | 
						|
	void *hdr;
 | 
						|
	int err;
 | 
						|
 | 
						|
	hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, flags, cmd);
 | 
						|
	if (!hdr)
 | 
						|
		return -EMSGSIZE;
 | 
						|
 | 
						|
	err = devlink_nl_put_handle(msg, devlink);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	if (region->port) {
 | 
						|
		err = nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX,
 | 
						|
				  region->port->index);
 | 
						|
		if (err)
 | 
						|
			goto nla_put_failure;
 | 
						|
	}
 | 
						|
 | 
						|
	err = nla_put_string(msg, DEVLINK_ATTR_REGION_NAME, region->ops->name);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_SIZE,
 | 
						|
				region->size,
 | 
						|
				DEVLINK_ATTR_PAD);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	err = nla_put_u32(msg, DEVLINK_ATTR_REGION_MAX_SNAPSHOTS,
 | 
						|
			  region->max_snapshots);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	err = devlink_nl_region_snapshots_id_put(msg, devlink, region);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	genlmsg_end(msg, hdr);
 | 
						|
	return 0;
 | 
						|
 | 
						|
nla_put_failure:
 | 
						|
	genlmsg_cancel(msg, hdr);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static struct sk_buff *
 | 
						|
devlink_nl_region_notify_build(struct devlink_region *region,
 | 
						|
			       struct devlink_snapshot *snapshot,
 | 
						|
			       enum devlink_command cmd, u32 portid, u32 seq)
 | 
						|
{
 | 
						|
	struct devlink *devlink = region->devlink;
 | 
						|
	struct sk_buff *msg;
 | 
						|
	void *hdr;
 | 
						|
	int err;
 | 
						|
 | 
						|
	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
 | 
						|
	if (!msg)
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
 | 
						|
	hdr = genlmsg_put(msg, portid, seq, &devlink_nl_family, 0, cmd);
 | 
						|
	if (!hdr) {
 | 
						|
		err = -EMSGSIZE;
 | 
						|
		goto out_free_msg;
 | 
						|
	}
 | 
						|
 | 
						|
	err = devlink_nl_put_handle(msg, devlink);
 | 
						|
	if (err)
 | 
						|
		goto out_cancel_msg;
 | 
						|
 | 
						|
	if (region->port) {
 | 
						|
		err = nla_put_u32(msg, DEVLINK_ATTR_PORT_INDEX,
 | 
						|
				  region->port->index);
 | 
						|
		if (err)
 | 
						|
			goto out_cancel_msg;
 | 
						|
	}
 | 
						|
 | 
						|
	err = nla_put_string(msg, DEVLINK_ATTR_REGION_NAME,
 | 
						|
			     region->ops->name);
 | 
						|
	if (err)
 | 
						|
		goto out_cancel_msg;
 | 
						|
 | 
						|
	if (snapshot) {
 | 
						|
		err = nla_put_u32(msg, DEVLINK_ATTR_REGION_SNAPSHOT_ID,
 | 
						|
				  snapshot->id);
 | 
						|
		if (err)
 | 
						|
			goto out_cancel_msg;
 | 
						|
	} else {
 | 
						|
		err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_SIZE,
 | 
						|
					region->size, DEVLINK_ATTR_PAD);
 | 
						|
		if (err)
 | 
						|
			goto out_cancel_msg;
 | 
						|
	}
 | 
						|
	genlmsg_end(msg, hdr);
 | 
						|
 | 
						|
	return msg;
 | 
						|
 | 
						|
out_cancel_msg:
 | 
						|
	genlmsg_cancel(msg, hdr);
 | 
						|
out_free_msg:
 | 
						|
	nlmsg_free(msg);
 | 
						|
	return ERR_PTR(err);
 | 
						|
}
 | 
						|
 | 
						|
static void devlink_nl_region_notify(struct devlink_region *region,
 | 
						|
				     struct devlink_snapshot *snapshot,
 | 
						|
				     enum devlink_command cmd)
 | 
						|
{
 | 
						|
	struct devlink *devlink = region->devlink;
 | 
						|
	struct sk_buff *msg;
 | 
						|
 | 
						|
	WARN_ON(cmd != DEVLINK_CMD_REGION_NEW && cmd != DEVLINK_CMD_REGION_DEL);
 | 
						|
 | 
						|
	if (!__devl_is_registered(devlink) || !devlink_nl_notify_need(devlink))
 | 
						|
		return;
 | 
						|
 | 
						|
	msg = devlink_nl_region_notify_build(region, snapshot, cmd, 0, 0);
 | 
						|
	if (IS_ERR(msg))
 | 
						|
		return;
 | 
						|
 | 
						|
	devlink_nl_notify_send(devlink, msg);
 | 
						|
}
 | 
						|
 | 
						|
void devlink_regions_notify_register(struct devlink *devlink)
 | 
						|
{
 | 
						|
	struct devlink_region *region;
 | 
						|
 | 
						|
	list_for_each_entry(region, &devlink->region_list, list)
 | 
						|
		devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW);
 | 
						|
}
 | 
						|
 | 
						|
void devlink_regions_notify_unregister(struct devlink *devlink)
 | 
						|
{
 | 
						|
	struct devlink_region *region;
 | 
						|
 | 
						|
	list_for_each_entry_reverse(region, &devlink->region_list, list)
 | 
						|
		devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_DEL);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * __devlink_snapshot_id_increment - Increment number of snapshots using an id
 | 
						|
 *	@devlink: devlink instance
 | 
						|
 *	@id: the snapshot id
 | 
						|
 *
 | 
						|
 *	Track when a new snapshot begins using an id. Load the count for the
 | 
						|
 *	given id from the snapshot xarray, increment it, and store it back.
 | 
						|
 *
 | 
						|
 *	Called when a new snapshot is created with the given id.
 | 
						|
 *
 | 
						|
 *	The id *must* have been previously allocated by
 | 
						|
 *	devlink_region_snapshot_id_get().
 | 
						|
 *
 | 
						|
 *	Returns 0 on success, or an error on failure.
 | 
						|
 */
 | 
						|
static int __devlink_snapshot_id_increment(struct devlink *devlink, u32 id)
 | 
						|
{
 | 
						|
	unsigned long count;
 | 
						|
	void *p;
 | 
						|
	int err;
 | 
						|
 | 
						|
	xa_lock(&devlink->snapshot_ids);
 | 
						|
	p = xa_load(&devlink->snapshot_ids, id);
 | 
						|
	if (WARN_ON(!p)) {
 | 
						|
		err = -EINVAL;
 | 
						|
		goto unlock;
 | 
						|
	}
 | 
						|
 | 
						|
	if (WARN_ON(!xa_is_value(p))) {
 | 
						|
		err = -EINVAL;
 | 
						|
		goto unlock;
 | 
						|
	}
 | 
						|
 | 
						|
	count = xa_to_value(p);
 | 
						|
	count++;
 | 
						|
 | 
						|
	err = xa_err(__xa_store(&devlink->snapshot_ids, id, xa_mk_value(count),
 | 
						|
				GFP_ATOMIC));
 | 
						|
unlock:
 | 
						|
	xa_unlock(&devlink->snapshot_ids);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * __devlink_snapshot_id_decrement - Decrease number of snapshots using an id
 | 
						|
 *	@devlink: devlink instance
 | 
						|
 *	@id: the snapshot id
 | 
						|
 *
 | 
						|
 *	Track when a snapshot is deleted and stops using an id. Load the count
 | 
						|
 *	for the given id from the snapshot xarray, decrement it, and store it
 | 
						|
 *	back.
 | 
						|
 *
 | 
						|
 *	If the count reaches zero, erase this id from the xarray, freeing it
 | 
						|
 *	up for future re-use by devlink_region_snapshot_id_get().
 | 
						|
 *
 | 
						|
 *	Called when a snapshot using the given id is deleted, and when the
 | 
						|
 *	initial allocator of the id is finished using it.
 | 
						|
 */
 | 
						|
static void __devlink_snapshot_id_decrement(struct devlink *devlink, u32 id)
 | 
						|
{
 | 
						|
	unsigned long count;
 | 
						|
	void *p;
 | 
						|
 | 
						|
	xa_lock(&devlink->snapshot_ids);
 | 
						|
	p = xa_load(&devlink->snapshot_ids, id);
 | 
						|
	if (WARN_ON(!p))
 | 
						|
		goto unlock;
 | 
						|
 | 
						|
	if (WARN_ON(!xa_is_value(p)))
 | 
						|
		goto unlock;
 | 
						|
 | 
						|
	count = xa_to_value(p);
 | 
						|
 | 
						|
	if (count > 1) {
 | 
						|
		count--;
 | 
						|
		__xa_store(&devlink->snapshot_ids, id, xa_mk_value(count),
 | 
						|
			   GFP_ATOMIC);
 | 
						|
	} else {
 | 
						|
		/* If this was the last user, we can erase this id */
 | 
						|
		__xa_erase(&devlink->snapshot_ids, id);
 | 
						|
	}
 | 
						|
unlock:
 | 
						|
	xa_unlock(&devlink->snapshot_ids);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 *	__devlink_snapshot_id_insert - Insert a specific snapshot ID
 | 
						|
 *	@devlink: devlink instance
 | 
						|
 *	@id: the snapshot id
 | 
						|
 *
 | 
						|
 *	Mark the given snapshot id as used by inserting a zero value into the
 | 
						|
 *	snapshot xarray.
 | 
						|
 *
 | 
						|
 *	This must be called while holding the devlink instance lock. Unlike
 | 
						|
 *	devlink_snapshot_id_get, the initial reference count is zero, not one.
 | 
						|
 *	It is expected that the id will immediately be used before
 | 
						|
 *	releasing the devlink instance lock.
 | 
						|
 *
 | 
						|
 *	Returns zero on success, or an error code if the snapshot id could not
 | 
						|
 *	be inserted.
 | 
						|
 */
 | 
						|
static int __devlink_snapshot_id_insert(struct devlink *devlink, u32 id)
 | 
						|
{
 | 
						|
	int err;
 | 
						|
 | 
						|
	xa_lock(&devlink->snapshot_ids);
 | 
						|
	if (xa_load(&devlink->snapshot_ids, id)) {
 | 
						|
		xa_unlock(&devlink->snapshot_ids);
 | 
						|
		return -EEXIST;
 | 
						|
	}
 | 
						|
	err = xa_err(__xa_store(&devlink->snapshot_ids, id, xa_mk_value(0),
 | 
						|
				GFP_ATOMIC));
 | 
						|
	xa_unlock(&devlink->snapshot_ids);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 *	__devlink_region_snapshot_id_get - get snapshot ID
 | 
						|
 *	@devlink: devlink instance
 | 
						|
 *	@id: storage to return snapshot id
 | 
						|
 *
 | 
						|
 *	Allocates a new snapshot id. Returns zero on success, or a negative
 | 
						|
 *	error on failure. Must be called while holding the devlink instance
 | 
						|
 *	lock.
 | 
						|
 *
 | 
						|
 *	Snapshot IDs are tracked using an xarray which stores the number of
 | 
						|
 *	users of the snapshot id.
 | 
						|
 *
 | 
						|
 *	Note that the caller of this function counts as a 'user', in order to
 | 
						|
 *	avoid race conditions. The caller must release its hold on the
 | 
						|
 *	snapshot by using devlink_region_snapshot_id_put.
 | 
						|
 */
 | 
						|
static int __devlink_region_snapshot_id_get(struct devlink *devlink, u32 *id)
 | 
						|
{
 | 
						|
	return xa_alloc(&devlink->snapshot_ids, id, xa_mk_value(1),
 | 
						|
			xa_limit_32b, GFP_KERNEL);
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 *	__devlink_region_snapshot_create - create a new snapshot
 | 
						|
 *	This will add a new snapshot of a region. The snapshot
 | 
						|
 *	will be stored on the region struct and can be accessed
 | 
						|
 *	from devlink. This is useful for future analyses of snapshots.
 | 
						|
 *	Multiple snapshots can be created on a region.
 | 
						|
 *	The @snapshot_id should be obtained using the getter function.
 | 
						|
 *
 | 
						|
 *	Must be called only while holding the region snapshot lock.
 | 
						|
 *
 | 
						|
 *	@region: devlink region of the snapshot
 | 
						|
 *	@data: snapshot data
 | 
						|
 *	@snapshot_id: snapshot id to be created
 | 
						|
 */
 | 
						|
static int
 | 
						|
__devlink_region_snapshot_create(struct devlink_region *region,
 | 
						|
				 u8 *data, u32 snapshot_id)
 | 
						|
{
 | 
						|
	struct devlink *devlink = region->devlink;
 | 
						|
	struct devlink_snapshot *snapshot;
 | 
						|
	int err;
 | 
						|
 | 
						|
	lockdep_assert_held(®ion->snapshot_lock);
 | 
						|
 | 
						|
	/* check if region can hold one more snapshot */
 | 
						|
	if (region->cur_snapshots == region->max_snapshots)
 | 
						|
		return -ENOSPC;
 | 
						|
 | 
						|
	if (devlink_region_snapshot_get_by_id(region, snapshot_id))
 | 
						|
		return -EEXIST;
 | 
						|
 | 
						|
	snapshot = kzalloc(sizeof(*snapshot), GFP_KERNEL);
 | 
						|
	if (!snapshot)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	err = __devlink_snapshot_id_increment(devlink, snapshot_id);
 | 
						|
	if (err)
 | 
						|
		goto err_snapshot_id_increment;
 | 
						|
 | 
						|
	snapshot->id = snapshot_id;
 | 
						|
	snapshot->region = region;
 | 
						|
	snapshot->data = data;
 | 
						|
 | 
						|
	list_add_tail(&snapshot->list, ®ion->snapshot_list);
 | 
						|
 | 
						|
	region->cur_snapshots++;
 | 
						|
 | 
						|
	devlink_nl_region_notify(region, snapshot, DEVLINK_CMD_REGION_NEW);
 | 
						|
	return 0;
 | 
						|
 | 
						|
err_snapshot_id_increment:
 | 
						|
	kfree(snapshot);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static void devlink_region_snapshot_del(struct devlink_region *region,
 | 
						|
					struct devlink_snapshot *snapshot)
 | 
						|
{
 | 
						|
	struct devlink *devlink = region->devlink;
 | 
						|
 | 
						|
	lockdep_assert_held(®ion->snapshot_lock);
 | 
						|
 | 
						|
	devlink_nl_region_notify(region, snapshot, DEVLINK_CMD_REGION_DEL);
 | 
						|
	region->cur_snapshots--;
 | 
						|
	list_del(&snapshot->list);
 | 
						|
	region->ops->destructor(snapshot->data);
 | 
						|
	__devlink_snapshot_id_decrement(devlink, snapshot->id);
 | 
						|
	kfree(snapshot);
 | 
						|
}
 | 
						|
 | 
						|
int devlink_nl_region_get_doit(struct sk_buff *skb, struct genl_info *info)
 | 
						|
{
 | 
						|
	struct devlink *devlink = info->user_ptr[0];
 | 
						|
	struct devlink_port *port = NULL;
 | 
						|
	struct devlink_region *region;
 | 
						|
	const char *region_name;
 | 
						|
	struct sk_buff *msg;
 | 
						|
	unsigned int index;
 | 
						|
	int err;
 | 
						|
 | 
						|
	if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME))
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) {
 | 
						|
		index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]);
 | 
						|
 | 
						|
		port = devlink_port_get_by_index(devlink, index);
 | 
						|
		if (!port)
 | 
						|
			return -ENODEV;
 | 
						|
	}
 | 
						|
 | 
						|
	region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]);
 | 
						|
	if (port)
 | 
						|
		region = devlink_port_region_get_by_name(port, region_name);
 | 
						|
	else
 | 
						|
		region = devlink_region_get_by_name(devlink, region_name);
 | 
						|
 | 
						|
	if (!region)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	msg = nlmsg_new(NLMSG_DEFAULT_SIZE, GFP_KERNEL);
 | 
						|
	if (!msg)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	err = devlink_nl_region_fill(msg, devlink, DEVLINK_CMD_REGION_GET,
 | 
						|
				     info->snd_portid, info->snd_seq, 0,
 | 
						|
				     region);
 | 
						|
	if (err) {
 | 
						|
		nlmsg_free(msg);
 | 
						|
		return err;
 | 
						|
	}
 | 
						|
 | 
						|
	return genlmsg_reply(msg, info);
 | 
						|
}
 | 
						|
 | 
						|
static int devlink_nl_cmd_region_get_port_dumpit(struct sk_buff *msg,
 | 
						|
						 struct netlink_callback *cb,
 | 
						|
						 struct devlink_port *port,
 | 
						|
						 int *idx, int start, int flags)
 | 
						|
{
 | 
						|
	struct devlink_region *region;
 | 
						|
	int err = 0;
 | 
						|
 | 
						|
	list_for_each_entry(region, &port->region_list, list) {
 | 
						|
		if (*idx < start) {
 | 
						|
			(*idx)++;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		err = devlink_nl_region_fill(msg, port->devlink,
 | 
						|
					     DEVLINK_CMD_REGION_GET,
 | 
						|
					     NETLINK_CB(cb->skb).portid,
 | 
						|
					     cb->nlh->nlmsg_seq,
 | 
						|
					     flags, region);
 | 
						|
		if (err)
 | 
						|
			goto out;
 | 
						|
		(*idx)++;
 | 
						|
	}
 | 
						|
 | 
						|
out:
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static int devlink_nl_region_get_dump_one(struct sk_buff *msg,
 | 
						|
					  struct devlink *devlink,
 | 
						|
					  struct netlink_callback *cb,
 | 
						|
					  int flags)
 | 
						|
{
 | 
						|
	struct devlink_nl_dump_state *state = devlink_dump_state(cb);
 | 
						|
	struct devlink_region *region;
 | 
						|
	struct devlink_port *port;
 | 
						|
	unsigned long port_index;
 | 
						|
	int idx = 0;
 | 
						|
	int err;
 | 
						|
 | 
						|
	list_for_each_entry(region, &devlink->region_list, list) {
 | 
						|
		if (idx < state->idx) {
 | 
						|
			idx++;
 | 
						|
			continue;
 | 
						|
		}
 | 
						|
		err = devlink_nl_region_fill(msg, devlink,
 | 
						|
					     DEVLINK_CMD_REGION_GET,
 | 
						|
					     NETLINK_CB(cb->skb).portid,
 | 
						|
					     cb->nlh->nlmsg_seq, flags,
 | 
						|
					     region);
 | 
						|
		if (err) {
 | 
						|
			state->idx = idx;
 | 
						|
			return err;
 | 
						|
		}
 | 
						|
		idx++;
 | 
						|
	}
 | 
						|
 | 
						|
	xa_for_each(&devlink->ports, port_index, port) {
 | 
						|
		err = devlink_nl_cmd_region_get_port_dumpit(msg, cb, port, &idx,
 | 
						|
							    state->idx, flags);
 | 
						|
		if (err) {
 | 
						|
			state->idx = idx;
 | 
						|
			return err;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int devlink_nl_region_get_dumpit(struct sk_buff *skb,
 | 
						|
				 struct netlink_callback *cb)
 | 
						|
{
 | 
						|
	return devlink_nl_dumpit(skb, cb, devlink_nl_region_get_dump_one);
 | 
						|
}
 | 
						|
 | 
						|
int devlink_nl_region_del_doit(struct sk_buff *skb, struct genl_info *info)
 | 
						|
{
 | 
						|
	struct devlink *devlink = info->user_ptr[0];
 | 
						|
	struct devlink_snapshot *snapshot;
 | 
						|
	struct devlink_port *port = NULL;
 | 
						|
	struct devlink_region *region;
 | 
						|
	const char *region_name;
 | 
						|
	unsigned int index;
 | 
						|
	u32 snapshot_id;
 | 
						|
 | 
						|
	if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME) ||
 | 
						|
	    GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_SNAPSHOT_ID))
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]);
 | 
						|
	snapshot_id = nla_get_u32(info->attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID]);
 | 
						|
 | 
						|
	if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) {
 | 
						|
		index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]);
 | 
						|
 | 
						|
		port = devlink_port_get_by_index(devlink, index);
 | 
						|
		if (!port)
 | 
						|
			return -ENODEV;
 | 
						|
	}
 | 
						|
 | 
						|
	if (port)
 | 
						|
		region = devlink_port_region_get_by_name(port, region_name);
 | 
						|
	else
 | 
						|
		region = devlink_region_get_by_name(devlink, region_name);
 | 
						|
 | 
						|
	if (!region)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	mutex_lock(®ion->snapshot_lock);
 | 
						|
	snapshot = devlink_region_snapshot_get_by_id(region, snapshot_id);
 | 
						|
	if (!snapshot) {
 | 
						|
		mutex_unlock(®ion->snapshot_lock);
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	devlink_region_snapshot_del(region, snapshot);
 | 
						|
	mutex_unlock(®ion->snapshot_lock);
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
int devlink_nl_region_new_doit(struct sk_buff *skb, struct genl_info *info)
 | 
						|
{
 | 
						|
	struct devlink *devlink = info->user_ptr[0];
 | 
						|
	struct devlink_snapshot *snapshot;
 | 
						|
	struct devlink_port *port = NULL;
 | 
						|
	struct nlattr *snapshot_id_attr;
 | 
						|
	struct devlink_region *region;
 | 
						|
	const char *region_name;
 | 
						|
	unsigned int index;
 | 
						|
	u32 snapshot_id;
 | 
						|
	u8 *data;
 | 
						|
	int err;
 | 
						|
 | 
						|
	if (GENL_REQ_ATTR_CHECK(info, DEVLINK_ATTR_REGION_NAME)) {
 | 
						|
		NL_SET_ERR_MSG(info->extack, "No region name provided");
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	region_name = nla_data(info->attrs[DEVLINK_ATTR_REGION_NAME]);
 | 
						|
 | 
						|
	if (info->attrs[DEVLINK_ATTR_PORT_INDEX]) {
 | 
						|
		index = nla_get_u32(info->attrs[DEVLINK_ATTR_PORT_INDEX]);
 | 
						|
 | 
						|
		port = devlink_port_get_by_index(devlink, index);
 | 
						|
		if (!port)
 | 
						|
			return -ENODEV;
 | 
						|
	}
 | 
						|
 | 
						|
	if (port)
 | 
						|
		region = devlink_port_region_get_by_name(port, region_name);
 | 
						|
	else
 | 
						|
		region = devlink_region_get_by_name(devlink, region_name);
 | 
						|
 | 
						|
	if (!region) {
 | 
						|
		NL_SET_ERR_MSG(info->extack, "The requested region does not exist");
 | 
						|
		return -EINVAL;
 | 
						|
	}
 | 
						|
 | 
						|
	if (!region->ops->snapshot) {
 | 
						|
		NL_SET_ERR_MSG(info->extack, "The requested region does not support taking an immediate snapshot");
 | 
						|
		return -EOPNOTSUPP;
 | 
						|
	}
 | 
						|
 | 
						|
	mutex_lock(®ion->snapshot_lock);
 | 
						|
 | 
						|
	if (region->cur_snapshots == region->max_snapshots) {
 | 
						|
		NL_SET_ERR_MSG(info->extack, "The region has reached the maximum number of stored snapshots");
 | 
						|
		err = -ENOSPC;
 | 
						|
		goto unlock;
 | 
						|
	}
 | 
						|
 | 
						|
	snapshot_id_attr = info->attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID];
 | 
						|
	if (snapshot_id_attr) {
 | 
						|
		snapshot_id = nla_get_u32(snapshot_id_attr);
 | 
						|
 | 
						|
		if (devlink_region_snapshot_get_by_id(region, snapshot_id)) {
 | 
						|
			NL_SET_ERR_MSG(info->extack, "The requested snapshot id is already in use");
 | 
						|
			err = -EEXIST;
 | 
						|
			goto unlock;
 | 
						|
		}
 | 
						|
 | 
						|
		err = __devlink_snapshot_id_insert(devlink, snapshot_id);
 | 
						|
		if (err)
 | 
						|
			goto unlock;
 | 
						|
	} else {
 | 
						|
		err = __devlink_region_snapshot_id_get(devlink, &snapshot_id);
 | 
						|
		if (err) {
 | 
						|
			NL_SET_ERR_MSG(info->extack, "Failed to allocate a new snapshot id");
 | 
						|
			goto unlock;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	if (port)
 | 
						|
		err = region->port_ops->snapshot(port, region->port_ops,
 | 
						|
						 info->extack, &data);
 | 
						|
	else
 | 
						|
		err = region->ops->snapshot(devlink, region->ops,
 | 
						|
					    info->extack, &data);
 | 
						|
	if (err)
 | 
						|
		goto err_snapshot_capture;
 | 
						|
 | 
						|
	err = __devlink_region_snapshot_create(region, data, snapshot_id);
 | 
						|
	if (err)
 | 
						|
		goto err_snapshot_create;
 | 
						|
 | 
						|
	if (!snapshot_id_attr) {
 | 
						|
		struct sk_buff *msg;
 | 
						|
 | 
						|
		snapshot = devlink_region_snapshot_get_by_id(region,
 | 
						|
							     snapshot_id);
 | 
						|
		if (WARN_ON(!snapshot)) {
 | 
						|
			err = -EINVAL;
 | 
						|
			goto unlock;
 | 
						|
		}
 | 
						|
 | 
						|
		msg = devlink_nl_region_notify_build(region, snapshot,
 | 
						|
						     DEVLINK_CMD_REGION_NEW,
 | 
						|
						     info->snd_portid,
 | 
						|
						     info->snd_seq);
 | 
						|
		err = PTR_ERR_OR_ZERO(msg);
 | 
						|
		if (err)
 | 
						|
			goto err_notify;
 | 
						|
 | 
						|
		err = genlmsg_reply(msg, info);
 | 
						|
		if (err)
 | 
						|
			goto err_notify;
 | 
						|
	}
 | 
						|
 | 
						|
	mutex_unlock(®ion->snapshot_lock);
 | 
						|
	return 0;
 | 
						|
 | 
						|
err_snapshot_create:
 | 
						|
	region->ops->destructor(data);
 | 
						|
err_snapshot_capture:
 | 
						|
	__devlink_snapshot_id_decrement(devlink, snapshot_id);
 | 
						|
	mutex_unlock(®ion->snapshot_lock);
 | 
						|
	return err;
 | 
						|
 | 
						|
err_notify:
 | 
						|
	devlink_region_snapshot_del(region, snapshot);
 | 
						|
unlock:
 | 
						|
	mutex_unlock(®ion->snapshot_lock);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static int devlink_nl_cmd_region_read_chunk_fill(struct sk_buff *msg,
 | 
						|
						 u8 *chunk, u32 chunk_size,
 | 
						|
						 u64 addr)
 | 
						|
{
 | 
						|
	struct nlattr *chunk_attr;
 | 
						|
	int err;
 | 
						|
 | 
						|
	chunk_attr = nla_nest_start_noflag(msg, DEVLINK_ATTR_REGION_CHUNK);
 | 
						|
	if (!chunk_attr)
 | 
						|
		return -EINVAL;
 | 
						|
 | 
						|
	err = nla_put(msg, DEVLINK_ATTR_REGION_CHUNK_DATA, chunk_size, chunk);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	err = nla_put_u64_64bit(msg, DEVLINK_ATTR_REGION_CHUNK_ADDR, addr,
 | 
						|
				DEVLINK_ATTR_PAD);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	nla_nest_end(msg, chunk_attr);
 | 
						|
	return 0;
 | 
						|
 | 
						|
nla_put_failure:
 | 
						|
	nla_nest_cancel(msg, chunk_attr);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
#define DEVLINK_REGION_READ_CHUNK_SIZE 256
 | 
						|
 | 
						|
typedef int devlink_chunk_fill_t(void *cb_priv, u8 *chunk, u32 chunk_size,
 | 
						|
				 u64 curr_offset,
 | 
						|
				 struct netlink_ext_ack *extack);
 | 
						|
 | 
						|
static int
 | 
						|
devlink_nl_region_read_fill(struct sk_buff *skb, devlink_chunk_fill_t *cb,
 | 
						|
			    void *cb_priv, u64 start_offset, u64 end_offset,
 | 
						|
			    u64 *new_offset, struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	u64 curr_offset = start_offset;
 | 
						|
	int err = 0;
 | 
						|
	u8 *data;
 | 
						|
 | 
						|
	/* Allocate and re-use a single buffer */
 | 
						|
	data = kmalloc(DEVLINK_REGION_READ_CHUNK_SIZE, GFP_KERNEL);
 | 
						|
	if (!data)
 | 
						|
		return -ENOMEM;
 | 
						|
 | 
						|
	*new_offset = start_offset;
 | 
						|
 | 
						|
	while (curr_offset < end_offset) {
 | 
						|
		u32 data_size;
 | 
						|
 | 
						|
		data_size = min_t(u32, end_offset - curr_offset,
 | 
						|
				  DEVLINK_REGION_READ_CHUNK_SIZE);
 | 
						|
 | 
						|
		err = cb(cb_priv, data, data_size, curr_offset, extack);
 | 
						|
		if (err)
 | 
						|
			break;
 | 
						|
 | 
						|
		err = devlink_nl_cmd_region_read_chunk_fill(skb, data, data_size, curr_offset);
 | 
						|
		if (err)
 | 
						|
			break;
 | 
						|
 | 
						|
		curr_offset += data_size;
 | 
						|
	}
 | 
						|
	*new_offset = curr_offset;
 | 
						|
 | 
						|
	kfree(data);
 | 
						|
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
devlink_region_snapshot_fill(void *cb_priv, u8 *chunk, u32 chunk_size,
 | 
						|
			     u64 curr_offset,
 | 
						|
			     struct netlink_ext_ack __always_unused *extack)
 | 
						|
{
 | 
						|
	struct devlink_snapshot *snapshot = cb_priv;
 | 
						|
 | 
						|
	memcpy(chunk, &snapshot->data[curr_offset], chunk_size);
 | 
						|
 | 
						|
	return 0;
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
devlink_region_port_direct_fill(void *cb_priv, u8 *chunk, u32 chunk_size,
 | 
						|
				u64 curr_offset, struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct devlink_region *region = cb_priv;
 | 
						|
 | 
						|
	return region->port_ops->read(region->port, region->port_ops, extack,
 | 
						|
				      curr_offset, chunk_size, chunk);
 | 
						|
}
 | 
						|
 | 
						|
static int
 | 
						|
devlink_region_direct_fill(void *cb_priv, u8 *chunk, u32 chunk_size,
 | 
						|
			   u64 curr_offset, struct netlink_ext_ack *extack)
 | 
						|
{
 | 
						|
	struct devlink_region *region = cb_priv;
 | 
						|
 | 
						|
	return region->ops->read(region->devlink, region->ops, extack,
 | 
						|
				 curr_offset, chunk_size, chunk);
 | 
						|
}
 | 
						|
 | 
						|
int devlink_nl_region_read_dumpit(struct sk_buff *skb,
 | 
						|
				  struct netlink_callback *cb)
 | 
						|
{
 | 
						|
	const struct genl_dumpit_info *info = genl_dumpit_info(cb);
 | 
						|
	struct devlink_nl_dump_state *state = devlink_dump_state(cb);
 | 
						|
	struct nlattr *chunks_attr, *region_attr, *snapshot_attr;
 | 
						|
	u64 ret_offset, start_offset, end_offset = U64_MAX;
 | 
						|
	struct nlattr **attrs = info->info.attrs;
 | 
						|
	struct devlink_port *port = NULL;
 | 
						|
	devlink_chunk_fill_t *region_cb;
 | 
						|
	struct devlink_region *region;
 | 
						|
	const char *region_name;
 | 
						|
	struct devlink *devlink;
 | 
						|
	unsigned int index;
 | 
						|
	void *region_cb_priv;
 | 
						|
	void *hdr;
 | 
						|
	int err;
 | 
						|
 | 
						|
	start_offset = state->start_offset;
 | 
						|
 | 
						|
	devlink = devlink_get_from_attrs_lock(sock_net(cb->skb->sk), attrs,
 | 
						|
					      false);
 | 
						|
	if (IS_ERR(devlink))
 | 
						|
		return PTR_ERR(devlink);
 | 
						|
 | 
						|
	if (!attrs[DEVLINK_ATTR_REGION_NAME]) {
 | 
						|
		NL_SET_ERR_MSG(cb->extack, "No region name provided");
 | 
						|
		err = -EINVAL;
 | 
						|
		goto out_unlock;
 | 
						|
	}
 | 
						|
 | 
						|
	if (attrs[DEVLINK_ATTR_PORT_INDEX]) {
 | 
						|
		index = nla_get_u32(attrs[DEVLINK_ATTR_PORT_INDEX]);
 | 
						|
 | 
						|
		port = devlink_port_get_by_index(devlink, index);
 | 
						|
		if (!port) {
 | 
						|
			err = -ENODEV;
 | 
						|
			goto out_unlock;
 | 
						|
		}
 | 
						|
	}
 | 
						|
 | 
						|
	region_attr = attrs[DEVLINK_ATTR_REGION_NAME];
 | 
						|
	region_name = nla_data(region_attr);
 | 
						|
 | 
						|
	if (port)
 | 
						|
		region = devlink_port_region_get_by_name(port, region_name);
 | 
						|
	else
 | 
						|
		region = devlink_region_get_by_name(devlink, region_name);
 | 
						|
 | 
						|
	if (!region) {
 | 
						|
		NL_SET_ERR_MSG_ATTR(cb->extack, region_attr, "Requested region does not exist");
 | 
						|
		err = -EINVAL;
 | 
						|
		goto out_unlock;
 | 
						|
	}
 | 
						|
 | 
						|
	snapshot_attr = attrs[DEVLINK_ATTR_REGION_SNAPSHOT_ID];
 | 
						|
	if (!snapshot_attr) {
 | 
						|
		if (!nla_get_flag(attrs[DEVLINK_ATTR_REGION_DIRECT])) {
 | 
						|
			NL_SET_ERR_MSG(cb->extack, "No snapshot id provided");
 | 
						|
			err = -EINVAL;
 | 
						|
			goto out_unlock;
 | 
						|
		}
 | 
						|
 | 
						|
		if (!region->ops->read) {
 | 
						|
			NL_SET_ERR_MSG(cb->extack, "Requested region does not support direct read");
 | 
						|
			err = -EOPNOTSUPP;
 | 
						|
			goto out_unlock;
 | 
						|
		}
 | 
						|
 | 
						|
		if (port)
 | 
						|
			region_cb = &devlink_region_port_direct_fill;
 | 
						|
		else
 | 
						|
			region_cb = &devlink_region_direct_fill;
 | 
						|
		region_cb_priv = region;
 | 
						|
	} else {
 | 
						|
		struct devlink_snapshot *snapshot;
 | 
						|
		u32 snapshot_id;
 | 
						|
 | 
						|
		if (nla_get_flag(attrs[DEVLINK_ATTR_REGION_DIRECT])) {
 | 
						|
			NL_SET_ERR_MSG_ATTR(cb->extack, snapshot_attr, "Direct region read does not use snapshot");
 | 
						|
			err = -EINVAL;
 | 
						|
			goto out_unlock;
 | 
						|
		}
 | 
						|
 | 
						|
		snapshot_id = nla_get_u32(snapshot_attr);
 | 
						|
		snapshot = devlink_region_snapshot_get_by_id(region, snapshot_id);
 | 
						|
		if (!snapshot) {
 | 
						|
			NL_SET_ERR_MSG_ATTR(cb->extack, snapshot_attr, "Requested snapshot does not exist");
 | 
						|
			err = -EINVAL;
 | 
						|
			goto out_unlock;
 | 
						|
		}
 | 
						|
		region_cb = &devlink_region_snapshot_fill;
 | 
						|
		region_cb_priv = snapshot;
 | 
						|
	}
 | 
						|
 | 
						|
	if (attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR] &&
 | 
						|
	    attrs[DEVLINK_ATTR_REGION_CHUNK_LEN]) {
 | 
						|
		if (!start_offset)
 | 
						|
			start_offset =
 | 
						|
				nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR]);
 | 
						|
 | 
						|
		end_offset = nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_ADDR]);
 | 
						|
		end_offset += nla_get_u64(attrs[DEVLINK_ATTR_REGION_CHUNK_LEN]);
 | 
						|
	}
 | 
						|
 | 
						|
	if (end_offset > region->size)
 | 
						|
		end_offset = region->size;
 | 
						|
 | 
						|
	/* return 0 if there is no further data to read */
 | 
						|
	if (start_offset == end_offset) {
 | 
						|
		err = 0;
 | 
						|
		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) {
 | 
						|
		err = -EMSGSIZE;
 | 
						|
		goto out_unlock;
 | 
						|
	}
 | 
						|
 | 
						|
	err = devlink_nl_put_handle(skb, devlink);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	if (region->port) {
 | 
						|
		err = nla_put_u32(skb, DEVLINK_ATTR_PORT_INDEX,
 | 
						|
				  region->port->index);
 | 
						|
		if (err)
 | 
						|
			goto nla_put_failure;
 | 
						|
	}
 | 
						|
 | 
						|
	err = nla_put_string(skb, DEVLINK_ATTR_REGION_NAME, region_name);
 | 
						|
	if (err)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	chunks_attr = nla_nest_start_noflag(skb, DEVLINK_ATTR_REGION_CHUNKS);
 | 
						|
	if (!chunks_attr) {
 | 
						|
		err = -EMSGSIZE;
 | 
						|
		goto nla_put_failure;
 | 
						|
	}
 | 
						|
 | 
						|
	err = devlink_nl_region_read_fill(skb, region_cb, region_cb_priv,
 | 
						|
					  start_offset, end_offset, &ret_offset,
 | 
						|
					  cb->extack);
 | 
						|
 | 
						|
	if (err && err != -EMSGSIZE)
 | 
						|
		goto nla_put_failure;
 | 
						|
 | 
						|
	/* Check if there was any progress done to prevent infinite loop */
 | 
						|
	if (ret_offset == start_offset) {
 | 
						|
		err = -EINVAL;
 | 
						|
		goto nla_put_failure;
 | 
						|
	}
 | 
						|
 | 
						|
	state->start_offset = ret_offset;
 | 
						|
 | 
						|
	nla_nest_end(skb, chunks_attr);
 | 
						|
	genlmsg_end(skb, hdr);
 | 
						|
	devl_unlock(devlink);
 | 
						|
	devlink_put(devlink);
 | 
						|
	return skb->len;
 | 
						|
 | 
						|
nla_put_failure:
 | 
						|
	genlmsg_cancel(skb, hdr);
 | 
						|
out_unlock:
 | 
						|
	devl_unlock(devlink);
 | 
						|
	devlink_put(devlink);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
 | 
						|
/**
 | 
						|
 * devl_region_create - create a new address region
 | 
						|
 *
 | 
						|
 * @devlink: devlink
 | 
						|
 * @ops: region operations and name
 | 
						|
 * @region_max_snapshots: Maximum supported number of snapshots for region
 | 
						|
 * @region_size: size of region
 | 
						|
 */
 | 
						|
struct devlink_region *devl_region_create(struct devlink *devlink,
 | 
						|
					  const struct devlink_region_ops *ops,
 | 
						|
					  u32 region_max_snapshots,
 | 
						|
					  u64 region_size)
 | 
						|
{
 | 
						|
	struct devlink_region *region;
 | 
						|
 | 
						|
	devl_assert_locked(devlink);
 | 
						|
 | 
						|
	if (WARN_ON(!ops) || WARN_ON(!ops->destructor))
 | 
						|
		return ERR_PTR(-EINVAL);
 | 
						|
 | 
						|
	if (devlink_region_get_by_name(devlink, ops->name))
 | 
						|
		return ERR_PTR(-EEXIST);
 | 
						|
 | 
						|
	region = kzalloc(sizeof(*region), GFP_KERNEL);
 | 
						|
	if (!region)
 | 
						|
		return ERR_PTR(-ENOMEM);
 | 
						|
 | 
						|
	region->devlink = devlink;
 | 
						|
	region->max_snapshots = region_max_snapshots;
 | 
						|
	region->ops = ops;
 | 
						|
	region->size = region_size;
 | 
						|
	INIT_LIST_HEAD(®ion->snapshot_list);
 | 
						|
	mutex_init(®ion->snapshot_lock);
 | 
						|
	list_add_tail(®ion->list, &devlink->region_list);
 | 
						|
	devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW);
 | 
						|
 | 
						|
	return region;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devl_region_create);
 | 
						|
 | 
						|
/**
 | 
						|
 *	devlink_region_create - create a new address region
 | 
						|
 *
 | 
						|
 *	@devlink: devlink
 | 
						|
 *	@ops: region operations and name
 | 
						|
 *	@region_max_snapshots: Maximum supported number of snapshots for region
 | 
						|
 *	@region_size: size of region
 | 
						|
 *
 | 
						|
 *	Context: Takes and release devlink->lock <mutex>.
 | 
						|
 */
 | 
						|
struct devlink_region *
 | 
						|
devlink_region_create(struct devlink *devlink,
 | 
						|
		      const struct devlink_region_ops *ops,
 | 
						|
		      u32 region_max_snapshots, u64 region_size)
 | 
						|
{
 | 
						|
	struct devlink_region *region;
 | 
						|
 | 
						|
	devl_lock(devlink);
 | 
						|
	region = devl_region_create(devlink, ops, region_max_snapshots,
 | 
						|
				    region_size);
 | 
						|
	devl_unlock(devlink);
 | 
						|
	return region;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devlink_region_create);
 | 
						|
 | 
						|
/**
 | 
						|
 *	devlink_port_region_create - create a new address region for a port
 | 
						|
 *
 | 
						|
 *	@port: devlink port
 | 
						|
 *	@ops: region operations and name
 | 
						|
 *	@region_max_snapshots: Maximum supported number of snapshots for region
 | 
						|
 *	@region_size: size of region
 | 
						|
 *
 | 
						|
 *	Context: Takes and release devlink->lock <mutex>.
 | 
						|
 */
 | 
						|
struct devlink_region *
 | 
						|
devlink_port_region_create(struct devlink_port *port,
 | 
						|
			   const struct devlink_port_region_ops *ops,
 | 
						|
			   u32 region_max_snapshots, u64 region_size)
 | 
						|
{
 | 
						|
	struct devlink *devlink = port->devlink;
 | 
						|
	struct devlink_region *region;
 | 
						|
	int err = 0;
 | 
						|
 | 
						|
	ASSERT_DEVLINK_PORT_INITIALIZED(port);
 | 
						|
 | 
						|
	if (WARN_ON(!ops) || WARN_ON(!ops->destructor))
 | 
						|
		return ERR_PTR(-EINVAL);
 | 
						|
 | 
						|
	devl_lock(devlink);
 | 
						|
 | 
						|
	if (devlink_port_region_get_by_name(port, ops->name)) {
 | 
						|
		err = -EEXIST;
 | 
						|
		goto unlock;
 | 
						|
	}
 | 
						|
 | 
						|
	region = kzalloc(sizeof(*region), GFP_KERNEL);
 | 
						|
	if (!region) {
 | 
						|
		err = -ENOMEM;
 | 
						|
		goto unlock;
 | 
						|
	}
 | 
						|
 | 
						|
	region->devlink = devlink;
 | 
						|
	region->port = port;
 | 
						|
	region->max_snapshots = region_max_snapshots;
 | 
						|
	region->port_ops = ops;
 | 
						|
	region->size = region_size;
 | 
						|
	INIT_LIST_HEAD(®ion->snapshot_list);
 | 
						|
	mutex_init(®ion->snapshot_lock);
 | 
						|
	list_add_tail(®ion->list, &port->region_list);
 | 
						|
	devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_NEW);
 | 
						|
 | 
						|
	devl_unlock(devlink);
 | 
						|
	return region;
 | 
						|
 | 
						|
unlock:
 | 
						|
	devl_unlock(devlink);
 | 
						|
	return ERR_PTR(err);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devlink_port_region_create);
 | 
						|
 | 
						|
/**
 | 
						|
 * devl_region_destroy - destroy address region
 | 
						|
 *
 | 
						|
 * @region: devlink region to destroy
 | 
						|
 */
 | 
						|
void devl_region_destroy(struct devlink_region *region)
 | 
						|
{
 | 
						|
	struct devlink *devlink = region->devlink;
 | 
						|
	struct devlink_snapshot *snapshot, *ts;
 | 
						|
 | 
						|
	devl_assert_locked(devlink);
 | 
						|
 | 
						|
	/* Free all snapshots of region */
 | 
						|
	mutex_lock(®ion->snapshot_lock);
 | 
						|
	list_for_each_entry_safe(snapshot, ts, ®ion->snapshot_list, list)
 | 
						|
		devlink_region_snapshot_del(region, snapshot);
 | 
						|
	mutex_unlock(®ion->snapshot_lock);
 | 
						|
 | 
						|
	list_del(®ion->list);
 | 
						|
	mutex_destroy(®ion->snapshot_lock);
 | 
						|
 | 
						|
	devlink_nl_region_notify(region, NULL, DEVLINK_CMD_REGION_DEL);
 | 
						|
	kfree(region);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devl_region_destroy);
 | 
						|
 | 
						|
/**
 | 
						|
 *	devlink_region_destroy - destroy address region
 | 
						|
 *
 | 
						|
 *	@region: devlink region to destroy
 | 
						|
 *
 | 
						|
 *	Context: Takes and release devlink->lock <mutex>.
 | 
						|
 */
 | 
						|
void devlink_region_destroy(struct devlink_region *region)
 | 
						|
{
 | 
						|
	struct devlink *devlink = region->devlink;
 | 
						|
 | 
						|
	devl_lock(devlink);
 | 
						|
	devl_region_destroy(region);
 | 
						|
	devl_unlock(devlink);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devlink_region_destroy);
 | 
						|
 | 
						|
/**
 | 
						|
 *	devlink_region_snapshot_id_get - get snapshot ID
 | 
						|
 *
 | 
						|
 *	This callback should be called when adding a new snapshot,
 | 
						|
 *	Driver should use the same id for multiple snapshots taken
 | 
						|
 *	on multiple regions at the same time/by the same trigger.
 | 
						|
 *
 | 
						|
 *	The caller of this function must use devlink_region_snapshot_id_put
 | 
						|
 *	when finished creating regions using this id.
 | 
						|
 *
 | 
						|
 *	Returns zero on success, or a negative error code on failure.
 | 
						|
 *
 | 
						|
 *	@devlink: devlink
 | 
						|
 *	@id: storage to return id
 | 
						|
 */
 | 
						|
int devlink_region_snapshot_id_get(struct devlink *devlink, u32 *id)
 | 
						|
{
 | 
						|
	return __devlink_region_snapshot_id_get(devlink, id);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devlink_region_snapshot_id_get);
 | 
						|
 | 
						|
/**
 | 
						|
 *	devlink_region_snapshot_id_put - put snapshot ID reference
 | 
						|
 *
 | 
						|
 *	This should be called by a driver after finishing creating snapshots
 | 
						|
 *	with an id. Doing so ensures that the ID can later be released in the
 | 
						|
 *	event that all snapshots using it have been destroyed.
 | 
						|
 *
 | 
						|
 *	@devlink: devlink
 | 
						|
 *	@id: id to release reference on
 | 
						|
 */
 | 
						|
void devlink_region_snapshot_id_put(struct devlink *devlink, u32 id)
 | 
						|
{
 | 
						|
	__devlink_snapshot_id_decrement(devlink, id);
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devlink_region_snapshot_id_put);
 | 
						|
 | 
						|
/**
 | 
						|
 *	devlink_region_snapshot_create - create a new snapshot
 | 
						|
 *	This will add a new snapshot of a region. The snapshot
 | 
						|
 *	will be stored on the region struct and can be accessed
 | 
						|
 *	from devlink. This is useful for future analyses of snapshots.
 | 
						|
 *	Multiple snapshots can be created on a region.
 | 
						|
 *	The @snapshot_id should be obtained using the getter function.
 | 
						|
 *
 | 
						|
 *	@region: devlink region of the snapshot
 | 
						|
 *	@data: snapshot data
 | 
						|
 *	@snapshot_id: snapshot id to be created
 | 
						|
 */
 | 
						|
int devlink_region_snapshot_create(struct devlink_region *region,
 | 
						|
				   u8 *data, u32 snapshot_id)
 | 
						|
{
 | 
						|
	int err;
 | 
						|
 | 
						|
	mutex_lock(®ion->snapshot_lock);
 | 
						|
	err = __devlink_region_snapshot_create(region, data, snapshot_id);
 | 
						|
	mutex_unlock(®ion->snapshot_lock);
 | 
						|
	return err;
 | 
						|
}
 | 
						|
EXPORT_SYMBOL_GPL(devlink_region_snapshot_create);
 |