diff options
-rw-r--r-- | include/linux/ftrace.h | 1 | ||||
-rw-r--r-- | kernel/trace/ftrace.c | 76 |
2 files changed, 47 insertions, 30 deletions
diff --git a/include/linux/ftrace.h b/include/linux/ftrace.h index 623819433ed..20e14d0093c 100644 --- a/include/linux/ftrace.h +++ b/include/linux/ftrace.h | |||
@@ -49,6 +49,7 @@ enum { | |||
49 | FTRACE_FL_FILTER = (1 << 2), | 49 | FTRACE_FL_FILTER = (1 << 2), |
50 | FTRACE_FL_ENABLED = (1 << 3), | 50 | FTRACE_FL_ENABLED = (1 << 3), |
51 | FTRACE_FL_NOTRACE = (1 << 4), | 51 | FTRACE_FL_NOTRACE = (1 << 4), |
52 | FTRACE_FL_CONVERTED = (1 << 5), | ||
52 | }; | 53 | }; |
53 | 54 | ||
54 | struct dyn_ftrace { | 55 | struct dyn_ftrace { |
diff --git a/kernel/trace/ftrace.c b/kernel/trace/ftrace.c index f762f5a2d33..ec54cb7d69d 100644 --- a/kernel/trace/ftrace.c +++ b/kernel/trace/ftrace.c | |||
@@ -216,6 +216,12 @@ ftrace_add_hash(struct dyn_ftrace *node, unsigned long key) | |||
216 | hlist_add_head_rcu(&node->node, &ftrace_hash[key]); | 216 | hlist_add_head_rcu(&node->node, &ftrace_hash[key]); |
217 | } | 217 | } |
218 | 218 | ||
219 | /* called from kstop_machine */ | ||
220 | static inline void ftrace_del_hash(struct dyn_ftrace *node) | ||
221 | { | ||
222 | hlist_del(&node->node); | ||
223 | } | ||
224 | |||
219 | static void ftrace_free_rec(struct dyn_ftrace *rec) | 225 | static void ftrace_free_rec(struct dyn_ftrace *rec) |
220 | { | 226 | { |
221 | /* no locking, only called from kstop_machine */ | 227 | /* no locking, only called from kstop_machine */ |
@@ -332,12 +338,11 @@ ftrace_record_ip(unsigned long ip) | |||
332 | #define FTRACE_ADDR ((long)(ftrace_caller)) | 338 | #define FTRACE_ADDR ((long)(ftrace_caller)) |
333 | #define MCOUNT_ADDR ((long)(mcount)) | 339 | #define MCOUNT_ADDR ((long)(mcount)) |
334 | 340 | ||
335 | static void | 341 | static int |
336 | __ftrace_replace_code(struct dyn_ftrace *rec, | 342 | __ftrace_replace_code(struct dyn_ftrace *rec, |
337 | unsigned char *old, unsigned char *new, int enable) | 343 | unsigned char *old, unsigned char *new, int enable) |
338 | { | 344 | { |
339 | unsigned long ip, fl; | 345 | unsigned long ip, fl; |
340 | int failed; | ||
341 | 346 | ||
342 | ip = rec->ip; | 347 | ip = rec->ip; |
343 | 348 | ||
@@ -364,7 +369,7 @@ __ftrace_replace_code(struct dyn_ftrace *rec, | |||
364 | 369 | ||
365 | if ((fl == (FTRACE_FL_FILTER | FTRACE_FL_ENABLED)) || | 370 | if ((fl == (FTRACE_FL_FILTER | FTRACE_FL_ENABLED)) || |
366 | (fl == 0) || (rec->flags & FTRACE_FL_NOTRACE)) | 371 | (fl == 0) || (rec->flags & FTRACE_FL_NOTRACE)) |
367 | return; | 372 | return 0; |
368 | 373 | ||
369 | /* | 374 | /* |
370 | * If it is enabled disable it, | 375 | * If it is enabled disable it, |
@@ -388,7 +393,7 @@ __ftrace_replace_code(struct dyn_ftrace *rec, | |||
388 | */ | 393 | */ |
389 | fl = rec->flags & (FTRACE_FL_NOTRACE | FTRACE_FL_ENABLED); | 394 | fl = rec->flags & (FTRACE_FL_NOTRACE | FTRACE_FL_ENABLED); |
390 | if (fl == FTRACE_FL_NOTRACE) | 395 | if (fl == FTRACE_FL_NOTRACE) |
391 | return; | 396 | return 0; |
392 | 397 | ||
393 | new = ftrace_call_replace(ip, FTRACE_ADDR); | 398 | new = ftrace_call_replace(ip, FTRACE_ADDR); |
394 | } else | 399 | } else |
@@ -396,34 +401,24 @@ __ftrace_replace_code(struct dyn_ftrace *rec, | |||
396 | 401 | ||
397 | if (enable) { | 402 | if (enable) { |
398 | if (rec->flags & FTRACE_FL_ENABLED) | 403 | if (rec->flags & FTRACE_FL_ENABLED) |
399 | return; | 404 | return 0; |
400 | rec->flags |= FTRACE_FL_ENABLED; | 405 | rec->flags |= FTRACE_FL_ENABLED; |
401 | } else { | 406 | } else { |
402 | if (!(rec->flags & FTRACE_FL_ENABLED)) | 407 | if (!(rec->flags & FTRACE_FL_ENABLED)) |
403 | return; | 408 | return 0; |
404 | rec->flags &= ~FTRACE_FL_ENABLED; | 409 | rec->flags &= ~FTRACE_FL_ENABLED; |
405 | } | 410 | } |
406 | } | 411 | } |
407 | 412 | ||
408 | failed = ftrace_modify_code(ip, old, new); | 413 | return ftrace_modify_code(ip, old, new); |
409 | if (failed) { | ||
410 | unsigned long key; | ||
411 | /* It is possible that the function hasn't been converted yet */ | ||
412 | key = hash_long(ip, FTRACE_HASHBITS); | ||
413 | if (!ftrace_ip_in_hash(ip, key)) { | ||
414 | rec->flags |= FTRACE_FL_FAILED; | ||
415 | ftrace_free_rec(rec); | ||
416 | } | ||
417 | |||
418 | } | ||
419 | } | 414 | } |
420 | 415 | ||
421 | static void ftrace_replace_code(int enable) | 416 | static void ftrace_replace_code(int enable) |
422 | { | 417 | { |
418 | int i, failed; | ||
423 | unsigned char *new = NULL, *old = NULL; | 419 | unsigned char *new = NULL, *old = NULL; |
424 | struct dyn_ftrace *rec; | 420 | struct dyn_ftrace *rec; |
425 | struct ftrace_page *pg; | 421 | struct ftrace_page *pg; |
426 | int i; | ||
427 | 422 | ||
428 | if (enable) | 423 | if (enable) |
429 | old = ftrace_nop_replace(); | 424 | old = ftrace_nop_replace(); |
@@ -438,7 +433,15 @@ static void ftrace_replace_code(int enable) | |||
438 | if (rec->flags & FTRACE_FL_FAILED) | 433 | if (rec->flags & FTRACE_FL_FAILED) |
439 | continue; | 434 | continue; |
440 | 435 | ||
441 | __ftrace_replace_code(rec, old, new, enable); | 436 | failed = __ftrace_replace_code(rec, old, new, enable); |
437 | if (failed && (rec->flags & FTRACE_FL_CONVERTED)) { | ||
438 | rec->flags |= FTRACE_FL_FAILED; | ||
439 | if ((system_state == SYSTEM_BOOTING) || | ||
440 | !kernel_text_address(rec->ip)) { | ||
441 | ftrace_del_hash(rec); | ||
442 | ftrace_free_rec(rec); | ||
443 | } | ||
444 | } | ||
442 | } | 445 | } |
443 | } | 446 | } |
444 | } | 447 | } |
@@ -467,7 +470,6 @@ ftrace_code_disable(struct dyn_ftrace *rec) | |||
467 | failed = ftrace_modify_code(ip, call, nop); | 470 | failed = ftrace_modify_code(ip, call, nop); |
468 | if (failed) { | 471 | if (failed) { |
469 | rec->flags |= FTRACE_FL_FAILED; | 472 | rec->flags |= FTRACE_FL_FAILED; |
470 | ftrace_free_rec(rec); | ||
471 | return 0; | 473 | return 0; |
472 | } | 474 | } |
473 | return 1; | 475 | return 1; |
@@ -621,8 +623,7 @@ unsigned long ftrace_update_tot_cnt; | |||
621 | static int __ftrace_update_code(void *ignore) | 623 | static int __ftrace_update_code(void *ignore) |
622 | { | 624 | { |
623 | struct dyn_ftrace *p; | 625 | struct dyn_ftrace *p; |
624 | struct hlist_head head; | 626 | struct hlist_node *t, *n; |
625 | struct hlist_node *t; | ||
626 | int save_ftrace_enabled; | 627 | int save_ftrace_enabled; |
627 | cycle_t start, stop; | 628 | cycle_t start, stop; |
628 | int i; | 629 | int i; |
@@ -637,18 +638,33 @@ static int __ftrace_update_code(void *ignore) | |||
637 | 638 | ||
638 | /* No locks needed, the machine is stopped! */ | 639 | /* No locks needed, the machine is stopped! */ |
639 | for (i = 0; i < FTRACE_HASHSIZE; i++) { | 640 | for (i = 0; i < FTRACE_HASHSIZE; i++) { |
640 | if (hlist_empty(&ftrace_hash[i])) | 641 | /* all CPUS are stopped, we are safe to modify code */ |
641 | continue; | 642 | hlist_for_each_entry_safe(p, t, n, &ftrace_hash[i], node) { |
643 | /* Skip over failed records which have not been | ||
644 | * freed. */ | ||
645 | if (p->flags & FTRACE_FL_FAILED) | ||
646 | continue; | ||
642 | 647 | ||
643 | head = ftrace_hash[i]; | 648 | /* Unconverted records are always at the head of the |
644 | INIT_HLIST_HEAD(&ftrace_hash[i]); | 649 | * hash bucket. Once we encounter a converted record, |
650 | * simply skip over to the next bucket. Saves ftraced | ||
651 | * some processor cycles (ftrace does its bid for | ||
652 | * global warming :-p ). */ | ||
653 | if (p->flags & (FTRACE_FL_CONVERTED)) | ||
654 | break; | ||
645 | 655 | ||
646 | /* all CPUS are stopped, we are safe to modify code */ | 656 | if (ftrace_code_disable(p)) { |
647 | hlist_for_each_entry(p, t, &head, node) { | 657 | p->flags |= FTRACE_FL_CONVERTED; |
648 | if (ftrace_code_disable(p)) | ||
649 | ftrace_update_cnt++; | 658 | ftrace_update_cnt++; |
650 | } | 659 | } else { |
660 | if ((system_state == SYSTEM_BOOTING) || | ||
661 | !kernel_text_address(p->ip)) { | ||
662 | ftrace_del_hash(p); | ||
663 | ftrace_free_rec(p); | ||
651 | 664 | ||
665 | } | ||
666 | } | ||
667 | } | ||
652 | } | 668 | } |
653 | 669 | ||
654 | stop = ftrace_now(raw_smp_processor_id()); | 670 | stop = ftrace_now(raw_smp_processor_id()); |