diff options
author | Nicolas Pitre <nicolas.pitre@linaro.org> | 2012-11-22 00:05:07 -0500 |
---|---|---|
committer | Nicolas Pitre <nicolas.pitre@linaro.org> | 2013-07-30 09:02:17 -0400 |
commit | 6b7437aed1568076cefa4d42747b1515dcb848db (patch) | |
tree | d3fddd017cd53b53bf43f3bf9f8c0dabb9452795 /arch/arm/common | |
parent | ed96762e3241f57aa812977cf1920d3ee0363f4d (diff) |
ARM: bL_switcher: ability to enable and disable the switcher via sysfs
The /sys/kernel/bL_switcher/enable file allows to enable or disable
the switcher by writing 1 or 0 to it respectively. It is still enabled
by default on boot.
Signed-off-by: Nicolas Pitre <nico@linaro.org>
Diffstat (limited to 'arch/arm/common')
-rw-r--r-- | arch/arm/common/bL_switcher.c | 171 |
1 files changed, 160 insertions, 11 deletions
diff --git a/arch/arm/common/bL_switcher.c b/arch/arm/common/bL_switcher.c index 1c2e5bcfb1f7..395f60f6292b 100644 --- a/arch/arm/common/bL_switcher.c +++ b/arch/arm/common/bL_switcher.c | |||
@@ -24,6 +24,7 @@ | |||
24 | #include <linux/tick.h> | 24 | #include <linux/tick.h> |
25 | #include <linux/mm.h> | 25 | #include <linux/mm.h> |
26 | #include <linux/string.h> | 26 | #include <linux/string.h> |
27 | #include <linux/sysfs.h> | ||
27 | #include <linux/irqchip/arm-gic.h> | 28 | #include <linux/irqchip/arm-gic.h> |
28 | 29 | ||
29 | #include <asm/smp_plat.h> | 30 | #include <asm/smp_plat.h> |
@@ -220,6 +221,7 @@ struct bL_thread { | |||
220 | struct task_struct *task; | 221 | struct task_struct *task; |
221 | wait_queue_head_t wq; | 222 | wait_queue_head_t wq; |
222 | int wanted_cluster; | 223 | int wanted_cluster; |
224 | struct completion started; | ||
223 | }; | 225 | }; |
224 | 226 | ||
225 | static struct bL_thread bL_threads[NR_CPUS]; | 227 | static struct bL_thread bL_threads[NR_CPUS]; |
@@ -231,6 +233,7 @@ static int bL_switcher_thread(void *arg) | |||
231 | int cluster; | 233 | int cluster; |
232 | 234 | ||
233 | sched_setscheduler_nocheck(current, SCHED_FIFO, ¶m); | 235 | sched_setscheduler_nocheck(current, SCHED_FIFO, ¶m); |
236 | complete(&t->started); | ||
234 | 237 | ||
235 | do { | 238 | do { |
236 | if (signal_pending(current)) | 239 | if (signal_pending(current)) |
@@ -246,7 +249,7 @@ static int bL_switcher_thread(void *arg) | |||
246 | return 0; | 249 | return 0; |
247 | } | 250 | } |
248 | 251 | ||
249 | static struct task_struct * __init bL_switcher_thread_create(int cpu, void *arg) | 252 | static struct task_struct *bL_switcher_thread_create(int cpu, void *arg) |
250 | { | 253 | { |
251 | struct task_struct *task; | 254 | struct task_struct *task; |
252 | 255 | ||
@@ -295,9 +298,11 @@ EXPORT_SYMBOL_GPL(bL_switch_request); | |||
295 | * Activation and configuration code. | 298 | * Activation and configuration code. |
296 | */ | 299 | */ |
297 | 300 | ||
301 | static unsigned int bL_switcher_active; | ||
302 | static unsigned int bL_switcher_cpu_original_cluster[MAX_CPUS_PER_CLUSTER]; | ||
298 | static cpumask_t bL_switcher_removed_logical_cpus; | 303 | static cpumask_t bL_switcher_removed_logical_cpus; |
299 | 304 | ||
300 | static void __init bL_switcher_restore_cpus(void) | 305 | static void bL_switcher_restore_cpus(void) |
301 | { | 306 | { |
302 | int i; | 307 | int i; |
303 | 308 | ||
@@ -305,7 +310,7 @@ static void __init bL_switcher_restore_cpus(void) | |||
305 | cpu_up(i); | 310 | cpu_up(i); |
306 | } | 311 | } |
307 | 312 | ||
308 | static int __init bL_switcher_halve_cpus(void) | 313 | static int bL_switcher_halve_cpus(void) |
309 | { | 314 | { |
310 | int cpu, cluster, i, ret; | 315 | int cpu, cluster, i, ret; |
311 | cpumask_t cluster_mask[2], common_mask; | 316 | cpumask_t cluster_mask[2], common_mask; |
@@ -349,8 +354,10 @@ static int __init bL_switcher_halve_cpus(void) | |||
349 | * is equal to their physical CPU number. This is | 354 | * is equal to their physical CPU number. This is |
350 | * not perfect but good enough for now. | 355 | * not perfect but good enough for now. |
351 | */ | 356 | */ |
352 | if (cpu == i) | 357 | if (cpu == i) { |
358 | bL_switcher_cpu_original_cluster[cpu] = cluster; | ||
353 | continue; | 359 | continue; |
360 | } | ||
354 | } | 361 | } |
355 | 362 | ||
356 | ret = cpu_down(i); | 363 | ret = cpu_down(i); |
@@ -364,18 +371,18 @@ static int __init bL_switcher_halve_cpus(void) | |||
364 | return 0; | 371 | return 0; |
365 | } | 372 | } |
366 | 373 | ||
367 | static int __init bL_switcher_init(void) | 374 | static int bL_switcher_enable(void) |
368 | { | 375 | { |
369 | int cpu, ret; | 376 | int cpu, ret; |
370 | 377 | ||
371 | pr_info("big.LITTLE switcher initializing\n"); | 378 | cpu_hotplug_driver_lock(); |
372 | 379 | if (bL_switcher_active) { | |
373 | if (MAX_NR_CLUSTERS != 2) { | 380 | cpu_hotplug_driver_unlock(); |
374 | pr_err("%s: only dual cluster systems are supported\n", __func__); | 381 | return 0; |
375 | return -EINVAL; | ||
376 | } | 382 | } |
377 | 383 | ||
378 | cpu_hotplug_driver_lock(); | 384 | pr_info("big.LITTLE switcher initializing\n"); |
385 | |||
379 | ret = bL_switcher_halve_cpus(); | 386 | ret = bL_switcher_halve_cpus(); |
380 | if (ret) { | 387 | if (ret) { |
381 | cpu_hotplug_driver_unlock(); | 388 | cpu_hotplug_driver_unlock(); |
@@ -385,13 +392,155 @@ static int __init bL_switcher_init(void) | |||
385 | for_each_online_cpu(cpu) { | 392 | for_each_online_cpu(cpu) { |
386 | struct bL_thread *t = &bL_threads[cpu]; | 393 | struct bL_thread *t = &bL_threads[cpu]; |
387 | init_waitqueue_head(&t->wq); | 394 | init_waitqueue_head(&t->wq); |
395 | init_completion(&t->started); | ||
388 | t->wanted_cluster = -1; | 396 | t->wanted_cluster = -1; |
389 | t->task = bL_switcher_thread_create(cpu, t); | 397 | t->task = bL_switcher_thread_create(cpu, t); |
390 | } | 398 | } |
399 | |||
400 | bL_switcher_active = 1; | ||
391 | cpu_hotplug_driver_unlock(); | 401 | cpu_hotplug_driver_unlock(); |
392 | 402 | ||
393 | pr_info("big.LITTLE switcher initialized\n"); | 403 | pr_info("big.LITTLE switcher initialized\n"); |
394 | return 0; | 404 | return 0; |
395 | } | 405 | } |
396 | 406 | ||
407 | #ifdef CONFIG_SYSFS | ||
408 | |||
409 | static void bL_switcher_disable(void) | ||
410 | { | ||
411 | unsigned int cpu, cluster, i; | ||
412 | struct bL_thread *t; | ||
413 | struct task_struct *task; | ||
414 | |||
415 | cpu_hotplug_driver_lock(); | ||
416 | if (!bL_switcher_active) { | ||
417 | cpu_hotplug_driver_unlock(); | ||
418 | return; | ||
419 | } | ||
420 | bL_switcher_active = 0; | ||
421 | |||
422 | /* | ||
423 | * To deactivate the switcher, we must shut down the switcher | ||
424 | * threads to prevent any other requests from being accepted. | ||
425 | * Then, if the final cluster for given logical CPU is not the | ||
426 | * same as the original one, we'll recreate a switcher thread | ||
427 | * just for the purpose of switching the CPU back without any | ||
428 | * possibility for interference from external requests. | ||
429 | */ | ||
430 | for_each_online_cpu(cpu) { | ||
431 | BUG_ON(cpu != (cpu_logical_map(cpu) & 0xff)); | ||
432 | t = &bL_threads[cpu]; | ||
433 | task = t->task; | ||
434 | t->task = NULL; | ||
435 | if (!task || IS_ERR(task)) | ||
436 | continue; | ||
437 | kthread_stop(task); | ||
438 | /* no more switch may happen on this CPU at this point */ | ||
439 | cluster = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1); | ||
440 | if (cluster == bL_switcher_cpu_original_cluster[cpu]) | ||
441 | continue; | ||
442 | init_completion(&t->started); | ||
443 | t->wanted_cluster = bL_switcher_cpu_original_cluster[cpu]; | ||
444 | task = bL_switcher_thread_create(cpu, t); | ||
445 | if (!IS_ERR(task)) { | ||
446 | wait_for_completion(&t->started); | ||
447 | kthread_stop(task); | ||
448 | cluster = MPIDR_AFFINITY_LEVEL(cpu_logical_map(cpu), 1); | ||
449 | if (cluster == bL_switcher_cpu_original_cluster[cpu]) | ||
450 | continue; | ||
451 | } | ||
452 | /* If execution gets here, we're in trouble. */ | ||
453 | pr_crit("%s: unable to restore original cluster for CPU %d\n", | ||
454 | __func__, cpu); | ||
455 | for_each_cpu(i, &bL_switcher_removed_logical_cpus) { | ||
456 | if ((cpu_logical_map(i) & 0xff) != cpu) | ||
457 | continue; | ||
458 | pr_crit("%s: CPU %d can't be restored\n", | ||
459 | __func__, i); | ||
460 | cpumask_clear_cpu(i, &bL_switcher_removed_logical_cpus); | ||
461 | break; | ||
462 | } | ||
463 | } | ||
464 | |||
465 | bL_switcher_restore_cpus(); | ||
466 | cpu_hotplug_driver_unlock(); | ||
467 | } | ||
468 | |||
469 | static ssize_t bL_switcher_active_show(struct kobject *kobj, | ||
470 | struct kobj_attribute *attr, char *buf) | ||
471 | { | ||
472 | return sprintf(buf, "%u\n", bL_switcher_active); | ||
473 | } | ||
474 | |||
475 | static ssize_t bL_switcher_active_store(struct kobject *kobj, | ||
476 | struct kobj_attribute *attr, const char *buf, size_t count) | ||
477 | { | ||
478 | int ret; | ||
479 | |||
480 | switch (buf[0]) { | ||
481 | case '0': | ||
482 | bL_switcher_disable(); | ||
483 | ret = 0; | ||
484 | break; | ||
485 | case '1': | ||
486 | ret = bL_switcher_enable(); | ||
487 | break; | ||
488 | default: | ||
489 | ret = -EINVAL; | ||
490 | } | ||
491 | |||
492 | return (ret >= 0) ? count : ret; | ||
493 | } | ||
494 | |||
495 | static struct kobj_attribute bL_switcher_active_attr = | ||
496 | __ATTR(active, 0644, bL_switcher_active_show, bL_switcher_active_store); | ||
497 | |||
498 | static struct attribute *bL_switcher_attrs[] = { | ||
499 | &bL_switcher_active_attr.attr, | ||
500 | NULL, | ||
501 | }; | ||
502 | |||
503 | static struct attribute_group bL_switcher_attr_group = { | ||
504 | .attrs = bL_switcher_attrs, | ||
505 | }; | ||
506 | |||
507 | static struct kobject *bL_switcher_kobj; | ||
508 | |||
509 | static int __init bL_switcher_sysfs_init(void) | ||
510 | { | ||
511 | int ret; | ||
512 | |||
513 | bL_switcher_kobj = kobject_create_and_add("bL_switcher", kernel_kobj); | ||
514 | if (!bL_switcher_kobj) | ||
515 | return -ENOMEM; | ||
516 | ret = sysfs_create_group(bL_switcher_kobj, &bL_switcher_attr_group); | ||
517 | if (ret) | ||
518 | kobject_put(bL_switcher_kobj); | ||
519 | return ret; | ||
520 | } | ||
521 | |||
522 | #endif /* CONFIG_SYSFS */ | ||
523 | |||
524 | static int __init bL_switcher_init(void) | ||
525 | { | ||
526 | int ret; | ||
527 | |||
528 | if (MAX_NR_CLUSTERS != 2) { | ||
529 | pr_err("%s: only dual cluster systems are supported\n", __func__); | ||
530 | return -EINVAL; | ||
531 | } | ||
532 | |||
533 | ret = bL_switcher_enable(); | ||
534 | if (ret) | ||
535 | return ret; | ||
536 | |||
537 | #ifdef CONFIG_SYSFS | ||
538 | ret = bL_switcher_sysfs_init(); | ||
539 | if (ret) | ||
540 | pr_err("%s: unable to create sysfs entry\n", __func__); | ||
541 | #endif | ||
542 | |||
543 | return 0; | ||
544 | } | ||
545 | |||
397 | late_initcall(bL_switcher_init); | 546 | late_initcall(bL_switcher_init); |