diff options
| -rw-r--r-- | arch/powerpc/kernel/sysfs.c | 309 |
1 files changed, 309 insertions, 0 deletions
diff --git a/arch/powerpc/kernel/sysfs.c b/arch/powerpc/kernel/sysfs.c index 156808086cac..56d172d16e56 100644 --- a/arch/powerpc/kernel/sysfs.c +++ b/arch/powerpc/kernel/sysfs.c | |||
| @@ -22,6 +22,8 @@ | |||
| 22 | 22 | ||
| 23 | static DEFINE_PER_CPU(struct cpu, cpu_devices); | 23 | static DEFINE_PER_CPU(struct cpu, cpu_devices); |
| 24 | 24 | ||
| 25 | static DEFINE_PER_CPU(struct kobject *, cache_toplevel); | ||
| 26 | |||
| 25 | /* SMT stuff */ | 27 | /* SMT stuff */ |
| 26 | 28 | ||
| 27 | #ifdef CONFIG_PPC_MULTIPLATFORM | 29 | #ifdef CONFIG_PPC_MULTIPLATFORM |
| @@ -297,6 +299,287 @@ static struct sysdev_attribute pa6t_attrs[] = { | |||
| 297 | #endif /* CONFIG_DEBUG_KERNEL */ | 299 | #endif /* CONFIG_DEBUG_KERNEL */ |
| 298 | }; | 300 | }; |
| 299 | 301 | ||
| 302 | struct cache_desc { | ||
| 303 | struct kobject kobj; | ||
| 304 | struct cache_desc *next; | ||
| 305 | const char *type; /* Instruction, Data, or Unified */ | ||
| 306 | u32 size; /* total cache size in KB */ | ||
| 307 | u32 line_size; /* in bytes */ | ||
| 308 | u32 nr_sets; /* number of sets */ | ||
| 309 | u32 level; /* e.g. 1, 2, 3... */ | ||
| 310 | u32 associativity; /* e.g. 8-way... 0 is fully associative */ | ||
| 311 | }; | ||
| 312 | |||
| 313 | DEFINE_PER_CPU(struct cache_desc *, cache_desc); | ||
| 314 | |||
| 315 | static struct cache_desc *kobj_to_cache_desc(struct kobject *k) | ||
| 316 | { | ||
| 317 | return container_of(k, struct cache_desc, kobj); | ||
| 318 | } | ||
| 319 | |||
| 320 | static void cache_desc_release(struct kobject *k) | ||
| 321 | { | ||
| 322 | struct cache_desc *desc = kobj_to_cache_desc(k); | ||
| 323 | |||
| 324 | pr_debug("%s: releasing %s\n", __func__, kobject_name(k)); | ||
| 325 | |||
| 326 | if (desc->next) | ||
| 327 | kobject_put(&desc->next->kobj); | ||
| 328 | |||
| 329 | kfree(kobj_to_cache_desc(k)); | ||
| 330 | } | ||
| 331 | |||
| 332 | static ssize_t cache_desc_show(struct kobject *k, struct attribute *attr, char *buf) | ||
| 333 | { | ||
| 334 | struct kobj_attribute *kobj_attr; | ||
| 335 | |||
| 336 | kobj_attr = container_of(attr, struct kobj_attribute, attr); | ||
| 337 | |||
| 338 | return kobj_attr->show(k, kobj_attr, buf); | ||
| 339 | } | ||
| 340 | |||
| 341 | static struct sysfs_ops cache_desc_sysfs_ops = { | ||
| 342 | .show = cache_desc_show, | ||
| 343 | }; | ||
| 344 | |||
| 345 | static struct kobj_type cache_desc_type = { | ||
| 346 | .release = cache_desc_release, | ||
| 347 | .sysfs_ops = &cache_desc_sysfs_ops, | ||
| 348 | }; | ||
| 349 | |||
| 350 | static ssize_t cache_size_show(struct kobject *k, struct kobj_attribute *attr, char *buf) | ||
| 351 | { | ||
| 352 | struct cache_desc *cache = kobj_to_cache_desc(k); | ||
| 353 | |||
| 354 | return sprintf(buf, "%uK\n", cache->size); | ||
| 355 | } | ||
| 356 | |||
| 357 | static struct kobj_attribute cache_size_attr = | ||
| 358 | __ATTR(size, 0444, cache_size_show, NULL); | ||
| 359 | |||
| 360 | static ssize_t cache_line_size_show(struct kobject *k, struct kobj_attribute *attr, char *buf) | ||
| 361 | { | ||
| 362 | struct cache_desc *cache = kobj_to_cache_desc(k); | ||
| 363 | |||
| 364 | return sprintf(buf, "%u\n", cache->line_size); | ||
| 365 | } | ||
| 366 | |||
| 367 | static struct kobj_attribute cache_line_size_attr = | ||
| 368 | __ATTR(coherency_line_size, 0444, cache_line_size_show, NULL); | ||
| 369 | |||
| 370 | static ssize_t cache_nr_sets_show(struct kobject *k, struct kobj_attribute *attr, char *buf) | ||
| 371 | { | ||
| 372 | struct cache_desc *cache = kobj_to_cache_desc(k); | ||
| 373 | |||
| 374 | return sprintf(buf, "%u\n", cache->nr_sets); | ||
| 375 | } | ||
| 376 | |||
| 377 | static struct kobj_attribute cache_nr_sets_attr = | ||
| 378 | __ATTR(number_of_sets, 0444, cache_nr_sets_show, NULL); | ||
| 379 | |||
| 380 | static ssize_t cache_type_show(struct kobject *k, struct kobj_attribute *attr, char *buf) | ||
| 381 | { | ||
| 382 | struct cache_desc *cache = kobj_to_cache_desc(k); | ||
| 383 | |||
| 384 | return sprintf(buf, "%s\n", cache->type); | ||
| 385 | } | ||
| 386 | |||
| 387 | static struct kobj_attribute cache_type_attr = | ||
| 388 | __ATTR(type, 0444, cache_type_show, NULL); | ||
| 389 | |||
| 390 | static ssize_t cache_level_show(struct kobject *k, struct kobj_attribute *attr, char *buf) | ||
| 391 | { | ||
| 392 | struct cache_desc *cache = kobj_to_cache_desc(k); | ||
| 393 | |||
| 394 | return sprintf(buf, "%u\n", cache->level); | ||
| 395 | } | ||
| 396 | |||
| 397 | static struct kobj_attribute cache_level_attr = | ||
| 398 | __ATTR(level, 0444, cache_level_show, NULL); | ||
| 399 | |||
| 400 | static ssize_t cache_assoc_show(struct kobject *k, struct kobj_attribute *attr, char *buf) | ||
| 401 | { | ||
| 402 | struct cache_desc *cache = kobj_to_cache_desc(k); | ||
| 403 | |||
| 404 | return sprintf(buf, "%u\n", cache->associativity); | ||
| 405 | } | ||
| 406 | |||
| 407 | static struct kobj_attribute cache_assoc_attr = | ||
| 408 | __ATTR(ways_of_associativity, 0444, cache_assoc_show, NULL); | ||
| 409 | |||
| 410 | struct cache_desc_info { | ||
| 411 | const char *type; | ||
| 412 | const char *size_prop; | ||
| 413 | const char *line_size_prop; | ||
| 414 | const char *nr_sets_prop; | ||
| 415 | }; | ||
| 416 | |||
| 417 | /* PowerPC Processor binding says the [di]-cache-* must be equal on | ||
| 418 | * unified caches, so just use d-cache properties. */ | ||
| 419 | static struct cache_desc_info ucache_info = { | ||
| 420 | .type = "Unified", | ||
| 421 | .size_prop = "d-cache-size", | ||
| 422 | .line_size_prop = "d-cache-line-size", | ||
| 423 | .nr_sets_prop = "d-cache-sets", | ||
| 424 | }; | ||
| 425 | |||
| 426 | static struct cache_desc_info dcache_info = { | ||
| 427 | .type = "Data", | ||
| 428 | .size_prop = "d-cache-size", | ||
| 429 | .line_size_prop = "d-cache-line-size", | ||
| 430 | .nr_sets_prop = "d-cache-sets", | ||
| 431 | }; | ||
| 432 | |||
| 433 | static struct cache_desc_info icache_info = { | ||
| 434 | .type = "Instruction", | ||
| 435 | .size_prop = "i-cache-size", | ||
| 436 | .line_size_prop = "i-cache-line-size", | ||
| 437 | .nr_sets_prop = "i-cache-sets", | ||
| 438 | }; | ||
| 439 | |||
| 440 | static struct cache_desc * __cpuinit create_cache_desc(struct device_node *np, struct kobject *parent, int index, int level, struct cache_desc_info *info) | ||
| 441 | { | ||
| 442 | const u32 *cache_line_size; | ||
| 443 | struct cache_desc *new; | ||
| 444 | const u32 *cache_size; | ||
| 445 | const u32 *nr_sets; | ||
| 446 | int rc; | ||
| 447 | |||
| 448 | new = kzalloc(sizeof(*new), GFP_KERNEL); | ||
| 449 | if (!new) | ||
| 450 | return NULL; | ||
| 451 | |||
| 452 | rc = kobject_init_and_add(&new->kobj, &cache_desc_type, parent, | ||
| 453 | "index%d", index); | ||
| 454 | if (rc) | ||
| 455 | goto err; | ||
| 456 | |||
| 457 | /* type */ | ||
| 458 | new->type = info->type; | ||
| 459 | rc = sysfs_create_file(&new->kobj, &cache_type_attr.attr); | ||
| 460 | WARN_ON(rc); | ||
| 461 | |||
| 462 | /* level */ | ||
| 463 | new->level = level; | ||
| 464 | rc = sysfs_create_file(&new->kobj, &cache_level_attr.attr); | ||
| 465 | WARN_ON(rc); | ||
| 466 | |||
| 467 | /* size */ | ||
| 468 | cache_size = of_get_property(np, info->size_prop, NULL); | ||
| 469 | if (cache_size) { | ||
| 470 | new->size = *cache_size / 1024; | ||
| 471 | rc = sysfs_create_file(&new->kobj, | ||
| 472 | &cache_size_attr.attr); | ||
| 473 | WARN_ON(rc); | ||
| 474 | } | ||
| 475 | |||
| 476 | /* coherency_line_size */ | ||
| 477 | cache_line_size = of_get_property(np, info->line_size_prop, NULL); | ||
| 478 | if (cache_line_size) { | ||
| 479 | new->line_size = *cache_line_size; | ||
| 480 | rc = sysfs_create_file(&new->kobj, | ||
| 481 | &cache_line_size_attr.attr); | ||
| 482 | WARN_ON(rc); | ||
| 483 | } | ||
| 484 | |||
| 485 | /* number_of_sets */ | ||
| 486 | nr_sets = of_get_property(np, info->nr_sets_prop, NULL); | ||
| 487 | if (nr_sets) { | ||
| 488 | new->nr_sets = *nr_sets; | ||
| 489 | rc = sysfs_create_file(&new->kobj, | ||
| 490 | &cache_nr_sets_attr.attr); | ||
| 491 | WARN_ON(rc); | ||
| 492 | } | ||
| 493 | |||
| 494 | /* ways_of_associativity */ | ||
| 495 | if (new->nr_sets == 1) { | ||
| 496 | /* fully associative */ | ||
| 497 | new->associativity = 0; | ||
| 498 | goto create_assoc; | ||
| 499 | } | ||
| 500 | |||
| 501 | if (new->nr_sets && new->size && new->line_size) { | ||
| 502 | /* If we have values for all of these we can derive | ||
| 503 | * the associativity. */ | ||
| 504 | new->associativity = | ||
| 505 | ((new->size * 1024) / new->nr_sets) / new->line_size; | ||
| 506 | create_assoc: | ||
| 507 | rc = sysfs_create_file(&new->kobj, | ||
| 508 | &cache_assoc_attr.attr); | ||
| 509 | WARN_ON(rc); | ||
| 510 | } | ||
| 511 | |||
| 512 | return new; | ||
| 513 | err: | ||
| 514 | kfree(new); | ||
| 515 | return NULL; | ||
| 516 | } | ||
| 517 | |||
| 518 | static bool cache_is_unified(struct device_node *np) | ||
| 519 | { | ||
| 520 | return of_get_property(np, "cache-unified", NULL); | ||
| 521 | } | ||
| 522 | |||
| 523 | static struct cache_desc * __cpuinit create_cache_index_info(struct device_node *np, struct kobject *parent, int index, int level) | ||
| 524 | { | ||
| 525 | const phandle *next_cache_phandle; | ||
| 526 | struct device_node *next_cache; | ||
| 527 | struct cache_desc *new, **end; | ||
| 528 | |||
| 529 | pr_debug("%s(node = %s, index = %d)\n", __func__, np->full_name, index); | ||
| 530 | |||
| 531 | if (cache_is_unified(np)) { | ||
| 532 | new = create_cache_desc(np, parent, index, level, | ||
| 533 | &ucache_info); | ||
| 534 | } else { | ||
| 535 | new = create_cache_desc(np, parent, index, level, | ||
| 536 | &dcache_info); | ||
| 537 | if (new) { | ||
| 538 | index++; | ||
| 539 | new->next = create_cache_desc(np, parent, index, level, | ||
| 540 | &icache_info); | ||
| 541 | } | ||
| 542 | } | ||
| 543 | if (!new) | ||
| 544 | return NULL; | ||
| 545 | |||
| 546 | end = &new->next; | ||
| 547 | while (*end) | ||
| 548 | end = &(*end)->next; | ||
| 549 | |||
| 550 | next_cache_phandle = of_get_property(np, "l2-cache", NULL); | ||
| 551 | if (!next_cache_phandle) | ||
| 552 | goto out; | ||
| 553 | |||
| 554 | next_cache = of_find_node_by_phandle(*next_cache_phandle); | ||
| 555 | if (!next_cache) | ||
| 556 | goto out; | ||
| 557 | |||
| 558 | *end = create_cache_index_info(next_cache, parent, ++index, ++level); | ||
| 559 | |||
| 560 | of_node_put(next_cache); | ||
| 561 | out: | ||
| 562 | return new; | ||
| 563 | } | ||
| 564 | |||
| 565 | static void __cpuinit create_cache_info(struct sys_device *sysdev) | ||
| 566 | { | ||
| 567 | struct kobject *cache_toplevel; | ||
| 568 | struct device_node *np = NULL; | ||
| 569 | int cpu = sysdev->id; | ||
| 570 | |||
| 571 | cache_toplevel = kobject_create_and_add("cache", &sysdev->kobj); | ||
| 572 | if (!cache_toplevel) | ||
| 573 | return; | ||
| 574 | per_cpu(cache_toplevel, cpu) = cache_toplevel; | ||
| 575 | np = of_get_cpu_node(cpu, NULL); | ||
| 576 | if (np != NULL) { | ||
| 577 | per_cpu(cache_desc, cpu) = | ||
| 578 | create_cache_index_info(np, cache_toplevel, 0, 1); | ||
| 579 | of_node_put(np); | ||
| 580 | } | ||
| 581 | return; | ||
| 582 | } | ||
| 300 | 583 | ||
| 301 | static void __cpuinit register_cpu_online(unsigned int cpu) | 584 | static void __cpuinit register_cpu_online(unsigned int cpu) |
| 302 | { | 585 | { |
| @@ -346,9 +629,33 @@ static void __cpuinit register_cpu_online(unsigned int cpu) | |||
| 346 | 629 | ||
| 347 | if (cpu_has_feature(CPU_FTR_DSCR)) | 630 | if (cpu_has_feature(CPU_FTR_DSCR)) |
| 348 | sysdev_create_file(s, &attr_dscr); | 631 | sysdev_create_file(s, &attr_dscr); |
| 632 | |||
| 633 | create_cache_info(s); | ||
| 349 | } | 634 | } |
| 350 | 635 | ||
| 351 | #ifdef CONFIG_HOTPLUG_CPU | 636 | #ifdef CONFIG_HOTPLUG_CPU |
| 637 | static void remove_cache_info(struct sys_device *sysdev) | ||
| 638 | { | ||
| 639 | struct kobject *cache_toplevel; | ||
| 640 | struct cache_desc *cache_desc; | ||
| 641 | int cpu = sysdev->id; | ||
| 642 | |||
| 643 | cache_desc = per_cpu(cache_desc, cpu); | ||
| 644 | if (cache_desc != NULL) { | ||
| 645 | sysfs_remove_file(&cache_desc->kobj, &cache_size_attr.attr); | ||
| 646 | sysfs_remove_file(&cache_desc->kobj, &cache_line_size_attr.attr); | ||
| 647 | sysfs_remove_file(&cache_desc->kobj, &cache_type_attr.attr); | ||
| 648 | sysfs_remove_file(&cache_desc->kobj, &cache_level_attr.attr); | ||
| 649 | sysfs_remove_file(&cache_desc->kobj, &cache_nr_sets_attr.attr); | ||
| 650 | sysfs_remove_file(&cache_desc->kobj, &cache_assoc_attr.attr); | ||
| 651 | |||
| 652 | kobject_put(&cache_desc->kobj); | ||
| 653 | } | ||
| 654 | cache_toplevel = per_cpu(cache_toplevel, cpu); | ||
| 655 | if (cache_toplevel != NULL) | ||
| 656 | kobject_put(cache_toplevel); | ||
| 657 | } | ||
| 658 | |||
| 352 | static void unregister_cpu_online(unsigned int cpu) | 659 | static void unregister_cpu_online(unsigned int cpu) |
| 353 | { | 660 | { |
| 354 | struct cpu *c = &per_cpu(cpu_devices, cpu); | 661 | struct cpu *c = &per_cpu(cpu_devices, cpu); |
| @@ -399,6 +706,8 @@ static void unregister_cpu_online(unsigned int cpu) | |||
| 399 | 706 | ||
| 400 | if (cpu_has_feature(CPU_FTR_DSCR)) | 707 | if (cpu_has_feature(CPU_FTR_DSCR)) |
| 401 | sysdev_remove_file(s, &attr_dscr); | 708 | sysdev_remove_file(s, &attr_dscr); |
| 709 | |||
| 710 | remove_cache_info(s); | ||
| 402 | } | 711 | } |
| 403 | #endif /* CONFIG_HOTPLUG_CPU */ | 712 | #endif /* CONFIG_HOTPLUG_CPU */ |
| 404 | 713 | ||
