xref: /freebsd/sys/compat/linuxkpi/common/include/linux/sysfs.h (revision 294553b022b1e775111365094a70041934d98501)
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
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
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 *
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
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
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
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
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
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
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
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
345 sysfs_remove_link(struct kobject *kobj, const char *name)
346 {
347 	/* TODO (along with sysfs_create_link) */
348 }
349 
350 static inline int
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
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
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
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
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
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
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
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
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
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
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
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
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
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