diff options
Diffstat (limited to 'fs/sysfs/file.c')
-rw-r--r-- | fs/sysfs/file.c | 109 |
1 files changed, 108 insertions, 1 deletions
diff --git a/fs/sysfs/file.c b/fs/sysfs/file.c index 3c91a57a1ed2..b13ba94cf8ac 100644 --- a/fs/sysfs/file.c +++ b/fs/sysfs/file.c | |||
@@ -49,6 +49,22 @@ static struct sysfs_ops subsys_sysfs_ops = { | |||
49 | .store = subsys_attr_store, | 49 | .store = subsys_attr_store, |
50 | }; | 50 | }; |
51 | 51 | ||
52 | /* | ||
53 | * There's one sysfs_buffer for each open file and one | ||
54 | * sysfs_open_dirent for each sysfs_dirent with one or more open | ||
55 | * files. | ||
56 | * | ||
57 | * filp->private_data points to sysfs_buffer and | ||
58 | * sysfs_dirent->s_attr.open points to sysfs_open_dirent. s_attr.open | ||
59 | * is protected by sysfs_open_dirent_lock. | ||
60 | */ | ||
61 | static spinlock_t sysfs_open_dirent_lock = SPIN_LOCK_UNLOCKED; | ||
62 | |||
63 | struct sysfs_open_dirent { | ||
64 | atomic_t refcnt; | ||
65 | struct list_head buffers; /* goes through sysfs_buffer.list */ | ||
66 | }; | ||
67 | |||
52 | struct sysfs_buffer { | 68 | struct sysfs_buffer { |
53 | size_t count; | 69 | size_t count; |
54 | loff_t pos; | 70 | loff_t pos; |
@@ -57,6 +73,7 @@ struct sysfs_buffer { | |||
57 | struct mutex mutex; | 73 | struct mutex mutex; |
58 | int needs_read_fill; | 74 | int needs_read_fill; |
59 | int event; | 75 | int event; |
76 | struct list_head list; | ||
60 | }; | 77 | }; |
61 | 78 | ||
62 | /** | 79 | /** |
@@ -237,6 +254,86 @@ sysfs_write_file(struct file *file, const char __user *buf, size_t count, loff_t | |||
237 | return len; | 254 | return len; |
238 | } | 255 | } |
239 | 256 | ||
257 | /** | ||
258 | * sysfs_get_open_dirent - get or create sysfs_open_dirent | ||
259 | * @sd: target sysfs_dirent | ||
260 | * @buffer: sysfs_buffer for this instance of open | ||
261 | * | ||
262 | * If @sd->s_attr.open exists, increment its reference count; | ||
263 | * otherwise, create one. @buffer is chained to the buffers | ||
264 | * list. | ||
265 | * | ||
266 | * LOCKING: | ||
267 | * Kernel thread context (may sleep). | ||
268 | * | ||
269 | * RETURNS: | ||
270 | * 0 on success, -errno on failure. | ||
271 | */ | ||
272 | static int sysfs_get_open_dirent(struct sysfs_dirent *sd, | ||
273 | struct sysfs_buffer *buffer) | ||
274 | { | ||
275 | struct sysfs_open_dirent *od, *new_od = NULL; | ||
276 | |||
277 | retry: | ||
278 | spin_lock(&sysfs_open_dirent_lock); | ||
279 | |||
280 | if (!sd->s_attr.open && new_od) { | ||
281 | sd->s_attr.open = new_od; | ||
282 | new_od = NULL; | ||
283 | } | ||
284 | |||
285 | od = sd->s_attr.open; | ||
286 | if (od) { | ||
287 | atomic_inc(&od->refcnt); | ||
288 | list_add_tail(&buffer->list, &od->buffers); | ||
289 | } | ||
290 | |||
291 | spin_unlock(&sysfs_open_dirent_lock); | ||
292 | |||
293 | if (od) { | ||
294 | kfree(new_od); | ||
295 | return 0; | ||
296 | } | ||
297 | |||
298 | /* not there, initialize a new one and retry */ | ||
299 | new_od = kmalloc(sizeof(*new_od), GFP_KERNEL); | ||
300 | if (!new_od) | ||
301 | return -ENOMEM; | ||
302 | |||
303 | atomic_set(&new_od->refcnt, 0); | ||
304 | INIT_LIST_HEAD(&new_od->buffers); | ||
305 | goto retry; | ||
306 | } | ||
307 | |||
308 | /** | ||
309 | * sysfs_put_open_dirent - put sysfs_open_dirent | ||
310 | * @sd: target sysfs_dirent | ||
311 | * @buffer: associated sysfs_buffer | ||
312 | * | ||
313 | * Put @sd->s_attr.open and unlink @buffer from the buffers list. | ||
314 | * If reference count reaches zero, disassociate and free it. | ||
315 | * | ||
316 | * LOCKING: | ||
317 | * None. | ||
318 | */ | ||
319 | static void sysfs_put_open_dirent(struct sysfs_dirent *sd, | ||
320 | struct sysfs_buffer *buffer) | ||
321 | { | ||
322 | struct sysfs_open_dirent *od = sd->s_attr.open; | ||
323 | |||
324 | spin_lock(&sysfs_open_dirent_lock); | ||
325 | |||
326 | list_del(&buffer->list); | ||
327 | if (atomic_dec_and_test(&od->refcnt)) | ||
328 | sd->s_attr.open = NULL; | ||
329 | else | ||
330 | od = NULL; | ||
331 | |||
332 | spin_unlock(&sysfs_open_dirent_lock); | ||
333 | |||
334 | kfree(od); | ||
335 | } | ||
336 | |||
240 | static int sysfs_open_file(struct inode *inode, struct file *file) | 337 | static int sysfs_open_file(struct inode *inode, struct file *file) |
241 | { | 338 | { |
242 | struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; | 339 | struct sysfs_dirent *attr_sd = file->f_path.dentry->d_fsdata; |
@@ -298,19 +395,29 @@ static int sysfs_open_file(struct inode *inode, struct file *file) | |||
298 | buffer->ops = ops; | 395 | buffer->ops = ops; |
299 | file->private_data = buffer; | 396 | file->private_data = buffer; |
300 | 397 | ||
398 | /* make sure we have open dirent struct */ | ||
399 | error = sysfs_get_open_dirent(attr_sd, buffer); | ||
400 | if (error) | ||
401 | goto err_free; | ||
402 | |||
301 | /* open succeeded, put active references */ | 403 | /* open succeeded, put active references */ |
302 | sysfs_put_active_two(attr_sd); | 404 | sysfs_put_active_two(attr_sd); |
303 | return 0; | 405 | return 0; |
304 | 406 | ||
407 | err_free: | ||
408 | kfree(buffer); | ||
305 | err_out: | 409 | err_out: |
306 | sysfs_put_active_two(attr_sd); | 410 | sysfs_put_active_two(attr_sd); |
307 | return error; | 411 | return error; |
308 | } | 412 | } |
309 | 413 | ||
310 | static int sysfs_release(struct inode * inode, struct file * filp) | 414 | static int sysfs_release(struct inode *inode, struct file *filp) |
311 | { | 415 | { |
416 | struct sysfs_dirent *sd = filp->f_path.dentry->d_fsdata; | ||
312 | struct sysfs_buffer *buffer = filp->private_data; | 417 | struct sysfs_buffer *buffer = filp->private_data; |
313 | 418 | ||
419 | sysfs_put_open_dirent(sd, buffer); | ||
420 | |||
314 | if (buffer->page) | 421 | if (buffer->page) |
315 | free_page((unsigned long)buffer->page); | 422 | free_page((unsigned long)buffer->page); |
316 | kfree(buffer); | 423 | kfree(buffer); |