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_MODE(_name, _mode) { \
68 .attr = { .name = __stringify(_name), .mode = _mode }, \
69 .show = _name##_show, \
70 }
71 #define __ATTR_RO(_name) __ATTR_RO_MODE(_name, 0444)
72 #define __ATTR_WO(_name) __ATTR(_name, 0200, NULL, _name##_store)
73 #define __ATTR_RW_MODE(_name, _mode) \
74 __ATTR(_name, _mode, _name##_show, _name##_store)
75 #define __ATTR_RW(_name) __ATTR_RW_MODE(_name, 0644)
76 #define __ATTR_NULL { .attr = { .name = NULL } }
77
78 #define ATTRIBUTE_GROUPS(_name) \
79 static struct attribute_group _name##_group = { \
80 .name = __stringify(_name), \
81 .attrs = _name##_attrs, \
82 }; \
83 static const struct attribute_group *_name##_groups[] = { \
84 &_name##_group, \
85 NULL, \
86 }
87
88 #define __BIN_ATTR(_name, _mode, _read, _write, _size) { \
89 .attr = { .name = __stringify(_name), .mode = _mode }, \
90 .read = _read, .write = _write, .size = _size, \
91 }
92 #define __BIN_ATTR_RO(_name, _size) { \
93 .attr = { .name = __stringify(_name), .mode = 0444 }, \
94 .read = _name##_read, .size = _size, \
95 }
96 #define __BIN_ATTR_WO(_name, _size) { \
97 .attr = { .name = __stringify(_name), .mode = 0200 }, \
98 .write = _name##_write, .size = _size, \
99 }
100 #define __BIN_ATTR_WR(_name, _size) { \
101 .attr = { .name = __stringify(_name), .mode = 0644 }, \
102 .read = _name##_read, .write = _name##_write, .size = _size, \
103 }
104
105 #define BIN_ATTR(_name, _mode, _read, _write, _size) \
106 struct bin_attribute bin_attr_##_name = \
107 __BIN_ATTR(_name, _mode, _read, _write, _size);
108
109 #define BIN_ATTR_RO(_name, _size) \
110 struct bin_attribute bin_attr_##_name = \
111 __BIN_ATTR_RO(_name, _size);
112
113 #define BIN_ATTR_WO(_name, _size) \
114 struct bin_attribute bin_attr_##_name = \
115 __BIN_ATTR_WO(_name, _size);
116
117 #define BIN_ATTR_WR(_name, _size) \
118 struct bin_attribute bin_attr_##_name = \
119 __BIN_ATTR_WR(_name, _size);
120
121 /*
122 * Handle our generic '\0' terminated 'C' string.
123 * Two cases:
124 * a variable string: point arg1 at it, arg2 is max length.
125 * a constant string: point arg1 at it, arg2 is zero.
126 */
127
128 static inline int
sysctl_handle_attr(SYSCTL_HANDLER_ARGS)129 sysctl_handle_attr(SYSCTL_HANDLER_ARGS)
130 {
131 struct kobject *kobj;
132 struct attribute *attr;
133 const struct sysfs_ops *ops;
134 char *buf;
135 int error;
136 ssize_t len;
137
138 kobj = arg1;
139 attr = (struct attribute *)(intptr_t)arg2;
140 if (kobj->ktype == NULL || kobj->ktype->sysfs_ops == NULL)
141 return (ENODEV);
142 buf = (char *)get_zeroed_page(GFP_KERNEL);
143 if (buf == NULL)
144 return (ENOMEM);
145 ops = kobj->ktype->sysfs_ops;
146 if (ops->show) {
147 len = ops->show(kobj, attr, buf);
148 /*
149 * It's valid to not have a 'show' so just return an
150 * empty string.
151 */
152 if (len < 0) {
153 error = -len;
154 if (error != EIO)
155 goto out;
156 buf[0] = '\0';
157 } else if (len) {
158 len--;
159 if (len >= PAGE_SIZE)
160 len = PAGE_SIZE - 1;
161 /* Trim trailing newline. */
162 buf[len] = '\0';
163 }
164 }
165
166 /* Leave one trailing byte to append a newline. */
167 error = sysctl_handle_string(oidp, buf, PAGE_SIZE - 1, req);
168 if (error != 0 || req->newptr == NULL || ops->store == NULL)
169 goto out;
170 len = strlcat(buf, "\n", PAGE_SIZE);
171 KASSERT(len < PAGE_SIZE, ("new attribute truncated"));
172 len = ops->store(kobj, attr, buf, len);
173 if (len < 0)
174 error = -len;
175 out:
176 free_page((unsigned long)buf);
177
178 return (error);
179 }
180
181 static inline int
sysfs_create_file(struct kobject * kobj,const struct attribute * attr)182 sysfs_create_file(struct kobject *kobj, const struct attribute *attr)
183 {
184 struct sysctl_oid *oid;
185
186 oid = SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(kobj->oidp), OID_AUTO,
187 attr->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE, kobj,
188 (uintptr_t)attr, sysctl_handle_attr, "A", "");
189 if (!oid) {
190 return (-ENOMEM);
191 }
192
193 return (0);
194 }
195
196 static inline struct kobject *
__sysfs_lookup_group(struct kobject * kobj,const char * group)197 __sysfs_lookup_group(struct kobject *kobj, const char *group)
198 {
199 int found;
200 struct sysctl_oid *group_oidp;
201 struct kobject *group_kobj;
202
203 found = 0;
204 if (group != NULL) {
205 SYSCTL_FOREACH(group_oidp, SYSCTL_CHILDREN(kobj->oidp)) {
206 if (strcmp(group_oidp->oid_name, group) != 0)
207 continue;
208 found = 1;
209 break;
210 }
211 } else {
212 found = 1;
213 group_oidp = kobj->oidp;
214 }
215
216 if (!found)
217 return (NULL);
218
219 group_kobj = group_oidp->oid_arg1;
220
221 return (group_kobj);
222 }
223
224 static inline int
sysfs_add_file_to_group(struct kobject * kobj,const struct attribute * attr,const char * group)225 sysfs_add_file_to_group(struct kobject *kobj,
226 const struct attribute *attr, const char *group)
227 {
228 int ret;
229 struct kobject *group_kobj;
230
231 group_kobj = __sysfs_lookup_group(kobj, group);
232 if (group_kobj == NULL)
233 return (-ENOENT);
234
235 ret = sysfs_create_file(group_kobj, attr);
236
237 return (ret);
238 }
239
240 static inline void
sysfs_remove_file(struct kobject * kobj,const struct attribute * attr)241 sysfs_remove_file(struct kobject *kobj, const struct attribute *attr)
242 {
243
244 if (kobj->oidp)
245 sysctl_remove_name(kobj->oidp, attr->name, 1, 1);
246 }
247
248 static inline void
sysfs_remove_file_from_group(struct kobject * kobj,const struct attribute * attr,const char * group)249 sysfs_remove_file_from_group(struct kobject *kobj,
250 const struct attribute *attr, const char *group)
251 {
252 struct kobject *group_kobj;
253
254 group_kobj = __sysfs_lookup_group(kobj, group);
255 if (group_kobj == NULL)
256 return;
257
258 sysfs_remove_file(group_kobj, attr);
259 }
260
261 static inline int
sysctl_handle_bin_attr(SYSCTL_HANDLER_ARGS)262 sysctl_handle_bin_attr(SYSCTL_HANDLER_ARGS)
263 {
264 struct kobject *kobj;
265 struct bin_attribute *attr;
266 char *buf;
267 int error;
268 ssize_t len;
269
270 kobj = arg1;
271 attr = (struct bin_attribute *)(intptr_t)arg2;
272 if (kobj->ktype == NULL || kobj->ktype->sysfs_ops == NULL)
273 return (ENODEV);
274 buf = (char *)get_zeroed_page(GFP_KERNEL);
275 if (buf == NULL)
276 return (ENOMEM);
277
278 if (attr->read) {
279 len = attr->read(
280 NULL, /* <-- struct file, unimplemented */
281 kobj, attr, buf, req->oldidx, PAGE_SIZE);
282 if (len < 0) {
283 error = -len;
284 if (error != EIO)
285 goto out;
286 }
287 }
288
289 error = sysctl_handle_opaque(oidp, buf, PAGE_SIZE, req);
290 if (error != 0 || req->newptr == NULL || attr->write == NULL)
291 goto out;
292
293 len = attr->write(
294 NULL, /* <-- struct file, unimplemented */
295 kobj, attr, buf, req->newidx, req->newlen);
296 if (len < 0)
297 error = -len;
298 out:
299 free_page((unsigned long)buf);
300
301 return (error);
302 }
303
304 static inline int
sysfs_create_bin_file(struct kobject * kobj,const struct bin_attribute * attr)305 sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
306 {
307 struct sysctl_oid *oid;
308 int ctlflags;
309
310 ctlflags = CTLTYPE_OPAQUE | CTLFLAG_MPSAFE;
311 if (attr->attr.mode & (S_IRUSR | S_IWUSR))
312 ctlflags |= CTLFLAG_RW;
313 else if (attr->attr.mode & S_IRUSR)
314 ctlflags |= CTLFLAG_RD;
315 else if (attr->attr.mode & S_IWUSR)
316 ctlflags |= CTLFLAG_WR;
317
318 oid = SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(kobj->oidp), OID_AUTO,
319 attr->attr.name, ctlflags, kobj,
320 (uintptr_t)attr, sysctl_handle_bin_attr, "", "");
321 if (oid == NULL)
322 return (-ENOMEM);
323
324 return (0);
325 }
326
327 static inline void
sysfs_remove_bin_file(struct kobject * kobj,const struct bin_attribute * attr)328 sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
329 {
330
331 if (kobj->oidp)
332 sysctl_remove_name(kobj->oidp, attr->attr.name, 1, 1);
333 }
334
335 static inline int
sysfs_create_link(struct kobject * kobj __unused,struct kobject * target __unused,const char * name __unused)336 sysfs_create_link(struct kobject *kobj __unused,
337 struct kobject *target __unused, const char *name __unused)
338 {
339 /* TODO */
340
341 return (0);
342 }
343
344 static inline void
sysfs_remove_link(struct kobject * kobj,const char * name)345 sysfs_remove_link(struct kobject *kobj, const char *name)
346 {
347 /* TODO (along with sysfs_create_link) */
348 }
349
350 static inline int
sysfs_create_files(struct kobject * kobj,const struct attribute * const * attrs)351 sysfs_create_files(struct kobject *kobj, const struct attribute * const *attrs)
352 {
353 int error = 0;
354 int i;
355
356 for (i = 0; attrs[i] && !error; i++)
357 error = sysfs_create_file(kobj, attrs[i]);
358 while (error && --i >= 0)
359 sysfs_remove_file(kobj, attrs[i]);
360
361 return (error);
362 }
363
364 static inline void
sysfs_remove_files(struct kobject * kobj,const struct attribute * const * attrs)365 sysfs_remove_files(struct kobject *kobj, const struct attribute * const *attrs)
366 {
367 int i;
368
369 for (i = 0; attrs[i]; i++)
370 sysfs_remove_file(kobj, attrs[i]);
371 }
372
373 static inline int
sysfs_create_group(struct kobject * kobj,const struct attribute_group * grp)374 sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
375 {
376 struct attribute **attr;
377 struct bin_attribute **bin_attr;
378 struct sysctl_oid *oidp;
379
380 /* Don't create the group node if grp->name is undefined. */
381 if (grp->name)
382 oidp = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->oidp),
383 OID_AUTO, grp->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, grp->name);
384 else
385 oidp = kobj->oidp;
386 for (attr = grp->attrs; attr != NULL && *attr != NULL; attr++) {
387 SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(oidp), OID_AUTO,
388 (*attr)->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
389 kobj, (uintptr_t)*attr, sysctl_handle_attr, "A", "");
390 }
391 for (bin_attr = grp->bin_attrs;
392 bin_attr != NULL && *bin_attr != NULL;
393 bin_attr++) {
394 SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(oidp), OID_AUTO,
395 (*bin_attr)->attr.name,
396 CTLTYPE_OPAQUE|CTLFLAG_RW|CTLFLAG_MPSAFE,
397 kobj, (uintptr_t)*bin_attr, sysctl_handle_bin_attr, "", "");
398 }
399
400 return (0);
401 }
402
403 static inline void
sysfs_remove_group(struct kobject * kobj,const struct attribute_group * grp)404 sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp)
405 {
406
407 if (kobj->oidp)
408 sysctl_remove_name(kobj->oidp, grp->name, 1, 1);
409 }
410
411 static inline int
sysfs_create_groups(struct kobject * kobj,const struct attribute_group ** grps)412 sysfs_create_groups(struct kobject *kobj, const struct attribute_group **grps)
413 {
414 int error = 0;
415 int i;
416
417 if (grps == NULL)
418 goto done;
419 for (i = 0; grps[i] && !error; i++)
420 error = sysfs_create_group(kobj, grps[i]);
421 while (error && --i >= 0)
422 sysfs_remove_group(kobj, grps[i]);
423 done:
424 return (error);
425 }
426
427 static inline void
sysfs_remove_groups(struct kobject * kobj,const struct attribute_group ** grps)428 sysfs_remove_groups(struct kobject *kobj, const struct attribute_group **grps)
429 {
430 int i;
431
432 if (grps == NULL)
433 return;
434 for (i = 0; grps[i]; i++)
435 sysfs_remove_group(kobj, grps[i]);
436 }
437
438 static inline int
sysfs_merge_group(struct kobject * kobj,const struct attribute_group * grp)439 sysfs_merge_group(struct kobject *kobj, const struct attribute_group *grp)
440 {
441
442 /* Really expected behavior is to return failure if group exists. */
443 return (sysfs_create_group(kobj, grp));
444 }
445
446 static inline void
sysfs_unmerge_group(struct kobject * kobj,const struct attribute_group * grp)447 sysfs_unmerge_group(struct kobject *kobj, const struct attribute_group *grp)
448 {
449 struct attribute **attr;
450 struct bin_attribute **bin_attr;
451 struct sysctl_oid *oidp;
452
453 SYSCTL_FOREACH(oidp, SYSCTL_CHILDREN(kobj->oidp)) {
454 if (strcmp(oidp->oid_name, grp->name) != 0)
455 continue;
456 for (attr = grp->attrs; attr != NULL && *attr != NULL; attr++) {
457 sysctl_remove_name(oidp, (*attr)->name, 1, 1);
458 }
459 for (bin_attr = grp->bin_attrs;
460 bin_attr != NULL && *bin_attr != NULL;
461 bin_attr++) {
462 sysctl_remove_name(oidp, (*bin_attr)->attr.name, 1, 1);
463 }
464 }
465 }
466
467 static inline int
sysfs_create_dir(struct kobject * kobj)468 sysfs_create_dir(struct kobject *kobj)
469 {
470 struct sysctl_oid *oid;
471
472 oid = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->parent->oidp),
473 OID_AUTO, kobj->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, kobj->name);
474 if (!oid) {
475 return (-ENOMEM);
476 }
477 kobj->oidp = oid;
478
479 return (0);
480 }
481
482 static inline void
sysfs_remove_dir(struct kobject * kobj)483 sysfs_remove_dir(struct kobject *kobj)
484 {
485
486 if (kobj->oidp == NULL)
487 return;
488 sysctl_remove_oid(kobj->oidp, 1, 1);
489 }
490
491 static inline bool
sysfs_streq(const char * s1,const char * s2)492 sysfs_streq(const char *s1, const char *s2)
493 {
494 int l1, l2;
495
496 l1 = strlen(s1);
497 l2 = strlen(s2);
498
499 if (l1 != 0 && s1[l1-1] == '\n')
500 l1--;
501 if (l2 != 0 && s2[l2-1] == '\n')
502 l2--;
503
504 return (l1 == l2 && strncmp(s1, s2, l1) == 0);
505 }
506
507 static inline int
sysfs_emit(char * buf,const char * fmt,...)508 sysfs_emit(char *buf, const char *fmt, ...)
509 {
510 va_list args;
511 int i;
512
513 if (!buf || offset_in_page(buf)) {
514 pr_warn("invalid sysfs_emit: buf:%p\n", buf);
515 return (0);
516 }
517
518 va_start(args, fmt);
519 i = vscnprintf(buf, PAGE_SIZE, fmt, args);
520 va_end(args);
521
522 return (i);
523 }
524
525 static inline int
sysfs_emit_at(char * buf,int at,const char * fmt,...)526 sysfs_emit_at(char *buf, int at, const char *fmt, ...)
527 {
528 va_list args;
529 int i;
530
531 if (!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE) {
532 pr_warn("invalid sysfs_emit: buf:%p at:%d\n", buf, at);
533 return (0);
534 }
535
536 va_start(args, fmt);
537 i = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
538 va_end(args);
539
540 return (i);
541 }
542
543 static inline int
_sysfs_match_string(const char * const * a,size_t l,const char * s)544 _sysfs_match_string(const char * const *a, size_t l, const char *s)
545 {
546 const char *p;
547 int i;
548
549 for (i = 0; i < l; i++) {
550 p = a[i];
551 if (p == NULL)
552 break;
553 if (sysfs_streq(p, s))
554 return (i);
555 }
556
557 return (-ENOENT);
558 }
559 #define sysfs_match_string(a, s) _sysfs_match_string(a, ARRAY_SIZE(a), s)
560
561 #define sysfs_attr_init(attr) do {} while(0)
562
563 #endif /* _LINUXKPI_LINUX_SYSFS_H_ */
564