>From a897e7492bbf0fe4f225e3f2aaf8b99f6ba91ba9 Mon Sep 17 00:00:00 2001
From: qiaonuohan <qiaonuohan@cn.fujitsu.com>
Date: Thu, 28 Feb 2013 15:20:06 +0800
Subject: [PATCH 2/2] hrtimer: add -r option to command timer

command timer -r is used to display all hrtimers and their related
information.

e.g.
crash> timer -r
UPTIME: 101476365(1000HZ)

cpu: 0  HRTIMER_CPU_BASE: ffff88004fc0e740
  clock: 0  HRTIMER_CLOCK_BASE: ffff88004fc0e780
    OFFSET:     0
    GET_TIME:   ktime_get
    ACTIVE TIMERS:
                  EXPIRES                  HRTIMER           FUNCTION
      101476368000000-101476368000000  ffff88004fc0e960  ffffffff810bdf70  <tick_sched_timer>
      101476483197066-101476542940065  ffff88004b19dd78  ffffffff810892b0  <hrtimer_wakeup>
      101477000000000-101477000000000  ffffffff81dbcca8  ffffffff810a5da0  <sched_rt_period_timer>
      101477040836262-101477040886262  ffff88001c3bfe98  ffffffff810892b0  <hrtimer_wakeup>
      101480042000000-101480042000000  ffff88004fc0eae0  ffffffff810ef660  <watchdog_timer_fn>
      101483658577100-101483658627100  ffff88004b2bfe98  ffffffff810892b0  <hrtimer_wakeup>
      101493474313352-101493504313351  ffff88004b67fa08  ffffffff810892b0  <hrtimer_wakeup>
      101500659099107-101500718679106  ffff880018cd5a08  ffffffff810892b0  <hrtimer_wakeup>
      101501914925514-101501914975514  ffff880049409e98  ffffffff810892b0  <hrtimer_wakeup>
      101504763551291-101504763601291  ffff88004b853e98  ffffffff810892b0  <hrtimer_wakeup>
      101568283611369-101568383611369  ffff88001315ba08  ffffffff810892b0  <hrtimer_wakeup>
      101666437688089-101666537688089  ffff88000244da08  ffffffff810892b0  <hrtimer_wakeup>
      103161659082464-103161759082464  ffff880018a9da08  ffffffff810892b0  <hrtimer_wakeup>
      103654004944707-103654104944707  ffff88004a83da08  ffffffff810892b0  <hrtimer_wakeup>
      104537771975347-104537772025347  ffff880037bd3e98  ffffffff810892b0  <hrtimer_wakeup>
      115344661345131-115344661395131  ffff88004c5b1e98  ffffffff810892b0  <hrtimer_wakeup>
      167961374089142-167961474089142  ffff88000263ba08  ffffffff810892b0  <hrtimer_wakeup>

  clock: 1  HRTIMER_CLOCK_BASE: ffff88004fc0e7c0
    OFFSET:     1361933899341424878
    GET_TIME:   ktime_get_real
    ACTIVE TIMERS:
                      EXPIRES                      HRTIMER           FUNCTION
      1362035378304824000-1362035378304874000  ffff880049b23e18  ffffffff810892b0  <hrtimer_wakeup>
      1362035413608278294-1362035413608328294  ffff8800167b3e18  ffffffff810892b0  <hrtimer_wakeup>

  clock: 2  HRTIMER_CLOCK_BASE: ffff88004fc0e800
    OFFSET:     0
    GET_TIME:   ktime_get_boottime
    ACTIVE TIMERS:
       EXPIRES      HRTIMER           FUNCTION
      (empty)

cpu: 1  HRTIMER_CPU_BASE: ffff88004fd0e740
  clock: 0  HRTIMER_CLOCK_BASE: ffff88004fd0e780
    OFFSET:     0
    GET_TIME:   ktime_get
    ACTIVE TIMERS:
                  EXPIRES                  HRTIMER           FUNCTION
      101476375000000-101476375000000  ffff88004fd0e960  ffffffff810bdf70  <tick_sched_timer>
      101477437633959-101477441627957  ffff8800131b1a08  ffffffff810892b0  <hrtimer_wakeup>
      101477659214391-101477661211390  ffff88000f5afa08  ffffffff810892b0  <hrtimer_wakeup>
      101477659425626-101477689395624  ffff88004d5b9a08  ffffffff810892b0  <hrtimer_wakeup>
      101478437554506-101478441548504  ffff880002a55a08  ffffffff810892b0  <hrtimer_wakeup>
