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 void
sysfs_remove_file(struct kobject * kobj,const struct attribute * attr)193 sysfs_remove_file(struct kobject *kobj, const struct attribute *attr)
194 {
195
196 if (kobj->oidp)
197 sysctl_remove_name(kobj->oidp, attr->name, 1, 1);
198 }
199
200 static inline int
sysctl_handle_bin_attr(SYSCTL_HANDLER_ARGS)201 sysctl_handle_bin_attr(SYSCTL_HANDLER_ARGS)
202 {
203 struct kobject *kobj;
204 struct bin_attribute *attr;
205 char *buf;
206 int error;
207 ssize_t len;
208
209 kobj = arg1;
210 attr = (struct bin_attribute *)(intptr_t)arg2;
211 if (kobj->ktype == NULL || kobj->ktype->sysfs_ops == NULL)
212 return (ENODEV);
213 buf = (char *)get_zeroed_page(GFP_KERNEL);
214 if (buf == NULL)
215 return (ENOMEM);
216
217 if (attr->read) {
218 len = attr->read(
219 NULL, /* <-- struct file, unimplemented */
220 kobj, attr, buf, req->oldidx, PAGE_SIZE);
221 if (len < 0) {
222 error = -len;
223 if (error != EIO)
224 goto out;
225 }
226 }
227
228 error = sysctl_handle_opaque(oidp, buf, PAGE_SIZE, req);
229 if (error != 0 || req->newptr == NULL || attr->write == NULL)
230 goto out;
231
232 len = attr->write(
233 NULL, /* <-- struct file, unimplemented */
234 kobj, attr, buf, req->newidx, req->newlen);
235 if (len < 0)
236 error = -len;
237 out:
238 free_page((unsigned long)buf);
239
240 return (error);
241 }
242
243 static inline int
sysfs_create_bin_file(struct kobject * kobj,const struct bin_attribute * attr)244 sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
245 {
246 struct sysctl_oid *oid;
247 int ctlflags;
248
249 ctlflags = CTLTYPE_OPAQUE | CTLFLAG_MPSAFE;
250 if (attr->attr.mode & (S_IRUSR | S_IWUSR))
251 ctlflags |= CTLFLAG_RW;
252 else if (attr->attr.mode & S_IRUSR)
253 ctlflags |= CTLFLAG_RD;
254 else if (attr->attr.mode & S_IWUSR)
255 ctlflags |= CTLFLAG_WR;
256
257 oid = SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(kobj->oidp), OID_AUTO,
258 attr->attr.name, ctlflags, kobj,
259 (uintptr_t)attr, sysctl_handle_bin_attr, "", "");
260 if (oid == NULL)
261 return (-ENOMEM);
262
263 return (0);
264 }
265
266 static inline void
sysfs_remove_bin_file(struct kobject * kobj,const struct bin_attribute * attr)267 sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr)
268 {
269
270 if (kobj->oidp)
271 sysctl_remove_name(kobj->oidp, attr->attr.name, 1, 1);
272 }
273
274 static inline int
sysfs_create_link(struct kobject * kobj __unused,struct kobject * target __unused,const char * name __unused)275 sysfs_create_link(struct kobject *kobj __unused,
276 struct kobject *target __unused, const char *name __unused)
277 {
278 /* TODO */
279
280 return (0);
281 }
282
283 static inline void
sysfs_remove_link(struct kobject * kobj,const char * name)284 sysfs_remove_link(struct kobject *kobj, const char *name)
285 {
286 /* TODO (along with sysfs_create_link) */
287 }
288
289 static inline int
sysfs_create_files(struct kobject * kobj,const struct attribute * const * attrs)290 sysfs_create_files(struct kobject *kobj, const struct attribute * const *attrs)
291 {
292 int error = 0;
293 int i;
294
295 for (i = 0; attrs[i] && !error; i++)
296 error = sysfs_create_file(kobj, attrs[i]);
297 while (error && --i >= 0)
298 sysfs_remove_file(kobj, attrs[i]);
299
300 return (error);
301 }
302
303 static inline void
sysfs_remove_files(struct kobject * kobj,const struct attribute * const * attrs)304 sysfs_remove_files(struct kobject *kobj, const struct attribute * const *attrs)
305 {
306 int i;
307
308 for (i = 0; attrs[i]; i++)
309 sysfs_remove_file(kobj, attrs[i]);
310 }
311
312 static inline int
sysfs_create_group(struct kobject * kobj,const struct attribute_group * grp)313 sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
314 {
315 struct attribute **attr;
316 struct sysctl_oid *oidp;
317
318 /* Don't create the group node if grp->name is undefined. */
319 if (grp->name)
320 oidp = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->oidp),
321 OID_AUTO, grp->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, grp->name);
322 else
323 oidp = kobj->oidp;
324 for (attr = grp->attrs; *attr != NULL; attr++) {
325 SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(oidp), OID_AUTO,
326 (*attr)->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
327 kobj, (uintptr_t)*attr, sysctl_handle_attr, "A", "");
328 }
329
330 return (0);
331 }
332
333 static inline void
sysfs_remove_group(struct kobject * kobj,const struct attribute_group * grp)334 sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp)
335 {
336
337 if (kobj->oidp)
338 sysctl_remove_name(kobj->oidp, grp->name, 1, 1);
339 }
340
341 static inline int
sysfs_create_groups(struct kobject * kobj,const struct attribute_group ** grps)342 sysfs_create_groups(struct kobject *kobj, const struct attribute_group **grps)
343 {
344 int error = 0;
345 int i;
346
347 if (grps == NULL)
348 goto done;
349 for (i = 0; grps[i] && !error; i++)
350 error = sysfs_create_group(kobj, grps[i]);
351 while (error && --i >= 0)
352 sysfs_remove_group(kobj, grps[i]);
353 done:
354 return (error);
355 }
356
357 static inline void
sysfs_remove_groups(struct kobject * kobj,const struct attribute_group ** grps)358 sysfs_remove_groups(struct kobject *kobj, const struct attribute_group **grps)
359 {
360 int i;
361
362 if (grps == NULL)
363 return;
364 for (i = 0; grps[i]; i++)
365 sysfs_remove_group(kobj, grps[i]);
366 }
367
368 static inline int
sysfs_merge_group(struct kobject * kobj,const struct attribute_group * grp)369 sysfs_merge_group(struct kobject *kobj, const struct attribute_group *grp)
370 {
371
372 /* Really expected behavior is to return failure if group exists. */
373 return (sysfs_create_group(kobj, grp));
374 }
375
376 static inline void
sysfs_unmerge_group(struct kobject * kobj,const struct attribute_group * grp)377 sysfs_unmerge_group(struct kobject *kobj, const struct attribute_group *grp)
378 {
379 struct attribute **attr;
380 struct sysctl_oid *oidp;
381
382 SYSCTL_FOREACH(oidp, SYSCTL_CHILDREN(kobj->oidp)) {
383 if (strcmp(oidp->oid_name, grp->name) != 0)
384 continue;
385 for (attr = grp->attrs; *attr != NULL; attr++) {
386 sysctl_remove_name(oidp, (*attr)->name, 1, 1);
387 }
388 }
389 }
390
391 static inline int
sysfs_create_dir(struct kobject * kobj)392 sysfs_create_dir(struct kobject *kobj)
393 {
394 struct sysctl_oid *oid;
395
396 oid = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->parent->oidp),
397 OID_AUTO, kobj->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, kobj->name);
398 if (!oid) {
399 return (-ENOMEM);
400 }
401 kobj->oidp = oid;
402
403 return (0);
404 }
405
406 static inline void
sysfs_remove_dir(struct kobject * kobj)407 sysfs_remove_dir(struct kobject *kobj)
408 {
409
410 if (kobj->oidp == NULL)
411 return;
412 sysctl_remove_oid(kobj->oidp, 1, 1);
413 }
414
415 static inline bool
sysfs_streq(const char * s1,const char * s2)416 sysfs_streq(const char *s1, const char *s2)
417 {
418 int l1, l2;
419
420 l1 = strlen(s1);
421 l2 = strlen(s2);
422
423 if (l1 != 0 && s1[l1-1] == '\n')
424 l1--;
425 if (l2 != 0 && s2[l2-1] == '\n')
426 l2--;
427
428 return (l1 == l2 && strncmp(s1, s2, l1) == 0);
429 }
430
431 static inline int
sysfs_emit(char * buf,const char * fmt,...)432 sysfs_emit(char *buf, const char *fmt, ...)
433 {
434 va_list args;
435 int i;
436
437 if (!buf || offset_in_page(buf)) {
438 pr_warn("invalid sysfs_emit: buf:%p\n", buf);
439 return (0);
440 }
441
442 va_start(args, fmt);
443 i = vscnprintf(buf, PAGE_SIZE, fmt, args);
444 va_end(args);
445
446 return (i);
447 }
448
449 static inline int
sysfs_emit_at(char * buf,int at,const char * fmt,...)450 sysfs_emit_at(char *buf, int at, const char *fmt, ...)
451 {
452 va_list args;
453 int i;
454
455 if (!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE) {
456 pr_warn("invalid sysfs_emit: buf:%p at:%d\n", buf, at);
457 return (0);
458 }
459
460 va_start(args, fmt);
461 i = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
462 va_end(args);
463
464 return (i);
465 }
466
467 static inline int
_sysfs_match_string(const char * const * a,size_t l,const char * s)468 _sysfs_match_string(const char * const *a, size_t l, const char *s)
469 {
470 const char *p;
471 int i;
472
473 for (i = 0; i < l; i++) {
474 p = a[i];
475 if (p == NULL)
476 break;
477 if (sysfs_streq(p, s))
478 return (i);
479 }
480
481 return (-ENOENT);
482 }
483 #define sysfs_match_string(a, s) _sysfs_match_string(a, ARRAY_SIZE(a), s)
484
485 #define sysfs_attr_init(attr) do {} while(0)
486
487 #endif /* _LINUXKPI_LINUX_SYSFS_H_ */
488