aboutsummaryrefslogtreecommitdiff
path: root/drivers/staging/gasket/gasket_sysfs.c
blob: fc45f0d13e87d2cfd92eda9f2246ede16b6487f0 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
// 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);