1 /*-
2 * Copyright (c) 2010 Isilon Systems, Inc.
3 * Copyright (c) 2010 iX Systems, Inc.
4 * Copyright (c) 2010 Panasas, Inc.
5 * Copyright (c) 2013, 2014 Mellanox Technologies, Ltd.
6 * All rights reserved.
7 *
8 * Redistribution and use in source and binary forms, with or without
9 * modification, are permitted provided that the following conditions
10 * are met:
11 * 1. Redistributions of source code must retain the above copyright
12 * notice unmodified, this list of conditions, and the following
13 * disclaimer.
14 * 2. Redistributions in binary form must reproduce the above copyright
15 * notice, this list of conditions and the following disclaimer in the
16 * documentation and/or other materials provided with the distribution.
17 *
18 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
19 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
20 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
21 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
22 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
23 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
24 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
25 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
26 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
27 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
28 */
29 #ifndef _LINUXKPI_LINUX_SYSFS_H_
30 #define _LINUXKPI_LINUX_SYSFS_H_
31
32 #include <sys/types.h>
33 #include <sys/sysctl.h>
34 #include <sys/errno.h>
35
36 #include <linux/kobject.h>
37 #include <linux/stringify.h>
38 #include <linux/mm.h>
39
40 struct sysfs_ops {
41 ssize_t (*show)(struct kobject *, struct attribute *, char *);
42 ssize_t (*store)(struct kobject *, struct attribute *, const char *,
43 size_t);
44 };
45
46 struct bin_attribute {
47 struct attribute attr;
48 size_t size;
49 ssize_t (*read)(struct linux_file *, struct kobject *,
50 struct bin_attribute *, char *, loff_t, size_t);
51 ssize_t (*write)(struct linux_file *, struct kobject *,
52 struct bin_attribute *, char *, loff_t, size_t);
53 };
54
55 struct attribute_group {
56 const char *name;
57 mode_t (*is_visible)(struct kobject *,
58 struct attribute *, int);
59 struct attribute **attrs;
60 struct bin_attribute **bin_attrs;
61 };
62
63 #define __ATTR(_name, _mode, _show, _store) { \
64 .attr = { .name = __stringify(_name), .mode = _mode }, \
65 .show = _show, .store = _store, \
66 }
67 #define __ATTR_RO(_name) { \
68 .attr = { .name = __stringify(_name), .mode = 0444 }, \
69 .show = _name##_show, \
70 }
71 #define __ATTR_WO(_name) __ATTR(_name, 0200, NULL, _name##_store)
72 #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
73 #define __ATTR_NULL { .attr = { .name = NULL } }
74
75 #define ATTRIBUTE_GROUPS(_name) \
76 static struct attribute_group _name##_group = { \
77 .name = __stringify(_name), \
78 .attrs = _name##_attrs, \
79 }; \
80 static const struct attribute_group *_name##_groups[] = { \
81 &_name##_group, \
82 NULL, \
83 }
84
85 #define __BIN_ATTR(_name, _mode, _read, _write, _size) { \
86 .attr = { .name = __stringify(_name), .mode = _mode }, \
87 .read = _read, .write = _write, .size = _size, \
88 }
89 #define __BIN_ATTR_RO(_name, _size) { \
90 .attr = { .name = __stringify(_name), .mode = 0444 }, \
91 .read = _name##_read, .size = _size, \
92 }
93 #define __BIN_ATTR_WO(_name, _size) { \
94 .attr = { .name = __stringify(_name), .mode = 0200 }, \
95 .write = _name##_write, .size = _size, \
96 }
97 #define __BIN_ATTR_WR(_name, _size) { \
98 .attr = { .name = __stringify(_name), .mode = 0644 }, \
99 .read = _name##_read, .write = _name##_write, .size = _size, \
100 }
101
102 #define BIN_ATTR(_name, _mode, _read, _write, _size) \
103 struct bin_attribute bin_attr_##_name = \
104 __BIN_ATTR(_name, _mode, _read, _write, _size);
105
106 #define BIN_ATTR_RO(_name, _size) \
107 struct bin_attribute bin_attr_##_name = \
108 __BIN_ATTR_RO(_name, _size);
109
110 #define BIN_ATTR_WO(_name, _size) \
111 struct bin_attribute bin_attr_##_name = \
112 __BIN_ATTR_WO(_name, _size);
113
114 #define BIN_ATTR_WR(_name, _size) \
115 struct bin_attribute bin_attr_##_name = \
116 __BIN_ATTR_WR(_name, _size);
117
118 /*
119 * Handle our generic '\0' terminated 'C' string.
120 * Two cases:
121 * a variable string: point arg1 at it, arg2 is max length.
122 * a constant string: point arg1 at it, arg2 is zero.
123 */
124
125 static inline int
sysctl_handle_attr(SYSCTL_HANDLER_ARGS)126 sysctl_handle_attr(SYSCTL_HANDLER_ARGS)
127 {
128 struct kobject *kobj;
129 struct attribute *attr;
130 const struct sysfs_ops *ops;
131 char *buf;
132 int error;
133 ssize_t len;
134
135 kobj = arg1;
136 attr = (struct attribute *)(intptr_t)arg2;
137 if (kobj->ktype == NULL || kobj->ktype->sysfs_ops == NULL)
138 return (ENODEV);
139 buf = (char *)get_zeroed_page(GFP_KERNEL);
140 if (buf == NULL)
141 return (ENOMEM);
142 ops = kobj->ktype->sysfs_ops;
143 if (ops->show) {
144 len = ops->show(kobj, attr, buf);
145 /*
146 * It's valid to not have a 'show' so just return an
147 * empty string.
148 */
149 if (len < 0) {
150 error = -len;
151 if (error != EIO)
152 goto out;
153 buf[0] = '\0';
154 } else if (len) {
155 len--;
156 if (len >= PAGE_SIZE)
157 len = PAGE_SIZE - 1;
158 /* Trim trailing newline. */
159 buf[len] = '\0';
160 }
161 }
162
163 /* Leave one trailing byte to append a newline. */
164 error = sysctl_handle_string(oidp, buf, PAGE_SIZE - 1, req);
165 if (error != 0 || req->newptr == NULL || ops->store == NULL)
166 goto out;
167 len = strlcat(buf, "\n", PAGE_SIZE);
168 KASSERT(len < PAGE_SIZE, ("new attribute truncated"));
169 len = ops->store(kobj, attr, buf, len);
170 if (len < 0)
171 error = -len;
172 out:
173 free_page((unsigned long)buf);
174
175 return (error);
176 }
177
178 static inline int
sysfs_create_file(struct kobject * kobj,const struct attribute * attr)179 sysfs_create_file(struct kobject *kobj, const struct attribute *attr)
180 {
181 struct sysctl_oid *oid;
182
183 oid = SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(kobj->oidp), OID_AUTO,
184 attr->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE, kobj,
185 (uintptr_t)attr, sysctl_handle_attr, "A", "");
186 if (!oid) {
187 return (-ENOMEM);
188 }
189
190 return (0);
191 }
192
193 static inline struct kobject *
__sysfs_lookup_group(struct kobject * kobj,const char * group)194 __sysfs_lookup_group(struct kobject *kobj, const char *group)
195 {
196 int found;
197 struct sysctl_oid *group_oidp;
198 struct kobject *group_kobj;
199
200 found = 0;
201 if (group != NULL) {
202 SYSCTL_FOREACH(group_oidp, SYSCTL_CHILDREN(kobj->oidp)) {
203 if (strcmp(group_oidp->oid_name, group) != 0)
204 continue;
205 found = 1;
206 break;
207 }
208 } else {
209 found = 1;
210 group_oidp = kobj->oidp;
211 }
212
213 if (!found)
214 return (NULL);
215
216 group_kobj = group_oidp->oid_arg1;
217
218 return (group_kobj);
219 }
220
221 static inline int
sysfs_add_file_to_group(struct kobject * kobj,const struct attribute * attr,const char * group)222 sysfs_add_file_to_group(struct kobject *kobj,
223 const struct attribute *attr, const char *group)
224 {
225 int ret;
226 struct kobject *group_kobj;
227
228 group_kobj = __sysfs_lookup_group(kobj, group);
229 if (group_kobj == NULL)
230 return (-ENOENT);
231
232 ret = sysfs_create_file(group_kobj, attr);
233
234 return (ret);
235 }
236
237 static inline void
sysfs_remove_file(struct kobject * kobj,const struct attribute * attr)238 sysfs_remove_file(struct kobject *kobj, const struct attribute *attr)
239 {
240
241 if (kobj->oidp)
242 sysctl_remove_name(kobj->oidp, attr->name, 1, 1);
243 }
244
245 static inline void
sysfs_remove_file_from_group(struct kobject * kobj,const struct attribute * attr,const char * group)246 sysfs_remove_file_from_group(struct kobject *kobj,
247 const struct attribute *attr, const char *group)
248 {
249 struct kobject *group_kobj;
250
251 group_kobj = __sysfs_lookup_group(kobj, group);
252 if (group_kobj == NULL)
253 return;
254
255 sysfs_remove_file(group_kobj, attr);
256 }
257
258 static inline int
sysctl_handle_bin_attr(SYSCTL_HANDLER_ARGS)259 sysctl_handle_bin_attr(SYSCTL_HANDLER_ARGS)
260 {
261 struct kobject *kobj;
262 struct bin_attribute *attr;
263 char *buf;
264 int error;
265 ssize_t len;
266
267 kobj = arg1;
268 attr = (struct bin_attribute *)(intptr_t)arg2;
269 if (kobj->ktype == NULL || kobj->ktype->sysfs_ops == NULL)
270 return (ENODEV);
271 buf = (char *)get_zeroed_page(GFP_KERNEL);
272 if (buf == NULL)
273 return (ENOMEM);
274
275 if (attr->read) {
276 len = attr->read(
277 NULL, /* <-- struct file, unimplemented */
278 kobj, attr, buf, req->oldidx, PAGE_SIZE);
279 if (len < 0) {
280 error = -len;
281 if (error != EIO)
282 goto out;
283 }
284 }
285
286 error = sysctl_handle_opaque(oidp, buf, PAGE_SIZE, req);
287 if (error != 0 || req->newptr == NULL || attr->write == NULL)
288 goto out;
289
290 len = attr->write(
291 NULL, /* <-- struct file, unimplemented */
292 kobj, attr, buf, req->newidx, req->newlen);
293 if (len < 0)
294 error = -len;
295 out:
296 free_page((unsigned long)buf);
297
298 return (error);
299 }
300
301 static inline int
sysfs_create_bin_file(struct kobject * kobj,const struct bin_attribute * attr)302 sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
303 {
304 struct sysctl_oid *oid;
305 int ctlflags;
306
307 ctlflags = CTLTYPE_OPAQUE | CTLFLAG_MPSAFE;
308 if (attr->attr.mode & (S_IRUSR | S_IWUSR))
309 ctlflags |= CTLFLAG_RW;
310 else if (attr->attr.mode & S_IRUSR)
311 ctlflags |= CTLFLAG_RD;
312 else if (attr->attr.mode & S_IWUSR)
313 ctlflags |= CTLFLAG_WR;
314
315 oid = SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(kobj->oidp), OID_AUTO,
316 attr->attr.name, ctlflags, kobj,
317 (uintptr_t)attr, sysctl_handle_bin_attr, "", "");
318 if (oid == NULL)
319 return (-ENOMEM);
320
321 return (0);
322 }
323
324 static inline void
sysfs_remove_bin_file(struct kobject * kobj,const struct bin_attribute * attr)325 sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
326 {
327
328 if (kobj->oidp)
329 sysctl_remove_name(kobj->oidp, attr->attr.name, 1, 1);
330 }
331
332 static inline int
sysfs_create_link(struct kobject * kobj __unused,struct kobject * target __unused,const char * name __unused)333 sysfs_create_link(struct kobject *kobj __unused,
334 struct kobject *target __unused, const char *name __unused)
335 {
336 /* TODO */
337
338 return (0);
339 }
340
341 static inline void
sysfs_remove_link(struct kobject * kobj,const char * name)342 sysfs_remove_link(struct kobject *kobj, const char *name)
343 {
344 /* TODO (along with sysfs_create_link) */
345 }
346
347 static inline int
sysfs_create_files(struct kobject * kobj,const struct attribute * const * attrs)348 sysfs_create_files(struct kobject *kobj, const struct attribute * const *attrs)
349 {
350 int error = 0;
351 int i;
352
353 for (i = 0; attrs[i] && !error; i++)
354 error = sysfs_create_file(kobj, attrs[i]);
355 while (error && --i >= 0)
356 sysfs_remove_file(kobj, attrs[i]);
357
358 return (error);
359 }
360
361 static inline void
sysfs_remove_files(struct kobject * kobj,const struct attribute * const * attrs)362 sysfs_remove_files(struct kobject *kobj, const struct attribute * const *attrs)
363 {
364 int i;
365
366 for (i = 0; attrs[i]; i++)
367 sysfs_remove_file(kobj, attrs[i]);
368 }
369
370 static inline int
sysfs_create_group(struct kobject * kobj,const struct attribute_group * grp)371 sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
372 {
373 struct attribute **attr;
374 struct bin_attribute **bin_attr;
375 struct sysctl_oid *oidp;
376
377 /* Don't create the group node if grp->name is undefined. */
378 if (grp->name)
379 oidp = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->oidp),
380 OID_AUTO, grp->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, grp->name);
381 else
382 oidp = kobj->oidp;
383 for (attr = grp->attrs; attr != NULL && *attr != NULL; attr++) {
384 SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(oidp), OID_AUTO,
385 (*attr)->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
386 kobj, (uintptr_t)*attr, sysctl_handle_attr, "A", "");
387 }
388 for (bin_attr = grp->bin_attrs;
389 bin_attr != NULL && *bin_attr != NULL;
390 bin_attr++) {
391 SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(oidp), OID_AUTO,
392 (*bin_attr)->attr.name,
393 CTLTYPE_OPAQUE|CTLFLAG_RW|CTLFLAG_MPSAFE,
394 kobj, (uintptr_t)*bin_attr, sysctl_handle_bin_attr, "", "");
395 }
396
397 return (0);
398 }
399
400 static inline void
sysfs_remove_group(struct kobject * kobj,const struct attribute_group * grp)401 sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp)
402 {
403
404 if (kobj->oidp)
405 sysctl_remove_name(kobj->oidp, grp->name, 1, 1);
406 }
407
408 static inline int
sysfs_create_groups(struct kobject * kobj,const struct attribute_group ** grps)409 sysfs_create_groups(struct kobject *kobj, const struct attribute_group **grps)
410 {
411 int error = 0;
412 int i;
413
414 if (grps == NULL)
415 goto done;
416 for (i = 0; grps[i] && !error; i++)
417 error = sysfs_create_group(kobj, grps[i]);
418 while (error && --i >= 0)
419 sysfs_remove_group(kobj, grps[i]);
420 done:
421 return (error);
422 }
423
424 static inline void
sysfs_remove_groups(struct kobject * kobj,const struct attribute_group ** grps)425 sysfs_remove_groups(struct kobject *kobj, const struct attribute_group **grps)
426 {
427 int i;
428
429 if (grps == NULL)
430 return;
431 for (i = 0; grps[i]; i++)
432 sysfs_remove_group(kobj, grps[i]);
433 }
434
435 static inline int
sysfs_merge_group(struct kobject * kobj,const struct attribute_group * grp)436 sysfs_merge_group(struct kobject *kobj, const struct attribute_group *grp)
437 {
438
439 /* Really expected behavior is to return failure if group exists. */
440 return (sysfs_create_group(kobj, grp));
441 }
442
443 static inline void
sysfs_unmerge_group(struct kobject * kobj,const struct attribute_group * grp)444 sysfs_unmerge_group(struct kobject *kobj, const struct attribute_group *grp)
445 {
446 struct attribute **attr;
447 struct bin_attribute **bin_attr;
448 struct sysctl_oid *oidp;
449
450 SYSCTL_FOREACH(oidp, SYSCTL_CHILDREN(kobj->oidp)) {
451 if (strcmp(oidp->oid_name, grp->name) != 0)
452 continue;
453 for (attr = grp->attrs; attr != NULL && *attr != NULL; attr++) {
454 sysctl_remove_name(oidp, (*attr)->name, 1, 1);
455 }
456 for (bin_attr = grp->bin_attrs;
457 bin_attr != NULL && *bin_attr != NULL;
458 bin_attr++) {
459 sysctl_remove_name(oidp, (*bin_attr)->attr.name, 1, 1);
460 }
461 }
462 }
463
464 static inline int
sysfs_create_dir(struct kobject * kobj)465 sysfs_create_dir(struct kobject *kobj)
466 {
467 struct sysctl_oid *oid;
468
469 oid = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->parent->oidp),
470 OID_AUTO, kobj->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, kobj->name);
471 if (!oid) {
472 return (-ENOMEM);
473 }
474 kobj->oidp = oid;
475
476 return (0);
477 }
478
479 static inline void
sysfs_remove_dir(struct kobject * kobj)480 sysfs_remove_dir(struct kobject *kobj)
481 {
482
483 if (kobj->oidp == NULL)
484 return;
485 sysctl_remove_oid(kobj->oidp, 1, 1);
486 }
487
488 static inline bool
sysfs_streq(const char * s1,const char * s2)489 sysfs_streq(const char *s1, const char *s2)
490 {
491 int l1, l2;
492
493 l1 = strlen(s1);
494 l2 = strlen(s2);
495
496 if (l1 != 0 && s1[l1-1] == '\n')
497 l1--;
498 if (l2 != 0 && s2[l2-1] == '\n')
499 l2--;
500
501 return (l1 == l2 && strncmp(s1, s2, l1) == 0);
502 }
503
504 static inline int
sysfs_emit(char * buf,const char * fmt,...)505 sysfs_emit(char *buf, const char *fmt, ...)
506 {
507 va_list args;
508 int i;
509
510 if (!buf || offset_in_page(buf)) {
511 pr_warn("invalid sysfs_emit: buf:%p\n", buf);
512 return (0);
513 }
514
515 va_start(args, fmt);
516 i = vscnprintf(buf, PAGE_SIZE, fmt, args);
517 va_end(args);
518
519 return (i);
520 }
521
522 static inline int
sysfs_emit_at(char * buf,int at,const char * fmt,...)523 sysfs_emit_at(char *buf, int at, const char *fmt, ...)
524 {
525 va_list args;
526 int i;
527
528 if (!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE) {
529 pr_warn("invalid sysfs_emit: buf:%p at:%d\n", buf, at);
530 return (0);
531 }
532
533 va_start(args, fmt);
534 i = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
535 va_end(args);
536
537 return (i);
538 }
539
540 static inline int
_sysfs_match_string(const char * const * a,size_t l,const char * s)541 _sysfs_match_string(const char * const *a, size_t l, const char *s)
542 {
543 const char *p;
544 int i;
545
546 for (i = 0; i < l; i++) {
547 p = a[i];
548 if (p == NULL)
549 break;
550 if (sysfs_streq(p, s))
551 return (i);
552 }
553
554 return (-ENOENT);
555 }
556 #define sysfs_match_string(a, s) _sysfs_match_string(a, ARRAY_SIZE(a), s)
557
558 #define sysfs_attr_init(attr) do {} while(0)
559
560 #endif /* _LINUXKPI_LINUX_SYSFS_H_ */
561