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 #define __ATTR(_name, _mode, _show, _store) { \
54 .attr = { .name = __stringify(_name), .mode = _mode }, \
55 .show = _show, .store = _store, \
56 }
57 #define __ATTR_RO(_name) { \
58 .attr = { .name = __stringify(_name), .mode = 0444 }, \
59 .show = _name##_show, \
60 }
61 #define __ATTR_WO(_name) __ATTR(_name, 0200, NULL, _name##_store)
62 #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store)
63 #define __ATTR_NULL { .attr = { .name = NULL } }
64
65 #define ATTRIBUTE_GROUPS(_name) \
66 static struct attribute_group _name##_group = { \
67 .name = __stringify(_name), \
68 .attrs = _name##_attrs, \
69 }; \
70 static const struct attribute_group *_name##_groups[] = { \
71 &_name##_group, \
72 NULL, \
73 }
74
75 /*
76 * Handle our generic '\0' terminated 'C' string.
77 * Two cases:
78 * a variable string: point arg1 at it, arg2 is max length.
79 * a constant string: point arg1 at it, arg2 is zero.
80 */
81
82 static inline int
sysctl_handle_attr(SYSCTL_HANDLER_ARGS)83 sysctl_handle_attr(SYSCTL_HANDLER_ARGS)
84 {
85 struct kobject *kobj;
86 struct attribute *attr;
87 const struct sysfs_ops *ops;
88 char *buf;
89 int error;
90 ssize_t len;
91
92 kobj = arg1;
93 attr = (struct attribute *)(intptr_t)arg2;
94 if (kobj->ktype == NULL || kobj->ktype->sysfs_ops == NULL)
95 return (ENODEV);
96 buf = (char *)get_zeroed_page(GFP_KERNEL);
97 if (buf == NULL)
98 return (ENOMEM);
99 ops = kobj->ktype->sysfs_ops;
100 if (ops->show) {
101 len = ops->show(kobj, attr, buf);
102 /*
103 * It's valid to not have a 'show' so just return an
104 * empty string.
105 */
106 if (len < 0) {
107 error = -len;
108 if (error != EIO)
109 goto out;
110 buf[0] = '\0';
111 } else if (len) {
112 len--;
113 if (len >= PAGE_SIZE)
114 len = PAGE_SIZE - 1;
115 /* Trim trailing newline. */
116 buf[len] = '\0';
117 }
118 }
119
120 /* Leave one trailing byte to append a newline. */
121 error = sysctl_handle_string(oidp, buf, PAGE_SIZE - 1, req);
122 if (error != 0 || req->newptr == NULL || ops->store == NULL)
123 goto out;
124 len = strlcat(buf, "\n", PAGE_SIZE);
125 KASSERT(len < PAGE_SIZE, ("new attribute truncated"));
126 len = ops->store(kobj, attr, buf, len);
127 if (len < 0)
128 error = -len;
129 out:
130 free_page((unsigned long)buf);
131
132 return (error);
133 }
134
135 static inline int
sysfs_create_file(struct kobject * kobj,const struct attribute * attr)136 sysfs_create_file(struct kobject *kobj, const struct attribute *attr)
137 {
138 struct sysctl_oid *oid;
139
140 oid = SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(kobj->oidp), OID_AUTO,
141 attr->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE, kobj,
142 (uintptr_t)attr, sysctl_handle_attr, "A", "");
143 if (!oid) {
144 return (-ENOMEM);
145 }
146
147 return (0);
148 }
149
150 static inline void
sysfs_remove_file(struct kobject * kobj,const struct attribute * attr)151 sysfs_remove_file(struct kobject *kobj, const struct attribute *attr)
152 {
153
154 if (kobj->oidp)
155 sysctl_remove_name(kobj->oidp, attr->name, 1, 1);
156 }
157
158 static inline int
sysfs_create_link(struct kobject * kobj __unused,struct kobject * target __unused,const char * name __unused)159 sysfs_create_link(struct kobject *kobj __unused,
160 struct kobject *target __unused, const char *name __unused)
161 {
162 /* TODO */
163
164 return (0);
165 }
166
167 static inline void
sysfs_remove_link(struct kobject * kobj,const char * name)168 sysfs_remove_link(struct kobject *kobj, const char *name)
169 {
170 /* TODO (along with sysfs_create_link) */
171 }
172
173 static inline int
sysfs_create_files(struct kobject * kobj,const struct attribute * const * attrs)174 sysfs_create_files(struct kobject *kobj, const struct attribute * const *attrs)
175 {
176 int error = 0;
177 int i;
178
179 for (i = 0; attrs[i] && !error; i++)
180 error = sysfs_create_file(kobj, attrs[i]);
181 while (error && --i >= 0)
182 sysfs_remove_file(kobj, attrs[i]);
183
184 return (error);
185 }
186
187 static inline void
sysfs_remove_files(struct kobject * kobj,const struct attribute * const * attrs)188 sysfs_remove_files(struct kobject *kobj, const struct attribute * const *attrs)
189 {
190 int i;
191
192 for (i = 0; attrs[i]; i++)
193 sysfs_remove_file(kobj, attrs[i]);
194 }
195
196 static inline int
sysfs_create_group(struct kobject * kobj,const struct attribute_group * grp)197 sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp)
198 {
199 struct attribute **attr;
200 struct sysctl_oid *oidp;
201
202 /* Don't create the group node if grp->name is undefined. */
203 if (grp->name)
204 oidp = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->oidp),
205 OID_AUTO, grp->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, grp->name);
206 else
207 oidp = kobj->oidp;
208 for (attr = grp->attrs; *attr != NULL; attr++) {
209 SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(oidp), OID_AUTO,
210 (*attr)->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE,
211 kobj, (uintptr_t)*attr, sysctl_handle_attr, "A", "");
212 }
213
214 return (0);
215 }
216
217 static inline void
sysfs_remove_group(struct kobject * kobj,const struct attribute_group * grp)218 sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp)
219 {
220
221 if (kobj->oidp)
222 sysctl_remove_name(kobj->oidp, grp->name, 1, 1);
223 }
224
225 static inline int
sysfs_create_groups(struct kobject * kobj,const struct attribute_group ** grps)226 sysfs_create_groups(struct kobject *kobj, const struct attribute_group **grps)
227 {
228 int error = 0;
229 int i;
230
231 if (grps == NULL)
232 goto done;
233 for (i = 0; grps[i] && !error; i++)
234 error = sysfs_create_group(kobj, grps[i]);
235 while (error && --i >= 0)
236 sysfs_remove_group(kobj, grps[i]);
237 done:
238 return (error);
239 }
240
241 static inline void
sysfs_remove_groups(struct kobject * kobj,const struct attribute_group ** grps)242 sysfs_remove_groups(struct kobject *kobj, const struct attribute_group **grps)
243 {
244 int i;
245
246 if (grps == NULL)
247 return;
248 for (i = 0; grps[i]; i++)
249 sysfs_remove_group(kobj, grps[i]);
250 }
251
252 static inline int
sysfs_merge_group(struct kobject * kobj,const struct attribute_group * grp)253 sysfs_merge_group(struct kobject *kobj, const struct attribute_group *grp)
254 {
255
256 /* Really expected behavior is to return failure if group exists. */
257 return (sysfs_create_group(kobj, grp));
258 }
259
260 static inline void
sysfs_unmerge_group(struct kobject * kobj,const struct attribute_group * grp)261 sysfs_unmerge_group(struct kobject *kobj, const struct attribute_group *grp)
262 {
263 struct attribute **attr;
264 struct sysctl_oid *oidp;
265
266 SYSCTL_FOREACH(oidp, SYSCTL_CHILDREN(kobj->oidp)) {
267 if (strcmp(oidp->oid_name, grp->name) != 0)
268 continue;
269 for (attr = grp->attrs; *attr != NULL; attr++) {
270 sysctl_remove_name(oidp, (*attr)->name, 1, 1);
271 }
272 }
273 }
274
275 static inline int
sysfs_create_dir(struct kobject * kobj)276 sysfs_create_dir(struct kobject *kobj)
277 {
278 struct sysctl_oid *oid;
279
280 oid = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->parent->oidp),
281 OID_AUTO, kobj->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, kobj->name);
282 if (!oid) {
283 return (-ENOMEM);
284 }
285 kobj->oidp = oid;
286
287 return (0);
288 }
289
290 static inline void
sysfs_remove_dir(struct kobject * kobj)291 sysfs_remove_dir(struct kobject *kobj)
292 {
293
294 if (kobj->oidp == NULL)
295 return;
296 sysctl_remove_oid(kobj->oidp, 1, 1);
297 }
298
299 static inline bool
sysfs_streq(const char * s1,const char * s2)300 sysfs_streq(const char *s1, const char *s2)
301 {
302 int l1, l2;
303
304 l1 = strlen(s1);
305 l2 = strlen(s2);
306
307 if (l1 != 0 && s1[l1-1] == '\n')
308 l1--;
309 if (l2 != 0 && s2[l2-1] == '\n')
310 l2--;
311
312 return (l1 == l2 && strncmp(s1, s2, l1) == 0);
313 }
314
315 static inline int
sysfs_emit(char * buf,const char * fmt,...)316 sysfs_emit(char *buf, const char *fmt, ...)
317 {
318 va_list args;
319 int i;
320
321 if (!buf || offset_in_page(buf)) {
322 pr_warn("invalid sysfs_emit: buf:%p\n", buf);
323 return (0);
324 }
325
326 va_start(args, fmt);
327 i = vscnprintf(buf, PAGE_SIZE, fmt, args);
328 va_end(args);
329
330 return (i);
331 }
332
333 static inline int
sysfs_emit_at(char * buf,int at,const char * fmt,...)334 sysfs_emit_at(char *buf, int at, const char *fmt, ...)
335 {
336 va_list args;
337 int i;
338
339 if (!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE) {
340 pr_warn("invalid sysfs_emit: buf:%p at:%d\n", buf, at);
341 return (0);
342 }
343
344 va_start(args, fmt);
345 i = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args);
346 va_end(args);
347
348 return (i);
349 }
350
351 #define sysfs_attr_init(attr) do {} while(0)
352
353 #endif /* _LINUXKPI_LINUX_SYSFS_H_ */
354