blob: fc45f0d13e87d2cfd92eda9f2246ede16b6487f0 [file] [log] [blame]
// 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);