|  | // 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 -EMSGSIZE; | 
|  |  | 
|  | 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 -EMSGSIZE; | 
|  |  | 
|  | 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 = devlink_nl_put_u64(msg, DEVLINK_ATTR_REGION_SIZE, region->size); | 
|  | 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 = devlink_nl_put_u64(msg, DEVLINK_ATTR_REGION_SIZE, | 
|  | region->size); | 
|  | 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 = devlink_nl_put_u64(msg, DEVLINK_ATTR_REGION_CHUNK_ADDR, addr); | 
|  | 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); |