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