Author | Tokens | Token Proportion | Commits | Commit Proportion |
---|---|---|---|---|
Simon Que | 1450 | 91.66% | 1 | 9.09% |
Todd Android Poynor | 119 | 7.52% | 5 | 45.45% |
Kees Cook | 4 | 0.25% | 1 | 9.09% |
Ivan Bornyakov | 4 | 0.25% | 1 | 9.09% |
Sanjana Sanikommu | 3 | 0.19% | 1 | 9.09% |
Greg Kroah-Hartman | 2 | 0.13% | 2 | 18.18% |
Total | 1582 | 11 |
// SPDX-License-Identifier: GPL-2.0 /* Copyright (C) 2018 Google, Inc. */ #include "gasket_sysfs.h" #include "gasket_core.h" #include <linux/device.h> #include <linux/printk.h> /* * Pair of kernel device and user-specified pointer. Used in lookups in sysfs * "show" functions to return user data. */ struct gasket_sysfs_mapping { /* * The device bound to this mapping. If this is NULL, then this mapping * is free. */ struct device *device; /* The Gasket descriptor for this device. */ struct gasket_dev *gasket_dev; /* This device's set of sysfs attributes/nodes. */ struct gasket_sysfs_attribute *attributes; /* The number of live elements in "attributes". */ int attribute_count; /* Protects structure from simultaneous access. */ struct mutex mutex; /* Tracks active users of this mapping. */ struct kref refcount; }; /* * Data needed to manage users of this sysfs utility. * Currently has a fixed size; if space is a concern, this can be dynamically * allocated. */ /* * 'Global' (file-scoped) list of mappings between devices and gasket_data * pointers. This removes the requirement to have a gasket_sysfs_data * handle in all files. */ static struct gasket_sysfs_mapping dev_mappings[GASKET_SYSFS_NUM_MAPPINGS]; /* Callback when a mapping's refcount goes to zero. */ static void release_entry(struct kref *ref) { /* All work is done after the return from kref_put. */ } /* Look up mapping information for the given device. */ static struct gasket_sysfs_mapping *get_mapping(struct device *device) { int i; for (i = 0; i < GASKET_SYSFS_NUM_MAPPINGS; i++) { mutex_lock(&dev_mappings[i].mutex); if (dev_mappings[i].device == device) { kref_get(&dev_mappings[i].refcount); mutex_unlock(&dev_mappings[i].mutex); return &dev_mappings[i]; } mutex_unlock(&dev_mappings[i].mutex); } dev_dbg(device, "%s: Mapping to device %s not found\n", __func__, device->kobj.name); return NULL; } /* Put a reference to a mapping. */ static void put_mapping(struct gasket_sysfs_mapping *mapping) { int i; int num_files_to_remove = 0; struct device_attribute *files_to_remove; struct device *device; if (!mapping) { pr_debug("%s: Mapping should not be NULL\n", __func__); return; } mutex_lock(&mapping->mutex); if (kref_put(&mapping->refcount, release_entry)) { dev_dbg(mapping->device, "Removing Gasket sysfs mapping\n"); /* * We can't remove the sysfs nodes in the kref callback, since * device_remove_file() blocks until the node is free. * Readers/writers of sysfs nodes, though, will be blocked on * the mapping mutex, resulting in deadlock. To fix this, the * sysfs nodes are removed outside the lock. */ device = mapping->device; num_files_to_remove = mapping->attribute_count; files_to_remove = kcalloc(num_files_to_remove, sizeof(*files_to_remove), GFP_KERNEL); if (files_to_remove) for (i = 0; i < num_files_to_remove; i++) files_to_remove[i] = mapping->attributes[i].attr; else num_files_to_remove = 0; kfree(mapping->attributes); mapping->attributes = NULL; mapping->attribute_count = 0; put_device(mapping->device); mapping->device = NULL; mapping->gasket_dev = NULL; } mutex_unlock(&mapping->mutex); if (num_files_to_remove != 0) { for (i = 0; i < num_files_to_remove; ++i) device_remove_file(device, &files_to_remove[i]); kfree(files_to_remove); } } /* * Put a reference to a mapping N times. * * In higher-level resource acquire/release function pairs, the release function * will need to release a mapping 2x - once for the refcount taken in the * release function itself, and once for the count taken in the acquire call. */ static void put_mapping_n(struct gasket_sysfs_mapping *mapping, int times) { int i; for (i = 0; i < times; i++) put_mapping(mapping); } void gasket_sysfs_init(void) { int i; for (i = 0; i < GASKET_SYSFS_NUM_MAPPINGS; i++) { dev_mappings[i].device = NULL; mutex_init(&dev_mappings[i].mutex); } } int gasket_sysfs_create_mapping(struct device *device, struct gasket_dev *gasket_dev) { struct gasket_sysfs_mapping *mapping; int map_idx = -1; /* * We need a function-level mutex to protect against the same device * being added [multiple times] simultaneously. */ static DEFINE_MUTEX(function_mutex); mutex_lock(&function_mutex); dev_dbg(device, "Creating sysfs entries for device\n"); /* Check that the device we're adding hasn't already been added. */ mapping = get_mapping(device); if (mapping) { dev_err(device, "Attempting to re-initialize sysfs mapping for device\n"); put_mapping(mapping); mutex_unlock(&function_mutex); return -EBUSY; } /* Find the first empty entry in the array. */ for (map_idx = 0; map_idx < GASKET_SYSFS_NUM_MAPPINGS; ++map_idx) { mutex_lock(&dev_mappings[map_idx].mutex); if (!dev_mappings[map_idx].device) /* Break with the mutex held! */ break; mutex_unlock(&dev_mappings[map_idx].mutex); } if (map_idx == GASKET_SYSFS_NUM_MAPPINGS) { dev_err(device, "All mappings have been exhausted\n"); mutex_unlock(&function_mutex); return -ENOMEM; } dev_dbg(device, "Creating sysfs mapping for device %s\n", device->kobj.name); mapping = &dev_mappings[map_idx]; mapping->attributes = kcalloc(GASKET_SYSFS_MAX_NODES, sizeof(*mapping->attributes), GFP_KERNEL); if (!mapping->attributes) { dev_dbg(device, "Unable to allocate sysfs attribute array\n"); mutex_unlock(&mapping->mutex); mutex_unlock(&function_mutex); return -ENOMEM; } kref_init(&mapping->refcount); mapping->device = get_device(device); mapping->gasket_dev = gasket_dev; mapping->attribute_count = 0; mutex_unlock(&mapping->mutex); mutex_unlock(&function_mutex); /* Don't decrement the refcount here! One open count keeps it alive! */ return 0; } int gasket_sysfs_create_entries(struct device *device, const struct gasket_sysfs_attribute *attrs) { int i; int ret; struct gasket_sysfs_mapping *mapping = get_mapping(device); if (!mapping) { dev_dbg(device, "Creating entries for device without first initializing mapping\n"); return -EINVAL; } mutex_lock(&mapping->mutex); for (i = 0; strcmp(attrs[i].attr.attr.name, GASKET_ARRAY_END_MARKER); i++) { if (mapping->attribute_count == GASKET_SYSFS_MAX_NODES) { dev_err(device, "Maximum number of sysfs nodes reached for device\n"); mutex_unlock(&mapping->mutex); put_mapping(mapping); return -ENOMEM; } ret = device_create_file(device, &attrs[i].attr); if (ret) { dev_dbg(device, "Unable to create device entries\n"); mutex_unlock(&mapping->mutex); put_mapping(mapping); return ret; } mapping->attributes[mapping->attribute_count] = attrs[i]; ++mapping->attribute_count; } mutex_unlock(&mapping->mutex); put_mapping(mapping); return 0; } EXPORT_SYMBOL(gasket_sysfs_create_entries); void gasket_sysfs_remove_mapping(struct device *device) { struct gasket_sysfs_mapping *mapping = get_mapping(device); if (!mapping) { dev_err(device, "Attempted to remove non-existent sysfs mapping to device\n"); return; } put_mapping_n(mapping, 2); } struct gasket_dev *gasket_sysfs_get_device_data(struct device *device) { struct gasket_sysfs_mapping *mapping = get_mapping(device); if (!mapping) { dev_err(device, "device not registered\n"); return NULL; } return mapping->gasket_dev; } EXPORT_SYMBOL(gasket_sysfs_get_device_data); void gasket_sysfs_put_device_data(struct device *device, struct gasket_dev *dev) { struct gasket_sysfs_mapping *mapping = get_mapping(device); if (!mapping) return; /* See comment of put_mapping_n() for why the '2' is necessary. */ put_mapping_n(mapping, 2); } EXPORT_SYMBOL(gasket_sysfs_put_device_data); struct gasket_sysfs_attribute * gasket_sysfs_get_attr(struct device *device, struct device_attribute *attr) { int i; int num_attrs; struct gasket_sysfs_mapping *mapping = get_mapping(device); struct gasket_sysfs_attribute *attrs = NULL; if (!mapping) return NULL; attrs = mapping->attributes; num_attrs = mapping->attribute_count; for (i = 0; i < num_attrs; ++i) { if (!strcmp(attrs[i].attr.attr.name, attr->attr.name)) return &attrs[i]; } dev_err(device, "Unable to find match for device_attribute %s\n", attr->attr.name); return NULL; } EXPORT_SYMBOL(gasket_sysfs_get_attr); void gasket_sysfs_put_attr(struct device *device, struct gasket_sysfs_attribute *attr) { int i; int num_attrs; struct gasket_sysfs_mapping *mapping = get_mapping(device); struct gasket_sysfs_attribute *attrs = NULL; if (!mapping) return; attrs = mapping->attributes; num_attrs = mapping->attribute_count; for (i = 0; i < num_attrs; ++i) { if (&attrs[i] == attr) { put_mapping_n(mapping, 2); return; } } dev_err(device, "Unable to put unknown attribute: %s\n", attr->attr.attr.name); } EXPORT_SYMBOL(gasket_sysfs_put_attr); ssize_t gasket_sysfs_register_store(struct device *device, struct device_attribute *attr, const char *buf, size_t count) { ulong parsed_value = 0; struct gasket_sysfs_mapping *mapping; struct gasket_dev *gasket_dev; struct gasket_sysfs_attribute *gasket_attr; if (count < 3 || buf[0] != '0' || buf[1] != 'x') { dev_err(device, "sysfs register write format: \"0x<hex value>\"\n"); return -EINVAL; } if (kstrtoul(buf, 16, &parsed_value) != 0) { dev_err(device, "Unable to parse input as 64-bit hex value: %s\n", buf); return -EINVAL; } mapping = get_mapping(device); if (!mapping) { dev_err(device, "Device driver may have been removed\n"); return 0; } gasket_dev = mapping->gasket_dev; if (!gasket_dev) { dev_err(device, "Device driver may have been removed\n"); return 0; } gasket_attr = gasket_sysfs_get_attr(device, attr); if (!gasket_attr) { put_mapping(mapping); return count; } gasket_dev_write_64(gasket_dev, parsed_value, gasket_attr->data.bar_address.bar, gasket_attr->data.bar_address.offset); if (gasket_attr->write_callback) gasket_attr->write_callback(gasket_dev, gasket_attr, parsed_value); gasket_sysfs_put_attr(device, gasket_attr); put_mapping(mapping); return count; } EXPORT_SYMBOL(gasket_sysfs_register_store);
Information contained on this website is for historical information purposes only and does not indicate or represent copyright ownership.
Created with Cregit http://github.com/cregit/cregit
Version 2.0-RC1