kobject_init_and_add() takes reference even when it fails.
If this function returns an error, kobject_put() must be called to
properly clean up the memory associated with the object. Thus,
replace kfree() by kobject_put() to fix this issue. Previous
commit "b8eb718348b8" fixed a similar problem.
Fixes: 7b96953bc6 ("vfio: Mediated device Core driver")
Signed-off-by: Qiushi Wu <wu000273@umn.edu>
Reviewed-by: Cornelia Huck <cohuck@redhat.com>
Reviewed-by: Kirti Wankhede <kwankhede@nvidia.com>
Signed-off-by: Alex Williamson <alex.williamson@redhat.com>
		
	
			
		
			
				
	
	
		
			282 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
			
		
		
	
	
			282 lines
		
	
	
		
			6.2 KiB
		
	
	
	
		
			C
		
	
	
	
	
	
| // SPDX-License-Identifier: GPL-2.0-only
 | |
| /*
 | |
|  * File attributes for Mediated devices
 | |
|  *
 | |
|  * Copyright (c) 2016, NVIDIA CORPORATION. All rights reserved.
 | |
|  *     Author: Neo Jia <cjia@nvidia.com>
 | |
|  *             Kirti Wankhede <kwankhede@nvidia.com>
 | |
|  */
 | |
| 
 | |
| #include <linux/sysfs.h>
 | |
| #include <linux/ctype.h>
 | |
| #include <linux/device.h>
 | |
| #include <linux/slab.h>
 | |
| #include <linux/uuid.h>
 | |
| #include <linux/mdev.h>
 | |
| 
 | |
| #include "mdev_private.h"
 | |
| 
 | |
| /* Static functions */
 | |
| 
 | |
| static ssize_t mdev_type_attr_show(struct kobject *kobj,
 | |
| 				     struct attribute *__attr, char *buf)
 | |
