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(_name) { \ 68 .attr = { .name = __stringify(_name), .mode = 0444 }, \ 69 .show = _name##_show, \ 70 } 71 #define __ATTR_WO(_name) __ATTR(_name, 0200, NULL, _name##_store) 72 #define __ATTR_RW(_name) __ATTR(_name, 0644, _name##_show, _name##_store) 73 #define __ATTR_NULL { .attr = { .name = NULL } } 74 75 #define ATTRIBUTE_GROUPS(_name) \ 76 static struct attribute_group _name##_group = { \ 77 .name = __stringify(_name), \ 78 .attrs = _name##_attrs, \ 79 }; \ 80 static const struct attribute_group *_name##_groups[] = { \ 81 &_name##_group, \ 82 NULL, \ 83 } 84 85 #define __BIN_ATTR(_name, _mode, _read, _write, _size) { \ 86 .attr = { .name = __stringify(_name), .mode = _mode }, \ 87 .read = _read, .write = _write, .size = _size, \ 88 } 89 #define __BIN_ATTR_RO(_name, _size) { \ 90 .attr = { .name = __stringify(_name), .mode = 0444 }, \ 91 .read = _name##_read, .size = _size, \ 92 } 93 #define __BIN_ATTR_WO(_name, _size) { \ 94 .attr = { .name = __stringify(_name), .mode = 0200 }, \ 95 .write = _name##_write, .size = _size, \ 96 } 97 #define __BIN_ATTR_WR(_name, _size) { \ 98 .attr = { .name = __stringify(_name), .mode = 0644 }, \ 99 .read = _name##_read, .write = _name##_write, .size = _size, \ 100 } 101 102 #define BIN_ATTR(_name, _mode, _read, _write, _size) \ 103 struct bin_attribute bin_attr_##_name = \ 104 __BIN_ATTR(_name, _mode, _read, _write, _size); 105 106 #define BIN_ATTR_RO(_name, _size) \ 107 struct bin_attribute bin_attr_##_name = \ 108 __BIN_ATTR_RO(_name, _size); 109 110 #define BIN_ATTR_WO(_name, _size) \ 111 struct bin_attribute bin_attr_##_name = \ 112 __BIN_ATTR_WO(_name, _size); 113 114 #define BIN_ATTR_WR(_name, _size) \ 115 struct bin_attribute bin_attr_##_name = \ 116 __BIN_ATTR_WR(_name, _size); 117 118 /* 119 * Handle our generic '\0' terminated 'C' string. 120 * Two cases: 121 * a variable string: point arg1 at it, arg2 is max length. 122 * a constant string: point arg1 at it, arg2 is zero. 123 */ 124 125 static inline int 126 sysctl_handle_attr(SYSCTL_HANDLER_ARGS) 127 { 128 struct kobject *kobj; 129 struct attribute *attr; 130 const struct sysfs_ops *ops; 131 char *buf; 132 int error; 133 ssize_t len; 134 135 kobj = arg1; 136 attr = (struct attribute *)(intptr_t)arg2; 137 if (kobj->ktype == NULL || kobj->ktype->sysfs_ops == NULL) 138 return (ENODEV); 139 buf = (char *)get_zeroed_page(GFP_KERNEL); 140 if (buf == NULL) 141 return (ENOMEM); 142 ops = kobj->ktype->sysfs_ops; 143 if (ops->show) { 144 len = ops->show(kobj, attr, buf); 145 /* 146 * It's valid to not have a 'show' so just return an 147 * empty string. 148 */ 149 if (len < 0) { 150 error = -len; 151 if (error != EIO) 152 goto out; 153 buf[0] = '\0'; 154 } else if (len) { 155 len--; 156 if (len >= PAGE_SIZE) 157 len = PAGE_SIZE - 1; 158 /* Trim trailing newline. */ 159 buf[len] = '\0'; 160 } 161 } 162 163 /* Leave one trailing byte to append a newline. */ 164 error = sysctl_handle_string(oidp, buf, PAGE_SIZE - 1, req); 165 if (error != 0 || req->newptr == NULL || ops->store == NULL) 166 goto out; 167 len = strlcat(buf, "\n", PAGE_SIZE); 168 KASSERT(len < PAGE_SIZE, ("new attribute truncated")); 169 len = ops->store(kobj, attr, buf, len); 170 if (len < 0) 171 error = -len; 172 out: 173 free_page((unsigned long)buf); 174 175 return (error); 176 } 177 178 static inline int 179 sysfs_create_file(struct kobject *kobj, const struct attribute *attr) 180 { 181 struct sysctl_oid *oid; 182 183 oid = SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(kobj->oidp), OID_AUTO, 184 attr->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE, kobj, 185 (uintptr_t)attr, sysctl_handle_attr, "A", ""); 186 if (!oid) { 187 return (-ENOMEM); 188 } 189 190 return (0); 191 } 192 193 static inline struct kobject * 194 __sysfs_lookup_group(struct kobject *kobj, const char *group) 195 { 196 int found; 197 struct sysctl_oid *group_oidp; 198 struct kobject *group_kobj; 199 200 found = 0; 201 if (group != NULL) { 202 SYSCTL_FOREACH(group_oidp, SYSCTL_CHILDREN(kobj->oidp)) { 203 if (strcmp(group_oidp->oid_name, group) != 0) 204 continue; 205 found = 1; 206 break; 207 } 208 } else { 209 found = 1; 210 group_oidp = kobj->oidp; 211 } 212 213 if (!found) 214 return (NULL); 215 216 group_kobj = group_oidp->oid_arg1; 217 218 return (group_kobj); 219 } 220 221 static inline int 222 sysfs_add_file_to_group(struct kobject *kobj, 223 const struct attribute *attr, const char *group) 224 { 225 int ret; 226 struct kobject *group_kobj; 227 228 group_kobj = __sysfs_lookup_group(kobj, group); 229 if (group_kobj == NULL) 230 return (-ENOENT); 231 232 ret = sysfs_create_file(group_kobj, attr); 233 234 return (ret); 235 } 236 237 static inline void 238 sysfs_remove_file(struct kobject *kobj, const struct attribute *attr) 239 { 240 241 if (kobj->oidp) 242 sysctl_remove_name(kobj->oidp, attr->name, 1, 1); 243 } 244 245 static inline void 246 sysfs_remove_file_from_group(struct kobject *kobj, 247 const struct attribute *attr, const char *group) 248 { 249 struct kobject *group_kobj; 250 251 group_kobj = __sysfs_lookup_group(kobj, group); 252 if (group_kobj == NULL) 253 return; 254 255 sysfs_remove_file(group_kobj, attr); 256 } 257 258 static inline int 259 sysctl_handle_bin_attr(SYSCTL_HANDLER_ARGS) 260 { 261 struct kobject *kobj; 262 struct bin_attribute *attr; 263 char *buf; 264 int error; 265 ssize_t len; 266 267 kobj = arg1; 268 attr = (struct bin_attribute *)(intptr_t)arg2; 269 if (kobj->ktype == NULL || kobj->ktype->sysfs_ops == NULL) 270 return (ENODEV); 271 buf = (char *)get_zeroed_page(GFP_KERNEL); 272 if (buf == NULL) 273 return (ENOMEM); 274 275 if (attr->read) { 276 len = attr->read( 277 NULL, /* <-- struct file, unimplemented */ 278 kobj, attr, buf, req->oldidx, PAGE_SIZE); 279 if (len < 0) { 280 error = -len; 281 if (error != EIO) 282 goto out; 283 } 284 } 285 286 error = sysctl_handle_opaque(oidp, buf, PAGE_SIZE, req); 287 if (error != 0 || req->newptr == NULL || attr->write == NULL) 288 goto out; 289 290 len = attr->write( 291 NULL, /* <-- struct file, unimplemented */ 292 kobj, attr, buf, req->newidx, req->newlen); 293 if (len < 0) 294 error = -len; 295 out: 296 free_page((unsigned long)buf); 297 298 return (error); 299 } 300 301 static inline int 302 sysfs_create_bin_file(struct kobject *kobj, const struct bin_attribute *attr) 303 { 304 struct sysctl_oid *oid; 305 int ctlflags; 306 307 ctlflags = CTLTYPE_OPAQUE | CTLFLAG_MPSAFE; 308 if (attr->attr.mode & (S_IRUSR | S_IWUSR)) 309 ctlflags |= CTLFLAG_RW; 310 else if (attr->attr.mode & S_IRUSR) 311 ctlflags |= CTLFLAG_RD; 312 else if (attr->attr.mode & S_IWUSR) 313 ctlflags |= CTLFLAG_WR; 314 315 oid = SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(kobj->oidp), OID_AUTO, 316 attr->attr.name, ctlflags, kobj, 317 (uintptr_t)attr, sysctl_handle_bin_attr, "", ""); 318 if (oid == NULL) 319 return (-ENOMEM); 320 321 return (0); 322 } 323 324 static inline void 325 sysfs_remove_bin_file(struct kobject *kobj, const struct bin_attribute *attr) 326 { 327 328 if (kobj->oidp) 329 sysctl_remove_name(kobj->oidp, attr->attr.name, 1, 1); 330 } 331 332 static inline int 333 sysfs_create_link(struct kobject *kobj __unused, 334 struct kobject *target __unused, const char *name __unused) 335 { 336 /* TODO */ 337 338 return (0); 339 } 340 341 static inline void 342 sysfs_remove_link(struct kobject *kobj, const char *name) 343 { 344 /* TODO (along with sysfs_create_link) */ 345 } 346 347 static inline int 348 sysfs_create_files(struct kobject *kobj, const struct attribute * const *attrs) 349 { 350 int error = 0; 351 int i; 352 353 for (i = 0; attrs[i] && !error; i++) 354 error = sysfs_create_file(kobj, attrs[i]); 355 while (error && --i >= 0) 356 sysfs_remove_file(kobj, attrs[i]); 357 358 return (error); 359 } 360 361 static inline void 362 sysfs_remove_files(struct kobject *kobj, const struct attribute * const *attrs) 363 { 364 int i; 365 366 for (i = 0; attrs[i]; i++) 367 sysfs_remove_file(kobj, attrs[i]); 368 } 369 370 static inline int 371 sysfs_create_group(struct kobject *kobj, const struct attribute_group *grp) 372 { 373 struct attribute **attr; 374 struct bin_attribute **bin_attr; 375 struct sysctl_oid *oidp; 376 377 /* Don't create the group node if grp->name is undefined. */ 378 if (grp->name) 379 oidp = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->oidp), 380 OID_AUTO, grp->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, grp->name); 381 else 382 oidp = kobj->oidp; 383 for (attr = grp->attrs; attr != NULL && *attr != NULL; attr++) { 384 SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(oidp), OID_AUTO, 385 (*attr)->name, CTLTYPE_STRING|CTLFLAG_RW|CTLFLAG_MPSAFE, 386 kobj, (uintptr_t)*attr, sysctl_handle_attr, "A", ""); 387 } 388 for (bin_attr = grp->bin_attrs; 389 bin_attr != NULL && *bin_attr != NULL; 390 bin_attr++) { 391 SYSCTL_ADD_OID(NULL, SYSCTL_CHILDREN(oidp), OID_AUTO, 392 (*bin_attr)->attr.name, 393 CTLTYPE_OPAQUE|CTLFLAG_RW|CTLFLAG_MPSAFE, 394 kobj, (uintptr_t)*bin_attr, sysctl_handle_bin_attr, "", ""); 395 } 396 397 return (0); 398 } 399 400 static inline void 401 sysfs_remove_group(struct kobject *kobj, const struct attribute_group *grp) 402 { 403 404 if (kobj->oidp) 405 sysctl_remove_name(kobj->oidp, grp->name, 1, 1); 406 } 407 408 static inline int 409 sysfs_create_groups(struct kobject *kobj, const struct attribute_group **grps) 410 { 411 int error = 0; 412 int i; 413 414 if (grps == NULL) 415 goto done; 416 for (i = 0; grps[i] && !error; i++) 417 error = sysfs_create_group(kobj, grps[i]); 418 while (error && --i >= 0) 419 sysfs_remove_group(kobj, grps[i]); 420 done: 421 return (error); 422 } 423 424 static inline void 425 sysfs_remove_groups(struct kobject *kobj, const struct attribute_group **grps) 426 { 427 int i; 428 429 if (grps == NULL) 430 return; 431 for (i = 0; grps[i]; i++) 432 sysfs_remove_group(kobj, grps[i]); 433 } 434 435 static inline int 436 sysfs_merge_group(struct kobject *kobj, const struct attribute_group *grp) 437 { 438 439 /* Really expected behavior is to return failure if group exists. */ 440 return (sysfs_create_group(kobj, grp)); 441 } 442 443 static inline void 444 sysfs_unmerge_group(struct kobject *kobj, const struct attribute_group *grp) 445 { 446 struct attribute **attr; 447 struct bin_attribute **bin_attr; 448 struct sysctl_oid *oidp; 449 450 SYSCTL_FOREACH(oidp, SYSCTL_CHILDREN(kobj->oidp)) { 451 if (strcmp(oidp->oid_name, grp->name) != 0) 452 continue; 453 for (attr = grp->attrs; attr != NULL && *attr != NULL; attr++) { 454 sysctl_remove_name(oidp, (*attr)->name, 1, 1); 455 } 456 for (bin_attr = grp->bin_attrs; 457 bin_attr != NULL && *bin_attr != NULL; 458 bin_attr++) { 459 sysctl_remove_name(oidp, (*bin_attr)->attr.name, 1, 1); 460 } 461 } 462 } 463 464 static inline int 465 sysfs_create_dir(struct kobject *kobj) 466 { 467 struct sysctl_oid *oid; 468 469 oid = SYSCTL_ADD_NODE(NULL, SYSCTL_CHILDREN(kobj->parent->oidp), 470 OID_AUTO, kobj->name, CTLFLAG_RD|CTLFLAG_MPSAFE, NULL, kobj->name); 471 if (!oid) { 472 return (-ENOMEM); 473 } 474 kobj->oidp = oid; 475 476 return (0); 477 } 478 479 static inline void 480 sysfs_remove_dir(struct kobject *kobj) 481 { 482 483 if (kobj->oidp == NULL) 484 return; 485 sysctl_remove_oid(kobj->oidp, 1, 1); 486 } 487 488 static inline bool 489 sysfs_streq(const char *s1, const char *s2) 490 { 491 int l1, l2; 492 493 l1 = strlen(s1); 494 l2 = strlen(s2); 495 496 if (l1 != 0 && s1[l1-1] == '\n') 497 l1--; 498 if (l2 != 0 && s2[l2-1] == '\n') 499 l2--; 500 501 return (l1 == l2 && strncmp(s1, s2, l1) == 0); 502 } 503 504 static inline int 505 sysfs_emit(char *buf, const char *fmt, ...) 506 { 507 va_list args; 508 int i; 509 510 if (!buf || offset_in_page(buf)) { 511 pr_warn("invalid sysfs_emit: buf:%p\n", buf); 512 return (0); 513 } 514 515 va_start(args, fmt); 516 i = vscnprintf(buf, PAGE_SIZE, fmt, args); 517 va_end(args); 518 519 return (i); 520 } 521 522 static inline int 523 sysfs_emit_at(char *buf, int at, const char *fmt, ...) 524 { 525 va_list args; 526 int i; 527 528 if (!buf || offset_in_page(buf) || at < 0 || at >= PAGE_SIZE) { 529 pr_warn("invalid sysfs_emit: buf:%p at:%d\n", buf, at); 530 return (0); 531 } 532 533 va_start(args, fmt); 534 i = vscnprintf(buf + at, PAGE_SIZE - at, fmt, args); 535 va_end(args); 536 537 return (i); 538 } 539 540 static inline int 541 _sysfs_match_string(const char * const *a, size_t l, const char *s) 542 { 543 const char *p; 544 int i; 545 546 for (i = 0; i < l; i++) { 547 p = a[i]; 548 if (p == NULL) 549 break; 550 if (sysfs_streq(p, s)) 551 return (i); 552 } 553 554 return (-ENOENT); 555 } 556 #define sysfs_match_string(a, s) _sysfs_match_string(a, ARRAY_SIZE(a), s) 557 558 #define sysfs_attr_init(attr) do {} while(0) 559 560 #endif /* _LINUXKPI_LINUX_SYSFS_H_ */ 561