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