| {
 | |
| 	struct mdev_type_attribute *attr = to_mdev_type_attr(__attr);
 | |
| 	struct mdev_type *type = to_mdev_type(kobj);
 | |
| 	ssize_t ret = -EIO;
 | |
| 
 | |
| 	if (attr->show)
 | |
| 		ret = attr->show(kobj, type->parent->dev, buf);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t mdev_type_attr_store(struct kobject *kobj,
 | |
| 				      struct attribute *__attr,
 | |
| 				      const char *buf, size_t count)
 | |
| {
 | |
| 	struct mdev_type_attribute *attr = to_mdev_type_attr(__attr);
 | |
| 	struct mdev_type *type = to_mdev_type(kobj);
 | |
| 	ssize_t ret = -EIO;
 | |
| 
 | |
| 	if (attr->store)
 | |
| 		ret = attr->store(&type->kobj, type->parent->dev, buf, count);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static const struct sysfs_ops mdev_type_sysfs_ops = {
 | |
| 	.show = mdev_type_attr_show,
 | |
| 	.store = mdev_type_attr_store,
 | |
| };
 | |
| 
 | |
| static ssize_t create_store(struct kobject *kobj, struct device *dev,
 | |
| 			    const char *buf, size_t count)
 | |
| {
 | |
| 	char *str;
 | |
| 	guid_t uuid;
 | |
| 	int ret;
 | |
| 
 | |
| 	if ((count < UUID_STRING_LEN) || (count > UUID_STRING_LEN + 1))
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	str = kstrndup(buf, count, GFP_KERNEL);
 | |
| 	if (!str)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	ret = guid_parse(str, &uuid);
 | |
| 	kfree(str);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = mdev_device_create(kobj, dev, &uuid);
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static MDEV_TYPE_ATTR_WO(create);
 | |
| 
 | |
| static void mdev_type_release(struct kobject *kobj)
 | |
| {
 | |
| 	struct mdev_type *type = to_mdev_type(kobj);
 | |
| 
 | |
| 	pr_debug("Releasing group %s\n", kobj->name);
 | |
| 	kfree(type);
 | |
| }
 | |
| 
 | |
| static struct kobj_type mdev_type_ktype = {
 | |
| 	.sysfs_ops = &mdev_type_sysfs_ops,
 | |
| 	.release = mdev_type_release,
 | |
| };
 | |
| 
 | |
| static struct mdev_type *add_mdev_supported_type(struct mdev_parent *parent,
 | |
| 						 struct attribute_group *group)
 | |
| {
 | |
| 	struct mdev_type *type;
 | |
| 	int ret;
 | |
| 
 | |
| 	if (!group->name) {
 | |
| 		pr_err("%s: Type name empty!\n", __func__);
 | |
| 		return ERR_PTR(-EINVAL);
 | |
| 	}
 | |
| 
 | |
| 	type = kzalloc(sizeof(*type), GFP_KERNEL);
 | |
| 	if (!type)
 | |
| 		return ERR_PTR(-ENOMEM);
 | |
| 
 | |
| 	type->kobj.kset = parent->mdev_types_kset;
 | |
| 
 | |
| 	ret = kobject_init_and_add(&type->kobj, &mdev_type_ktype, NULL,
 | |
| 				   "%s-%s", dev_driver_string(parent->dev),
 | |
| 				   group->name);
 | |
| 	if (ret) {
 | |
| 		kobject_put(&type->kobj);
 | |
| 		return ERR_PTR(ret);
 | |
| 	}
 | |
| 
 | |
| 	ret = sysfs_create_file(&type->kobj, &mdev_type_attr_create.attr);
 | |
| 	if (ret)
 | |
| 		goto attr_create_failed;
 | |
| 
 | |
| 	type->devices_kobj = kobject_create_and_add("devices", &type->kobj);
 | |
| 	if (!type->devices_kobj) {
 | |
| 		ret = -ENOMEM;
 | |
| 		goto attr_devices_failed;
 | |
| 	}
 | |
| 
 | |
| 	ret = sysfs_create_files(&type->kobj,
 | |
| 				 (const struct attribute **)group->attrs);
 | |
| 	if (ret) {
 | |
| 		ret = -ENOMEM;
 | |
| 		goto attrs_failed;
 | |
| 	}
 | |
| 
 | |
| 	type->group = group;
 | |
| 	type->parent = parent;
 | |
| 	return type;
 | |
| 
 | |
| attrs_failed:
 | |
| 	kobject_put(type->devices_kobj);
 | |
| attr_devices_failed:
 | |
| 	sysfs_remove_file(&type->kobj, &mdev_type_attr_create.attr);
 | |
| attr_create_failed:
 | |
| 	kobject_del(&type->kobj);
 | |
| 	kobject_put(&type->kobj);
 | |
| 	return ERR_PTR(ret);
 | |
| }
 | |
| 
 | |
| static void remove_mdev_supported_type(struct mdev_type *type)
 | |
| {
 | |
| 	sysfs_remove_files(&type->kobj,
 | |
| 			   (const struct attribute **)type->group->attrs);
 | |
| 	kobject_put(type->devices_kobj);
 | |
| 	sysfs_remove_file(&type->kobj, &mdev_type_attr_create.attr);
 | |
| 	kobject_del(&type->kobj);
 | |
| 	kobject_put(&type->kobj);
 | |
| }
 | |
| 
 | |
| static int add_mdev_supported_type_groups(struct mdev_parent *parent)
 | |
| {
 | |
| 	int i;
 | |
| 
 | |
| 	for (i = 0; parent->ops->supported_type_groups[i]; i++) {
 | |
| 		struct mdev_type *type;
 | |
| 
 | |
| 		type = add_mdev_supported_type(parent,
 | |
| 					parent->ops->supported_type_groups[i]);
 | |
| 		if (IS_ERR(type)) {
 | |
| 			struct mdev_type *ltype, *tmp;
 | |
| 
 | |
| 			list_for_each_entry_safe(ltype, tmp, &parent->type_list,
 | |
| 						  next) {
 | |
| 				list_del(<ype->next);
 | |
| 				remove_mdev_supported_type(ltype);
 | |
| 			}
 | |
| 			return PTR_ERR(type);
 | |
| 		}
 | |
| 		list_add(&type->next, &parent->type_list);
 | |
| 	}
 | |
| 	return 0;
 | |
| }
 | |
| 
 | |
| /* mdev sysfs functions */
 | |
| void parent_remove_sysfs_files(struct mdev_parent *parent)
 | |
| {
 | |
| 	struct mdev_type *type, *tmp;
 | |
| 
 | |
| 	list_for_each_entry_safe(type, tmp, &parent->type_list, next) {
 | |
| 		list_del(&type->next);
 | |
| 		remove_mdev_supported_type(type);
 | |
| 	}
 | |
| 
 | |
| 	sysfs_remove_groups(&parent->dev->kobj, parent->ops->dev_attr_groups);
 | |
| 	kset_unregister(parent->mdev_types_kset);
 | |
| }
 | |
| 
 | |
| int parent_create_sysfs_files(struct mdev_parent *parent)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	parent->mdev_types_kset = kset_create_and_add("mdev_supported_types",
 | |
| 					       NULL, &parent->dev->kobj);
 | |
| 
 | |
| 	if (!parent->mdev_types_kset)
 | |
| 		return -ENOMEM;
 | |
| 
 | |
| 	INIT_LIST_HEAD(&parent->type_list);
 | |
| 
 | |
| 	ret = sysfs_create_groups(&parent->dev->kobj,
 | |
| 				  parent->ops->dev_attr_groups);
 | |
| 	if (ret)
 | |
| 		goto create_err;
 | |
| 
 | |
| 	ret = add_mdev_supported_type_groups(parent);
 | |
| 	if (ret)
 | |
| 		sysfs_remove_groups(&parent->dev->kobj,
 | |
| 				    parent->ops->dev_attr_groups);
 | |
| 	else
 | |
| 		return ret;
 | |
| 
 | |
| create_err:
 | |
| 	kset_unregister(parent->mdev_types_kset);
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| static ssize_t remove_store(struct device *dev, struct device_attribute *attr,
 | |
| 			    const char *buf, size_t count)
 | |
| {
 | |
| 	unsigned long val;
 | |
| 
 | |
| 	if (kstrtoul(buf, 0, &val) < 0)
 | |
| 		return -EINVAL;
 | |
| 
 | |
| 	if (val && device_remove_file_self(dev, attr)) {
 | |
| 		int ret;
 | |
| 
 | |
| 		ret = mdev_device_remove(dev);
 | |
| 		if (ret)
 | |
| 			return ret;
 | |
| 	}
 | |
| 
 | |
| 	return count;
 | |
| }
 | |
| 
 | |
| static DEVICE_ATTR_WO(remove);
 | |
| 
 | |
| static const struct attribute *mdev_device_attrs[] = {
 | |
| 	&dev_attr_remove.attr,
 | |
| 	NULL,
 | |
| };
 | |
| 
 | |
| int  mdev_create_sysfs_files(struct device *dev, struct mdev_type *type)
 | |
| {
 | |
| 	int ret;
 | |
| 
 | |
| 	ret = sysfs_create_link(type->devices_kobj, &dev->kobj, dev_name(dev));
 | |
| 	if (ret)
 | |
| 		return ret;
 | |
| 
 | |
| 	ret = sysfs_create_link(&dev->kobj, &type->kobj, "mdev_type");
 | |
| 	if (ret)
 | |
| 		goto type_link_failed;
 | |
| 
 | |
| 	ret = sysfs_create_files(&dev->kobj, mdev_device_attrs);
 | |
| 	if (ret)
 | |
| 		goto create_files_failed;
 | |
| 
 | |
| 	return ret;
 | |
| 
 | |
| create_files_failed:
 | |
| 	sysfs_remove_link(&dev->kobj, "mdev_type");
 | |
| type_link_failed:
 | |
| 	sysfs_remove_link(type->devices_kobj, dev_name(dev));
 | |
| 	return ret;
 | |
| }
 | |
| 
 | |
| void mdev_remove_sysfs_files(struct device *dev, struct mdev_type *type)
 | |
| {
 | |
| 	sysfs_remove_files(&dev->kobj, mdev_device_attrs);
 | |
| 	sysfs_remove_link(&dev->kobj, "mdev_type");
 | |
| 	sysfs_remove_link(type->devices_kobj, dev_name(dev));
 | |
| }
 |