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