diff options
| -rw-r--r-- | fs/sysfs/file.c | 109 | ||||
| -rw-r--r-- | fs/sysfs/sysfs.h | 3 |
2 files changed, 111 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); |
diff --git a/fs/sysfs/sysfs.h b/fs/sysfs/sysfs.h index 42b0327d1624..3adce7d5e4f7 100644 --- a/fs/sysfs/sysfs.h +++ b/fs/sysfs/sysfs.h | |||
| @@ -1,3 +1,5 @@ | |||
| 1 | struct sysfs_open_dirent; | ||
| 2 | |||
| 1 | /* type-specific structures for sysfs_dirent->s_* union members */ | 3 | /* type-specific structures for sysfs_dirent->s_* union members */ |
| 2 | struct sysfs_elem_dir { | 4 | struct sysfs_elem_dir { |
| 3 | struct kobject *kobj; | 5 | struct kobject *kobj; |
| @@ -11,6 +13,7 @@ struct sysfs_elem_symlink { | |||
| 11 | 13 | ||
| 12 | struct sysfs_elem_attr { | 14 | struct sysfs_elem_attr { |
| 13 | struct attribute *attr; | 15 | struct attribute *attr; |
| 16 | struct sysfs_open_dirent *open; | ||
| 14 | }; | 17 | }; |
| 15 | 18 | ||
| 16 | struct sysfs_elem_bin_attr { | 19 | struct sysfs_elem_bin_attr { |