...
---
 defs.h   |   22 ++++
 help.c   |   82 ++++++++++++-
 kernel.c |  388 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++-
 3 files changed, 484 insertions(+), 8 deletions(-)

diff --git a/defs.h b/defs.h
index 2993f2b..66e75e4 100755
--- a/defs.h
+++ b/defs.h
@@ -1848,6 +1848,26 @@ struct offset_table {                    /* stash of commonly-used offsets */
 	long vmap_area_list;
 	long vmap_area_flags;
 	long vmap_area_vm;
+	long hrtimer_cpu_base_clock_base;
+	long hrtimer_clock_base_offset;
+	long hrtimer_clock_base_active;
+	long hrtimer_clock_base_first;
+	long hrtimer_clock_base_get_time;
+	long hrtimer_base_active;
+	long hrtimer_base_first;
+	long hrtimer_base_pending;
+	long hrtimer_base_get_time;
+	long hrtimer_node;
+	long hrtimer_list;
+	long hrtimer_softexpires;
+	long hrtimer_expires;
+	long hrtimer_function;
+	long timerqueue_head_next;
+	long timerqueue_node_expires;
+	long timerqueue_node_node;
+	long ktime_t_tv64;
+	long ktime_t_sec;
+	long ktime_t_nsec;
 };
 
 struct size_table {         /* stash of commonly-used sizes */
@@ -1985,6 +2005,8 @@ struct size_table {         /* stash of commonly-used sizes */
 	long rt_rq;
 	long task_group;
 	long vmap_area;
+	long hrtimer_clock_base;
+	long hrtimer_base;
 };
 
 struct array_table {
diff --git a/help.c b/help.c
index c542743..9b106ec 100755
--- a/help.c
+++ b/help.c
@@ -2154,13 +2154,17 @@ NULL
 
 char *help_timer[] = {
 "timer",
-"timer queue data",
-" ",
-"  This command displays the timer queue entries, both old- and new-style,",
-"  in chronological order.  In the case of the old-style timers, the",
-"  timer_table array index is shown; in the case of the new-style timers, ",
-"  the timer_list address is shown.  On later kernels, the timer data is",
-"  per-cpu.",
+"timer data",
+"[-r]"
+" ",
+"  This command provides information of timer. With no arguments, the command",
+"  displays the timer queue entries, both old- and new-style, in chronological",
+"  order. In the case of the old-style timers, the timer_table array index is",
+"  shown; in the case of the new-style timers, the timer_list address is",
+"  shown. On later kernels, the timer data is per-cpu.",
+"  ",
+"  -r  shows all hrtimers and their related information. On earlier kernels, ",
+"      expire is an integer, and on later kernels, expire is a range.",
 "\nEXAMPLES",
 "    %s> timer",
 "    JIFFIES",
@@ -2218,6 +2222,72 @@ char *help_timer[] = {
 "    18277900   ceebaec0   c01232bb  <process_timeout>",
 "    18283769   cf739f64   c01232bb  <process_timeout>",
 "    18331902   cee8af64   c01232bb  <process_timeout>",
+" ",
+"  Display hrtimers on a 2-cpu system:\n",
+"    %s> timer -r",
+"    UPTIME: 101476365(1000HZ)",
+"    ",
+"    cpu: 0  HRTIMER_CPU_BASE: ffff88004fc0e740",
+"      clock: 0  HRTIMER_CLOCK_BASE: ffff88004fc0e780",
+"        OFFSET:     0",
+"        GET_TIME:   ktime_get",
+"        ACTIVE TIMERS:",
+"                      EXPIRES                  HRTIMER           FUNCTION",
+"          101476368000000-101476368000000  ffff88004fc0e960  ffffffff810bdf70  <tick_sched_timer>",
+"          101476483197066-101476542940065  ffff88004b19dd78  ffffffff810892b0  <hrtimer_wakeup>",
+"          101477000000000-101477000000000  ffffffff81dbcca8  ffffffff810a5da0  <sched_rt_period_timer>",
+"          101477040836262-101477040886262  ffff88001c3bfe98  ffffffff810892b0  <hrtimer_wakeup>",
+"          101480042000000-101480042000000  ffff88004fc0eae0  ffffffff810ef660  <watchdog_timer_fn>",
+"          101483658577100-101483658627100  ffff88004b2bfe98  ffffffff810892b0  <hrtimer_wakeup>",
+"          101493474313352-101493504313351  ffff88004b67fa08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101500659099107-101500718679106  ffff880018cd5a08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101501914925514-101501914975514  ffff880049409e98  ffffffff810892b0  <hrtimer_wakeup>",
+"          101504763551291-101504763601291  ffff88004b853e98  ffffffff810892b0  <hrtimer_wakeup>",
+"          101568283611369-101568383611369  ffff88001315ba08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101666437688089-101666537688089  ffff88000244da08  ffffffff810892b0  <hrtimer_wakeup>",
+"          103161659082464-103161759082464  ffff880018a9da08  ffffffff810892b0  <hrtimer_wakeup>",
+"          103654004944707-103654104944707  ffff88004a83da08  ffffffff810892b0  <hrtimer_wakeup>",
+"          104537771975347-104537772025347  ffff880037bd3e98  ffffffff810892b0  <hrtimer_wakeup>",
+"          115344661345131-115344661395131  ffff88004c5b1e98  ffffffff810892b0  <hrtimer_wakeup>",
+"          167961374089142-167961474089142  ffff88000263ba08  ffffffff810892b0  <hrtimer_wakeup>",
+"    ",
+"      clock: 1  HRTIMER_CLOCK_BASE: ffff88004fc0e7c0",
+"        OFFSET:     1361933899341424878",
+"        GET_TIME:   ktime_get_real",
+"        ACTIVE TIMERS:",
+"                          EXPIRES                      HRTIMER           FUNCTION",
+"          1362035378304824000-1362035378304874000  ffff880049b23e18  ffffffff810892b0  <hrtimer_wakeup>",
+"          1362035413608278294-1362035413608328294  ffff8800167b3e18  ffffffff810892b0  <hrtimer_wakeup>",
+"    ",
+"      clock: 2  HRTIMER_CLOCK_BASE: ffff88004fc0e800",
+"        OFFSET:     0",
+"        GET_TIME:   ktime_get_boottime",
+"        ACTIVE TIMERS:",
+"           EXPIRES      HRTIMER           FUNCTION",
+"          (empty)",
+"    ",
+"    cpu: 1  HRTIMER_CPU_BASE: ffff88004fd0e740",
+"      clock: 0  HRTIMER_CLOCK_BASE: ffff88004fd0e780",
+"        OFFSET:     0",
+"        GET_TIME:   ktime_get",
+"        ACTIVE TIMERS:",
+"                      EXPIRES                  HRTIMER           FUNCTION",
+"          101476375000000-101476375000000  ffff88004fd0e960  ffffffff810bdf70  <tick_sched_timer>",
+"          101477437633959-101477441627957  ffff8800131b1a08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101477659214391-101477661211390  ffff88000f5afa08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101477659425626-101477689395624  ffff88004d5b9a08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101478437554506-101478441548504  ffff880002a55a08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101479437513335-101479441507333  ffff88000fa09a08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101479438252334-101479448243333  ffff88000f5d7a08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101479437745656-101479467715653  ffff880002a19a08  ffffffff810892b0  <hrtimer_wakeup>",
+"          101479593440674-101479603440673  ffff880002801908  ffffffff810892b0  <hrtimer_wakeup>",
+"          101480055000000-101480055000000  ffff88004fd0eae0  ffffffff810ef660  <watchdog_timer_fn>",
+"          101516024553200-101516084553199  ffff88004845bd78  ffffffff810892b0  <hrtimer_wakeup>",
+"          101549823430729-101549923430729  ffff88004b26fd78  ffffffff810892b0  <hrtimer_wakeup>",
+"          101556024454821-101556124454820  ffff88004b9bfd78  ffffffff810892b0  <hrtimer_wakeup>",
+"          101582823375135-101582823375135  ffff8800379ba0f8  ffffffff81064bc0  <it_real_fn>",
+"          101789024549606-101789024549606  ffff88004ac0aa78  ffffffff81064bc0  <it_real_fn>",
+"    ...",
 NULL               
 };
 
diff --git a/kernel.c b/kernel.c
index c420518..2f96383 100755
--- a/kernel.c
+++ b/kernel.c
@@ -34,6 +34,13 @@ static void display_bh_1(void);
 static void display_bh_2(void);
 static void display_bh_3(void);
 static void display_bh_4(void);
+static void dump_hrtimer_data(void);
+static void dump_hrtimer_clock_base(const void *, const int);
+static void dump_hrtimer_base(const void *, const int);
+static void dump_active_timers(const void *);
+static int get_expires_len(const int, const ulong *);
+static void print_timer(const void *);
+static ulonglong ktime_to_ns(const void *);
 static void dump_timer_data(void);
 static void dump_timer_data_tvec_bases_v1(void);
 static void dump_timer_data_tvec_bases_v2(void);
@@ -638,6 +645,51 @@ kernel_init()
 	BUG_bytes_init();
 	
 	kt->flags &= ~PRE_KERNEL_INIT;
+
+	/*
+	 *  for hrtimer
+	 */
+	MEMBER_OFFSET_INIT(hrtimer_cpu_base_clock_base, "hrtimer_cpu_base",
+		"clock_base");
+	MEMBER_OFFSET_INIT(hrtimer_clock_base_offset, "hrtimer_clock_base",
+		"offset");
+	MEMBER_OFFSET_INIT(hrtimer_clock_base_active, "hrtimer_clock_base",
+		"active");
+	MEMBER_OFFSET_INIT(hrtimer_clock_base_first, "hrtimer_clock_base",
+		"first");
+	MEMBER_OFFSET_INIT(hrtimer_clock_base_get_time, "hrtimer_clock_base",
+		"get_time");
+	MEMBER_OFFSET_INIT(hrtimer_base_active, "hrtimer_base", "active");
+	MEMBER_OFFSET_INIT(hrtimer_base_first, "hrtimer_base", "first");
+	MEMBER_OFFSET_INIT(hrtimer_base_pending, "hrtimer_base", "pending");
+	MEMBER_OFFSET_INIT(hrtimer_base_get_time, "hrtimer_base", "get_time");
+	MEMBER_OFFSET_INIT(hrtimer_node, "hrtimer", "node");
+	MEMBER_OFFSET_INIT(hrtimer_list, "hrtimer", "list");
+	MEMBER_OFFSET_INIT(hrtimer_expires, "hrtimer", "expires");
+	if (INVALID_MEMBER(hrtimer_expires))
+		MEMBER_OFFSET_INIT(hrtimer_expires, "hrtimer", "_expires");
+	if (INVALID_MEMBER(hrtimer_expires)) {
+		MEMBER_OFFSET_INIT(timerqueue_head_next, "timerqueue_head", "next");
+		MEMBER_OFFSET_INIT(timerqueue_node_expires, "timerqueue_node",
+			"expires");
+		MEMBER_OFFSET_INIT(timerqueue_node_node, "timerqueue_node_node",
+			"node");
+	}
+
+	MEMBER_OFFSET_INIT(hrtimer_softexpires, "hrtimer", "_softexpires");
+	MEMBER_OFFSET_INIT(hrtimer_function, "hrtimer", "function");
+	MEMBER_OFFSET_INIT(ktime_t_tv64, "ktime", "tv64");
+	if (INVALID_MEMBER(ktime_t_tv64))
+		MEMBER_OFFSET_INIT(ktime_t_tv64, "ktime_t", "tv64");
+	MEMBER_OFFSET_INIT(ktime_t_sec, "ktime", "sec");
+	if (INVALID_MEMBER(ktime_t_sec))
+		MEMBER_OFFSET_INIT(ktime_t_sec, "ktime_t", "sec");
+	MEMBER_OFFSET_INIT(ktime_t_nsec, "ktime", "nsec");
+	if (INVALID_MEMBER(ktime_t_nsec))
+		MEMBER_OFFSET_INIT(ktime_t_nsec, "ktime_t", "nsec");
+
+	STRUCT_SIZE_INIT(hrtimer_clock_base, "hrtimer_clock_base");
+	STRUCT_SIZE_INIT(hrtimer_base, "hrtimer_base");
 }
 
 /*
@@ -6333,10 +6385,17 @@ void
 cmd_timer(void)
 {
         int c;
+	int rflag;
 
-        while ((c = getopt(argcnt, args, "")) != EOF) {
+	rflag = 0;
+
+        while ((c = getopt(argcnt, args, "r")) != EOF) {
                 switch(c)
                 {
+		case 'r':
+			rflag = 1;
+			break;
+
                 default:
                         argerrs++;
                         break;
@@ -6346,7 +6405,332 @@ cmd_timer(void)
         if (argerrs)
                 cmd_usage(pc->curcmd, SYNOPSIS);
 
-	dump_timer_data();
+	if (rflag)
+		dump_hrtimer_data();
+	else
+		dump_timer_data();
+}
+
+static void
+dump_hrtimer_data(void)
+{
+	int i, j;
+	ulonglong jiffies_64;
+	int hrtimer_max_clock_bases, max_hrtimer_bases;
+	struct syment * hrtimer_bases;
+
+	hrtimer_max_clock_bases = 0;
+	max_hrtimer_bases = 0;
+
+	/* 
+	 * deside whether hrtimer is available and
+	 * set hrtimer_max_clock_bases or max_hrtimer_bases.
+	 * if both are not available, hrtimer is not available.
+	 */
+	if (VALID_SIZE(hrtimer_clock_base)) {
+		hrtimer_max_clock_bases = 2;
+		if (symbol_exists("ktime_get_boottime"))
+			hrtimer_max_clock_bases = 3;
+	} else if (VALID_SIZE(hrtimer_base)) {
+		max_hrtimer_bases = 2;
+	} else
+		command_not_supported();
+
+	/* get current time(uptime) */
+	get_uptime(NULL, &jiffies_64);
+	fprintf(fp, "UPTIME: %lld(%dHZ)\n\n", jiffies_64, machdep->hz);
+
+	hrtimer_bases = per_cpu_symbol_search("hrtimer_bases");
+	for (i = 0; i < kt->cpus; i++) {
+		if (i)
+			fprintf(fp, "\n");
+		fprintf(fp, "cpu: %d  ", i);
+		if (VALID_SIZE(hrtimer_clock_base)) {
+			fprintf(fp, "HRTIMER_CPU_BASE: %lx\n",
+				(ulong)(hrtimer_bases->value +
+				kt->__per_cpu_offset[i]));
+
+			for (j = 0; j < hrtimer_max_clock_bases; j++) {
+				if (j)
+					fprintf(fp, "\n");
+				dump_hrtimer_clock_base(
+					(void *)(hrtimer_bases->value) +
+					kt->__per_cpu_offset[i], j);
+			}
+		} else {
+			fprintf(fp, "HRTIMER_BASES[]: %lx\n",
+				(ulong)(hrtimer_bases->value) +
+				kt->__per_cpu_offset[i]);
+
+			for (j = 0; j < max_hrtimer_bases; j++) {
+				if (j)
+					fprintf(fp, "\n");
+				dump_hrtimer_base(
+					(void *)(hrtimer_bases->value) +
+					kt->__per_cpu_offset[i], j);
+			}
+		}
+	}
+}
+
+static int expires_len = VADDR_PRLEN;
+
+static void
+dump_hrtimer_clock_base(const void *hrtimer_bases, const int num)
+{
+	void *base;
+	ulonglong offset;
+	ulong get_time;
+	char buf[BUFSIZE];
+
+	base = (void *)hrtimer_bases + OFFSET(hrtimer_cpu_base_clock_base) +
+		SIZE(hrtimer_clock_base) * num;
+	fprintf(fp, "  clock: %d  HRTIMER_CLOCK_BASE: %lx\n", num, (ulong)base);
+
+	if (VALID_MEMBER(hrtimer_clock_base_offset)) {
+		offset = ktime_to_ns(base + OFFSET(hrtimer_clock_base_offset));
+		fprintf(fp, "    OFFSET:\t%lld\n", offset);
+	}
+
+	readmem((ulong)(base + OFFSET(hrtimer_clock_base_get_time)), KVADDR,
+		&get_time, sizeof(get_time), "hrtimer_clock_base get_time",
+		FAULT_ON_ERROR);
+
+	fprintf(fp ,"    GET_TIME:\t%s\n", value_to_symstr(get_time, buf, 0));
+
+	dump_active_timers(base);
+}
+
+static void
+dump_hrtimer_base(const void *hrtimer_bases, const int num)
+{
+	void *base;
+	ulong get_time;
+	char buf[BUFSIZE];
+	
+	base = (void *)hrtimer_bases + SIZE(hrtimer_base) * num;
+	fprintf(fp, "  clock: %d  HRTIMER_BASE: %lx\n", num, (ulong)base);
+
+	readmem((ulong)(base + OFFSET(hrtimer_base_get_time)), KVADDR,
+		&get_time, sizeof(get_time), "hrtimer_base get_time",
+		FAULT_ON_ERROR);
+
+	fprintf(fp ,"    GET_TIME:\t%s\n", value_to_symstr(get_time, buf, 0));
+
+	dump_active_timers(base);
+}
+
+static void
+dump_active_timers(const void *base)
+{
+	int next, i, t;
+	struct rb_node *curr;
+	int timer_cnt;
+	ulong *timer_list;
+	void  *timer;
+	char buf1[BUFSIZE];
+	char buf2[BUFSIZE];
+	char buf3[BUFSIZE];
+
+
+	next = 0;
+
+	/* search hrtimers */
+	hq_open();
+	timer_cnt = 0;
+next_one:
+	i = 0;
+
+	/* get the first node */
+	if (VALID_MEMBER(hrtimer_base_pending))
+		readmem((ulong)(base + OFFSET(hrtimer_base_pending) -
+			OFFSET(hrtimer_list) + OFFSET(hrtimer_node)),
+			KVADDR, &curr, sizeof(curr), "hrtimer_base pending",
+			FAULT_ON_ERROR);
+	else if (VALID_MEMBER(hrtimer_base_first))
+		readmem((ulong)(base + OFFSET(hrtimer_base_first)),
+			KVADDR, &curr, sizeof(curr), "hrtimer_base first",
+			FAULT_ON_ERROR);
+	else if (VALID_MEMBER(hrtimer_clock_base_first))
+		readmem((ulong)(base + OFFSET(hrtimer_clock_base_first)),
+			KVADDR,	&curr, sizeof(curr), "hrtimer_clock_base first",
+			FAULT_ON_ERROR);
+	else
+		readmem((ulong)(base + OFFSET(hrtimer_clock_base_active) +
+				OFFSET(timerqueue_head_next)),
+			KVADDR, &curr, sizeof(curr), "hrtimer_clock base",
+			FAULT_ON_ERROR);
+
+	while (curr && i < next) {
+		curr = rb_next(curr);
+		i++;
+	}
+
+	if (curr) {
+		if (!hq_enter((ulong)curr)) {
+			error(INFO, "duplicate rb_node: %lx\n", curr);
+			return;
+		}
+
+		timer_cnt++;
+		next++;
+		goto next_one;
+	}
+
+	if (timer_cnt) {
+		timer_list = (ulong *)GETBUF(timer_cnt * sizeof(long));
+		timer_cnt = retrieve_list(timer_list, timer_cnt);
+	}
+	hq_close();
+
+	/* dump hrtimers */
+	/* print header */
+	expires_len = get_expires_len(timer_cnt, timer_list);
+	if (expires_len < 8)
+		expires_len = 8;
+
+	fprintf(fp, "    ACTIVE TIMERS:\n");
+	fprintf(fp, "      %s  %s  %s\n",
+		mkstring(buf1, expires_len, CENTER|RJUST, "EXPIRES"),
+		mkstring(buf2, VADDR_PRLEN, CENTER|LJUST, "HRTIMER"),
+		mkstring(buf3, VADDR_PRLEN, CENTER|LJUST, "FUNCTION"));
+
+	/* print timer */
+	if (!timer_cnt)
+		fprintf(fp, "      (empty)\n");
+
+	for (t = 0; t < timer_cnt; t++) {
+		if (VALID_MEMBER(timerqueue_node_node))
+                        timer = (void *)(timer_list[t] -
+				OFFSET(timerqueue_node_node) -
+                                OFFSET(hrtimer_node));
+                else
+                        timer = (void *)(timer_list[t] - OFFSET(hrtimer_node));
+
+		print_timer(timer);
+	}
+}
+
+static int
+get_expires_len(const int timer_cnt, const ulong *timer_list)
+{
+	void *last_timer;
+	char buf[BUFSIZE];
+	ulonglong softexpires, expires;
+	int len;
+
+	len = 0;
+
+	if (!timer_cnt)
+		return 8;
+
+	if (VALID_MEMBER(timerqueue_node_node))
+        	last_timer = (void *)(timer_list[timer_cnt - 1] -
+			OFFSET(timerqueue_node_node) -
+                	OFFSET(hrtimer_node));
+        else
+                last_timer = (void *)(timer_list[timer_cnt -1] -
+			OFFSET(hrtimer_node));
+
+	/* expire is range */
+	if (VALID_MEMBER(hrtimer_softexpires)) {
+		softexpires = ktime_to_ns(last_timer + OFFSET(hrtimer_softexpires));
+		sprintf(buf, "%lld", softexpires);
+		len += strlen(buf) + 1;
+	}
+
+	if (VALID_MEMBER(hrtimer_expires))
+		expires = ktime_to_ns(last_timer + OFFSET(hrtimer_expires));
+	else
+		expires = ktime_to_ns(last_timer + OFFSET(hrtimer_node) +
+			OFFSET(timerqueue_node_expires));
+
+	sprintf(buf, "%lld", expires);
+	len += strlen(buf);
+
+	return len;
+}
+
+/*
+ * print hrtimer and its related information
+ */
+static void
+print_timer(const void *timer)
+{
+	ulonglong softexpires, expires;
+	
+	ulong function;
+	char buf1[BUFSIZE];
+	char buf2[BUFSIZE];
+	char buf3[BUFSIZE];
+
+	/* align information */
+	fprintf(fp, "      ");
+
+	if (!accessible((ulong)timer)) {
+		fprintf(fp, "(destroyed timer)\n");
+		return;
+	}
+
+	if (VALID_MEMBER(hrtimer_expires))
+		expires = ktime_to_ns(timer + OFFSET(hrtimer_expires));
+	else
+		expires = ktime_to_ns(timer + OFFSET(hrtimer_node) +
+			OFFSET(timerqueue_node_expires));
+
+	if (VALID_MEMBER(hrtimer_softexpires)) {
+		softexpires = ktime_to_ns(timer + OFFSET(hrtimer_softexpires));
+		sprintf(buf1, "%lld-%lld", softexpires, expires);
+	} else {
+		sprintf(buf1, "%lld", expires);
+	}
+	fprintf(fp, "%s  ", mkstring(buf2, expires_len, CENTER|RJUST, buf1));
+
+	fprintf(fp, "%lx  ", (ulong)timer);
+
+	if (accessible((ulong)(timer + OFFSET(hrtimer_function)))) {
+		readmem((ulong)(timer + OFFSET(hrtimer_function)), KVADDR, &function,
+			sizeof(function), "hrtimer function", FAULT_ON_ERROR);
+		fprintf(fp, "%lx  ", function);
+		fprintf(fp ,"<%s>", value_to_symstr(function, buf3, 0));
+	}
+
+	fprintf(fp, "\n");
+}
+
+/*
+ * convert ktime to ns, only need the address of ktime
+ */
+static ulonglong
+ktime_to_ns(const void *ktime)
+{
+	ulonglong ns;
+
+	ns = 0;
+
+	if (VALID_MEMBER(ktime_t_tv64)) {
+		if (accessible((ulong)ktime + OFFSET(ktime_t_tv64)))
+			readmem((ulong)ktime + OFFSET(ktime_t_tv64), KVADDR, &ns,
+				sizeof(ns), "ktime_t tv64", FAULT_ON_ERROR);
+	} else {
+		uint32_t sec, nsec;
+
+		sec = 0;
+		nsec = 0;
+
+		if (accessible((ulong)ktime + OFFSET(ktime_t_sec)) &&
+			accessible((ulong)ktime + OFFSET(ktime_t_nsec))) {
+			readmem((ulong)ktime + OFFSET(ktime_t_sec), KVADDR, &sec,
+				sizeof(sec), "ktime_t sec", FAULT_ON_ERROR);
+
+			readmem((ulong)ktime + OFFSET(ktime_t_nsec), KVADDR, &nsec,
+				sizeof(nsec), "ktime_t nsec", FAULT_ON_ERROR);
+		}
+
+		ns = sec * 1000000000L + nsec;
+	}
+
+	return ns;
 }
 
 /*
-- 
1.7.1

