xref: /src/sys/compat/linuxkpi/common/include/linux/sysfs.h (revision 5bb0f63020669bd3675c651ba7745fc4356edc1a)
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