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