| // 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); |