[ARM] kernel先頭から。その2

2013/11/07Linux::ARMimport

kernel entryからの、SoC依存部hook関数呼び出し順序を確認する

前知識

"なんとか_initcall()"は、呼び出し者が居ないのに、何故か実行されている。
これはlinker scriptを使って関数テーブルを作り、それを順次呼び出しすることで実行を可能としている。
わからなければ、とりあえず、以下の順に呼び出す順番が決まっているマクロと覚えておけばいいだろう。
pure, core, postcore, arch, subsys, fs, rootfs, device, late


FILE: include/linux/init.h
/* initcalls are now grouped by functionality into separate 
 * subsections. Ordering inside the subsections is determined
 * by link order. 
 * For backwards compatibility, initcall() puts the call in 
 * the device init subsection.
 *
 * The `id' arg to __define_initcall() is needed so that multiple initcalls
 * can point at the same handler without causing duplicate-symbol build errors.
 */

#define __define_initcall(level,fn,id) \
        static initcall_t __initcall_##fn##id __used \
        __attribute__((__section__(".initcall" level ".init"))) = fn

/*
 * Early initcalls run before initializing SMP.
 *
 * Only for built-in code, not modules.
 */
#define early_initcall(fn)              __define_initcall("early",fn,early)

/*
 * A "pure" initcall has no dependencies on anything else, and purely
 * initializes variables that couldn't be statically initialized.
 *
 * This only exists for built-in code, not for modules.
 */
#define pure_initcall(fn)               __define_initcall("0",fn,0)

#define core_initcall(fn)               __define_initcall("1",fn,1)
#define core_initcall_sync(fn)          __define_initcall("1s",fn,1s)
#define postcore_initcall(fn)           __define_initcall("2",fn,2)
#define postcore_initcall_sync(fn)      __define_initcall("2s",fn,2s)
#define arch_initcall(fn)               __define_initcall("3",fn,3)
#define arch_initcall_sync(fn)          __define_initcall("3s",fn,3s)
#define subsys_initcall(fn)             __define_initcall("4",fn,4)
#define subsys_initcall_sync(fn)        __define_initcall("4s",fn,4s)
#define fs_initcall(fn)                 __define_initcall("5",fn,5)
#define fs_initcall_sync(fn)            __define_initcall("5s",fn,5s)
#define rootfs_initcall(fn)             __define_initcall("rootfs",fn,rootfs)
#define device_initcall(fn)             __define_initcall("6",fn,6)
#define device_initcall_sync(fn)        __define_initcall("6s",fn,6s)
#define late_initcall(fn)               __define_initcall("7",fn,7)
#define late_initcall_sync(fn)          __define_initcall("7s",fn,7s)


FILE: include/asm-generated/vmlinux.lds.h
#define INITCALLS							\
	*(.initcallearly.init)						\
	VMLINUX_SYMBOL(__early_initcall_end) = .;			\
  	*(.initcall0.init)						\
  	*(.initcall0s.init)						\
  	*(.initcall1.init)						\
  	*(.initcall1s.init)						\
  	*(.initcall2.init)						\
  	*(.initcall2s.init)						\
  	*(.initcall3.init)						\
  	*(.initcall3s.init)						\
  	*(.initcall4.init)						\
  	*(.initcall4s.init)						\
  	*(.initcall5.init)						\
  	*(.initcall5s.init)						\
	*(.initcallrootfs.init)						\
  	*(.initcall6.init)						\
  	*(.initcall6s.init)						\
  	*(.initcall7.init)						\
  	*(.initcall7s.init)

#define INIT_CALLS							\
		VMLINUX_SYMBOL(__initcall_start) = .;			\
		INITCALLS						\
		VMLINUX_SYMBOL(__initcall_end) = .;
...
#define INIT_DATA_SECTION(initsetup_align)				\
	.init.data : AT(ADDR(.init.data) - LOAD_OFFSET) {		\
		INIT_DATA						\
		INIT_SETUP(initsetup_align)				\
		INIT_CALLS						\
		CON_INITCALL						\
		SECURITY_INITCALL					\
		INIT_RAM_FS						\
	}
...
以上のマクロ・ldscriptから、"__initcall_start"でアドレスを参照できますね。
呼び出しているのは以下の場所。

FILE: init/main.c
extern initcall_t __initcall_start[], __initcall_end[], __early_initcall_end[];

static void __init do_initcalls(void)
{
	initcall_t *fn;

	for (fn = __early_initcall_end; fn < __initcall_end; fn++)
		do_one_initcall(*fn);
}
...
static void __init do_basic_setup(void)
{
	cpuset_init_smp();
	usermodehelper_init();
	init_tmpfs();
	driver_init();
	init_irq_proc();
	do_ctors();
	do_initcalls();
}
...

static void __init do_pre_smp_initcalls(void)
{
	initcall_t *fn;

	for (fn = __initcall_start; fn < __early_initcall_end; fn++)
		do_one_initcall(*fn);
}

...
static int __init kernel_init(void * unused)  // この子は __init_refok rest_init(void)からthread化される。
{
	/*
	 * Wait until kthreadd is all set-up.
	 */
	wait_for_completion(&kthreadd_done);

	/* Now the scheduler is fully set up and can do blocking allocations */
	gfp_allowed_mask = __GFP_BITS_MASK;

	/*
	 * init can allocate pages on any node
	 */
	set_mems_allowed(node_states[N_HIGH_MEMORY]);
	/*
	 * init can run on any cpu.
	 */
	set_cpus_allowed_ptr(current, cpu_all_mask);

	cad_pid = task_pid(current);

	smp_prepare_cpus(setup_max_cpus);

	do_pre_smp_initcalls();    /// ★early init callに属する関数を実行。
	lockup_detector_init();

	smp_init();
	sched_init_smp();

	do_basic_setup();         /// ★init callを実行。early init以外を順次呼び出す.
...

本命

vmlinuxのstartup routineを抜けると、以下のstart_kernelが呼ばれる。
FILE: arch/arm/kernel/head-common.S
__mmap_switched:
    b       start_kernel
FILE: init/main.c
asmlinkage void __init start_kernel(void)
{
	char * command_line;
	extern const struct kernel_param __start___param[], __stop___param[];

	smp_setup_processor_id();

	/*
	 * Need to run as early as possible, to initialize the
	 * lockdep hash:
	 */
	lockdep_init();
	debug_objects_early_init();

	/*
	 * Set up the the initial canary ASAP:
	 */
	boot_init_stack_canary();

	cgroup_init_early();

	local_irq_disable();
	early_boot_irqs_disabled = true;

/*
 * Interrupts are still disabled. Do necessary setups, then
 * enable them
 */
	tick_init();
	boot_cpu_init();
	page_address_init();
	printk(KERN_NOTICE "%s", linux_banner);
	setup_arch(&command_line);              //★ARMの場合はヴェクタ設定、メモリ領域の確保、MMU初期設定まで完了する
	mm_init_owner(&init_mm, &init_task);
	mm_init_cpumask(&init_mm);
	setup_command_line(command_line);
	setup_nr_cpu_ids();
	setup_per_cpu_areas();
	smp_prepare_boot_cpu();	/* arch-specific boot-cpu hooks */

	build_all_zonelists(NULL);
	page_alloc_init();

	printk(KERN_NOTICE "Kernel command line: %s\n", boot_command_line);
	parse_early_param();
	parse_args("Booting kernel", static_command_line, __start___param,
		   __stop___param - __start___param,
		   &unknown_bootoption);
	/*
	 * These use large bootmem allocations and must precede
	 * kmem_cache_init()
	 */
	setup_log_buf(0);
	pidhash_init();
	vfs_caches_init_early();
	sort_main_extable();
	trap_init();
	mm_init();

	/*
	 * Set up the scheduler prior starting any interrupts (such as the
	 * timer interrupt). Full topology setup happens at smp_init()
	 * time - but meanwhile we still have a functioning scheduler.
	 */
	sched_init();
	/*
	 * Disable preemption - early bootup scheduling is extremely
	 * fragile until we cpu_idle() for the first time.
	 */
	preempt_disable();
	if (!irqs_disabled()) {
		printk(KERN_WARNING "start_kernel(): bug: interrupts were "
				"enabled *very* early, fixing it\n");
		local_irq_disable();
	}
	idr_init_cache();
	perf_event_init();
	rcu_init();
	radix_tree_init();
	/* init some links before init_ISA_irqs() */
	early_irq_init();
	init_IRQ();
	prio_tree_init();
	init_timers();
	hrtimers_init();
	softirq_init();
	timekeeping_init();
	time_init();
	profile_init();
	call_function_init();
	if (!irqs_disabled())
		printk(KERN_CRIT "start_kernel(): bug: interrupts were "
				 "enabled early\n");
	early_boot_irqs_disabled = false;
	local_irq_enable();

	kmem_cache_init_late();

	/*
	 * HACK ALERT! This is early. We're enabling the console before
	 * we've done PCI setups etc, and console_init() must be aware of
	 * this. But we do want output early, in case something goes wrong.
	 */
	console_init();
	if (panic_later)
		panic(panic_later, panic_param);

	lockdep_info();

	/*
	 * Need to run this when irqs are enabled, because it wants
	 * to self-test [hard/soft]-irqs on/off lock inversion bugs
	 * too:
	 */
	locking_selftest();

#ifdef CONFIG_BLK_DEV_INITRD
	if (initrd_start && !initrd_below_start_ok &&
	    page_to_pfn(virt_to_page((void *)initrd_start)) < min_low_pfn) {
		printk(KERN_CRIT "initrd overwritten (0x%08lx < 0x%08lx) - "
		    "disabling it.\n",
		    page_to_pfn(virt_to_page((void *)initrd_start)),
		    min_low_pfn);
		initrd_start = 0;
	}
#endif
	page_cgroup_init();
	enable_debug_pagealloc();
	debug_objects_mem_init();
	kmemleak_init();
	setup_per_cpu_pageset();
	numa_policy_init();
	if (late_time_init)
		late_time_init();
	sched_clock_init();
	calibrate_delay();
	pidmap_init();
	anon_vma_init();
#ifdef CONFIG_X86
	if (efi_enabled)
		efi_enter_virtual_mode();
#endif
	thread_info_cache_init();
	cred_init();
	fork_init(totalram_pages);
	proc_caches_init();
	buffer_init();
	key_init();
	security_init();
	dbg_late_init();
	vfs_caches_init(totalram_pages);
	signals_init();
	/* rootfs populating might need page-writeback */
	page_writeback_init();
#ifdef CONFIG_PROC_FS
	proc_root_init();
#endif
	cgroup_init();
	cpuset_init();
	taskstats_init_early();
	delayacct_init();

	check_bugs();

	acpi_early_init(); /* before LAPIC and SMP init */
	sfi_init_late();

	ftrace_init();

	/* Do the rest non-__init'ed, we're now alive */
	rest_init();    //★ kernel_init()をkthread_startするよ
}

arm固有のマシン情報

定義は以下にある。powerpcに比べると、hook関数が少ないように思う。
それぞれの役割と、呼び出されるタイミングを確認する。
func.ptr 目的 remark
fixupsetup_arch()のほぼ先頭、setup_machine_tags()の後半に呼ばれる。ATAGなど修正入れられる。
reservereserve any platform specific memblock areassetup_arch()の中頃, arm_memblock_init()の後半で呼ばれる。(paging_init()より早い)
map_ioAsk the machine support to map in the statically mapped devices.setup_arch()の中頃, paging_init()の先で呼ばれる
init_earlysetup_arch()の最後, start_kernel()から呼ばれる.
init_irqstart_kernel()の中頃、init_IRQ()経由。(setup_arch()の呼び出しより後)
init_machinecustomize_machine()から呼ばれる。 arch_initcall()マクロで括られているので、kernel_init()スレッドから実行。 start_kernelの最後、rest_init()の最後のほう.
handle_irqCONFIG_MULTI_IRQ_HANDLER定義時のみ存在。
setup_arch()のinit_early手前でglobal変数"handle_arch_irq"に保存、irq handlerから呼ばれる。
FILE: arch/arm/include/asm/mach/arch.h
struct machine_desc {
    unsigned int       nr;        /* architecture number    */
    const char        *name;        /* architecture name    */
    unsigned long      boot_params;    /* tagged list        */
    const char       **dt_compat;    /* array of device tree 'compatible' strings */

    unsigned int       nr_irqs;    /* number of IRQs */

    unsigned int      video_start;    /* start of video RAM    */
    unsigned int      video_end;    /* end of video RAM    */

    unsigned int      reserve_lp0 :1;    /* never has lp0    */
    unsigned int      reserve_lp1 :1;    /* never has lp1    */
    unsigned int      reserve_lp2 :1;    /* never has lp2    */
    unsigned int      soft_reboot :1;    /* soft reboot        */

    void    (*fixup)(struct machine_desc *, struct tag *, char **, struct meminfo *);
    void    (*reserve)(void);              /* reserve mem blocks  */
    void    (*map_io)(void);               /* IO mapping function */
    void    (*init_early)(void);
    void    (*init_irq)(void);

    struct sys_timer    *timer;            /* system tick timer    */
    void            (*init_machine)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
    void            (*handle_irq)(struct pt_regs *);
#endif
};
FILE: arch/arm/mm/init.c
void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)

ここの処理の最後に呼び出している。この前に initrd領域もreservedにしてしまう。
	/* reserve any platform specific memblock areas */
	if (mdesc->reserve)
		mdesc->reserve();

FILE: arch/arm/mm/mmu.c
void __init paging_init(struct machine_desc *mdesc)
 static void __init devicemaps_init(struct machine_desc *mdesc)
 ...
  if (mdesc->map_io)
   mdesc->map_io();

ここでkernel管理のPage tableを初期化する。
ヴェクタ用pageの確保、high/lo vector処理、VMALLOC_ENDカラ後ろのアクセス禁止設定を行う。
void __init setup_arch(char **cmdline_p)
...
	paging_init(mdesc); // ★ ioremap設定するので、map_io()はこの先で呼ばれる。
...
	if (mdesc->init_early)
		mdesc->init_early();
FILE: arch/arm/kernel/setup.c
static int __init customize_machine(void)
{
	/* customizes platform devices, or adds new ones */
	if (machine_desc->init_machine)
		machine_desc->init_machine();
	return 0;
}
arch_initcall(customize_machine);

arm

ARM関係

filename remark
00-INDEXthis file
Bootingrequirements for booting
InterruptsARM Interrupt subsystem documentation
msmMSM specific documentation
NetwinderNetwinder specific documentation
PortingSymbol definitions for porting Linux to a new ARM machine.
SetupKernel initialization parameters on ARM Linux
READMEGeneral ARM documentation
SA1100/SA1100 documentation
Samsung-S3C24XXS3C24XX ARM Linux Overview
Sharp-LHLinux on Sharp LH79524 and LH7A40X System On a Chip (SOC)
SPEArST SPEAr platform Linux Overview
VFP/Release notes for Linux Kernel Vector Floating Point support code
empeg/Ltd's Empeg MP3 Car Audio Player
mem_alignmentalignment abort handler documentation
memory.txtdescription of the virtual memory layout
nwfpe/NWFPE floating point emulator documentation
swp_emulationSWP/SWPB emulation handler/logging description

memory.txt

Kernel Memory Layout on ARM Linux

Russell King <rmk@arm.linux.org.uk>
November 17, 2005 (2.6.15)
This document describes the virtual memory layout which the Linux kernel uses for ARM processors.
It indicates which regions are free for platforms to use, and which are used by generic code.
このドキュメントでは、LinuxカーネルがARMプロセッサ用に使用する仮想メモリのレイアウトを記述します。
これは、プラットフォームが使用するためのフリーな領域がどこにあるか、そして、汎用コードによって使用されるのはどこか、を示します。
The ARM CPU is capable of addressing a maximum of 4GB virtual memory space,
and this must be shared between user space processes, the kernel, and hardware devices.
ARM CPUは、最大4GBの仮想メモリ空間アドレッシングを許容します。
この空間は、ユーザ空間プロセス、カーネル、ハードウェアデバイスの間で共有する必要があります。
As the ARM architecture matures, it becomes necessary to reserve certain regions of VM space for use for new facilities;
therefore this document may reserve more VM space over time.
ARMアーキテクチャは静寂するにつれて、新しい設備(機能)を使うために、仮想空間の特定の領域を予約する必要が出てきます。
したがって、このドキュメントは、時間の経過と共に、より多くの仮想空間を仮想空間を予約するでしょう。
Start End Use
ffff8000ffffffffcopy_user_page / clear_user_page で使う。
SA11xx と Xscale では、minicache mappingの設定に使う。
ffff4000ffffffffARMv6アーキ以降の cache aliasing
ffff1000ffff7fffReserved. Platformsはこの領域を使ってはいけない
ffff0000ffff0fffCPU vector page. CPUがヴェクタリロケーションをサポートしていれば、ここにマップされる(Control RegisterのV bitで設定)
fffe0000fffeffffXScale cache flush area. This is used in proc-xscale.S to flush the whole data cache. (XScale does not have TCM.)
fffe8000fffeffffDTCM mapping area. for platforms with DTCM mounted inside the CPU.
fffe0000fffe7fffITCM mapping area for platforms with ITCM mounted inside the CPU.
fff00000fffdffffFixmap mapping region. fix_to_virt()で与えられるアドレスは、ここに配置される。
ffc00000ffefffffDMA memory mapping region. the dma_alloc_xxx()で返ってくるメモリが、ここに動的にマップされる。
ff000000ffbfffffReserved for future expansion of DMA mapping region.
fee00000feffffffMapping of PCI I/O space. これは、vmalloc空間の静的マッピングです。
VMALLOC_STARTVMALLOC_END-1vmalloc() / ioremap() 空間。 vmalloc(),ioremap()によって返されるメモリは、この領域に動的に配置される。 マシン固有の静的マッピングも、iotable_init()によって、ここに配置される。
VMALLOC_STARTは、high_memory変数の値に基づいて得られる。VMALLOC_ENDは0xff000000である(ヘッダで定義)。
PAGE_OFFSEThigh_memory-1Kernel direct-mapped RAM region. これは、プラットフォームのRAM、通常は、1:1の関係にあるすべてのプラットフォームのRAMを配置します。
CONFIG_PAGE_OFFSETで設定する。一般的な32bit kernelでは 0xC0000000(kernel:user=1GB:3GB設定)となっている。
PKMAP_BASEPAGE_OFFSET-1永続的なkernelマッピング。 カーネル空間にHIGHMEMページを配置する方法の一つ。
MODULES_VADDRMODULES_END-1Kernel module space.
insmodにより挿入されるKernel modulesは、ここに動的に配置されます。
00001000TASK_SIZE-1User space mappings.
スレッド毎のマッピングは、mmap()システムコールにより、ここに配置される。
0000000000000fffCPU vector page / null pointer trap.
ヴェクタremapをサポートしていないCPUは、ヴェクタページをここに配置します。 kernel空間・user空間の双方から、NULLポインタデリファレンスもまた、この配置から捕まえられます。

Please note that mappings which collide with the above areas may result in a non-bootable kernel,
or may cause the kernel to (eventually) panic at run time.
上記領域と衝突するマッピングは、非ブートカーネルになったり、実行時に(最終的に)kernel panic要因となるかもしれないことに、ご注意ください。
Since future CPUs may impact the kernel mapping layout,
user programs must not access any memory which is not mapped inside their 0x0001000 to TASK_SIZE address range.
If they wish to access these areas,
they must set up their own mappings using open() and mmap().
将来のCPUでは、kernelマッピングレイアウトに影響があるかもしれないので、ユーザプログラムは、0x0001000からTASK_SIZEまでのアドレス範囲から外れたメモリをアクセスしてはならない。
もしそれらの領域をアクセスしたければ、open()やmmap()を使って、固有のマッピングをセットアップしなければならない。


関連ファイル

FILE: arch/arm/include/asm/fixmap.h

めも

更新されていないのか、vendorの仕様か、ちょっと配置が違う気がする。
変数を書き換えれば、上の方(vmalloc前後)は任意に動くから、いじってるかもなぁ。

clock_sourceとclock_eventとか

2013/11/03Linux::timeimport
※この文章は、Linux-3.12.0-rc7のソースコードを参照しながら書いています。
 versionによる差異があることは認識いただき、参考にされる場合は、実際に使用する版でも確認してください。


kernelに必須な機能である時間管理、その源となるリソースを管理するもの、と思う。

clock sourceと、clock eventのに種類があり、前者はハードウェア時計相当、後者は一定時間後に割り込みをもらうもの、ぽい。

関連しそうなドライバは、以下のディレクトリ配下の模様。
DIR: drivers/clocksource/
drivers/clocksource/dw_apb_timer.c
drivers/clocksource/arm_arch_timer.c
drivers/clocksource/arm_global_timer.c
drivers/clocksource/dummy_timer.c

driver:: clocksource "jiffies", "refined-jiffies"

探してみると、別のディレクトリにもデバイスとして登録しているものがありますね。
core_initcall()マクロで、core起動時に呼び出すように設定(要確認)しているので、
必ず登録される模様。ratingが1と最低値なので、他に登録されればそれが使われる、と。

FILE: kernel/time/jiffies.c

static struct clocksource clocksource_jiffies = {
        .name           = "jiffies",
        .rating         = 1, /* lowest valid rating*/
        .read           = jiffies_read,
        .mask           = 0xffffffff, /*32bits*/
        .mult           = NSEC_PER_JIFFY << JIFFIES_SHIFT, /* details above */
        .shift          = JIFFIES_SHIFT,
};


EXPORT_SYMBOL(jiffies);

static int __init init_jiffies_clocksource(void)
{
	return clocksource_register(&clocksource_jiffies);
}

core_initcall(init_jiffies_clocksource);

struct clocksource * __init __weak clocksource_default_clock(void)
{
	return &clocksource_jiffies;
}

"refined-jiffies"に関しては、Linux 3.12-rc7の段階で、x86でしか作られません。
./arch/x86/kernel/setup.c:1215:	register_refined_jiffies(CLOCK_TICK_RATE);

clocksource

ヘッダファイルに簡単な説明があるので、これと実装されているコードから推定する…。
あとは呼び出している側のコードをみて、ユースケースから考えるしかなさげ。

struct clocksource
フリーランカウンタのための、ハードウェア抽象化に使う。
基本となるハードウェアに、ほとんど状態依存なくアクセスするための手段を提供する。
システム時刻のために使われる。

FILE: include/linux/clocksource.h
struct clocksource {
	/*
	 * Hotpath data, fits in a single cache line when the
	 * clocksource itself is cacheline aligned.
	 */
	cycle_t (*read)(struct clocksource *cs); // clocksourceを引数で渡して、サイクル値を返す。
	cycle_t cycle_last; // read()メンバ呼び出しで最近読みだしたcycle値。
	cycle_t mask;    // 非64bitカウンタの引き算のための2の補数のためのbitmask値。
	u32 mult;        // cycleからナノ秒の乗数
	u32 shift;       // cycleからナノ秒の除数(2の累乗)
	u64 max_idle_ns; // clocksourceによって許容される最大アイドル時間(ナノ秒)
	u32 maxadj;      // 最大調整値?to multiってなんだ。。。(11%以下)
#ifdef CONFIG_ARCH_CLOCKSOURCE_DATA
	struct arch_clocksource_data archdata; // architecture固有のデータ
#endif

	const char *name;      // clocksourceの名前
	struct list_head list; // システムに登録するためのリストヘッド
	int rating;            // 複数登録された時の選択基準(大きいのが選ばれる)
	int (*enable)(struct clocksource *cs);    // [option] clocksourceを有効にする。
	void (*disable)(struct clocksource *cs);  // [option] clocksourceを無効にする。
	unsigned long flags;   // 特別な属性を記述するためのフラグ
	void (*suspend)(struct clocksource *cs);  // 必要ならば、サスペンド機能
	void (*resume)(struct clocksource *cs);   // 必要ならば、レジューム機能

	/* private: */
#ifdef CONFIG_CLOCKSOURCE_WATCHDOG
	/* Watchdog related data, used by the framework */
	struct list_head wd_list;
	cycle_t cs_last;
	cycle_t wd_last;
#endif
	struct module *owner;  // module reference.. clocksource moduleで設定される(書きこむなという意)
} ____cacheline_aligned;
ratingのインフレを避けるため、クロックソースのratingを割り当てる方法に関して、
以下のリストが基準を与えます。
value meanings
1-99実際の利用には適さない。起動時やテスト目的にのみ使用可能。
100-199基本レベルのユーザビリティ。実際の使用のための機能であるが、望ましくない。
200-299良い。正しく、使えるクロックソース。
300-399望まれる。適度に高速かつ正確なクロックソース。
400-499完全に理想的なクロックソース。利用可能な場合、使用する必要があります。
数個見ただけですが、SoC依存タイマは350あたりを使っていた。

flagsの値

symbol value remark
CLOCK_SOURCE_IS_CONTINUOUS0x01?通常セットされるようなものじゃないと使えないぽい??
CLOCK_SOURCE_MUST_VERIFY0x02clocksource監視用には使えない(監視される側のみ)を宣言する。
CLOCK_SOURCE_WATCHDOG0x10※内部フラグと思われる
CLOCK_SOURCE_VALID_FOR_HRES0x20※内部フラグ?CONTINUOUSがセットされていれば、コレも各所でセットされる
CLOCK_SOURCE_UNSTABLE0x40clocksource_unstable()でセットされる。HRES,WATCHDOGフラグが落とされる。
CLOCK_SOURCE_SUSPEND_NONSTOP0x80※セットするのはx86固有コードになってる
CLOCK_SOURCE_RESELECT0x100watchdogでソース切り替えが発生条件を満たしてセット、kthreadで再選択後にクリアするぽい
クロックソースが複数ある場合に、相互監視して、ズレが大きくなったものを排除していく模様。
単数出会った場合は、自分自身で差分を見るので、故障検出はできない、か、WD機能が働かない。
ここでいうwatchdogは、クロックソースのカウンタ増加速度が正しいかどうかを見ているだけ。
冗長性をあげるためなのか、仮想化がらみか??

clock event

[b:CONFIG_GENERIC_CLOCKEVENTS_BUILD]が定義されているときのみ有効。
メンバにIRQNが入っている。clocksourceには入っていないので、使用目的が妄想可能ですね。

FILE: include/linux/clockchips.h
/* Clock event notification values */
enum clock_event_nofitiers
symbol val meaning
CLOCK_EVT_NOTIFY_ADD0
CLOCK_EVT_NOTIFY_BROADCAST_ON1
CLOCK_EVT_NOTIFY_BROADCAST_OFF2
CLOCK_EVT_NOTIFY_BROADCAST_FORCE3
CLOCK_EVT_NOTIFY_BROADCAST_ENTER4
CLOCK_EVT_NOTIFY_BROADCAST_EXIT5
CLOCK_EVT_NOTIFY_SUSPEND6
CLOCK_EVT_NOTIFY_RESUME7
CLOCK_EVT_NOTIFY_CPU_DYING8
CLOCK_EVT_NOTIFY_CPU_DEAD9
struct clock_event_device - clock event device descriptor
struct clock_event_device {
	void  (*event_handler)(struct clock_event_device *);  // event sourceの低レベルハンドラから呼ばれるために、フレームワークによってアサインされる。
	int   (*set_next_event)(unsigned long evt, struct clock_event_device *);  // clocksource deltaを使って次のイベントをセットする関数
	int   (*set_next_ktime)(ktime_t expires, struct clock_event_device *);    // ktime値を直接使って次のイベントをセットする関数
	ktime_t  next_event;   // oneshot mode時の次のイベントのためのローカルストレージ
	u64      max_delta_ns; // 最大の差分値(ナノ秒)
	u64      min_delta_ns; // 最小の差分値(ナノ秒)
	u32      mult;         // ナノ秒からサイクル数への乗数
	u32      shift;        // サイクル数からナノ秒の除数(2の累乗)
	enum clock_event_mode  mode; // マネージメントコードによって代入される、動作モード
	unsigned int           features; // 機能
	unsigned long          retries;  // 強制プログラムの再回数

	void    (*broadcast)(const struct cpumask *mask); // broadcastイベント関数(?)
	void    (*set_mode)(enum clock_event_mode mode, struct clock_event_device *); // 動作モード設定関数
	void    (*suspend)(struct clock_event_device *);
	void    (*resume)(struct clock_event_device *);
	unsigned long  min_delta_ticks; // リコンフィギュレーションのために保存されたtickの最小デルタ値
	unsigned long  max_delta_ticks; // リコンフィギュレーションのために保存されたtickの最大デルタ値

	const char  *name;  // clock event名へのポインタ
	int   rating;       // clockeventの選択基準値
	int   irq;          // IRQ number (only for non CPU local devices)
	const struct cpumask  *cpumask; // このデバイスが動作するCPUについて示す値
	struct list_head      list;     // マネージメントコードのためのリストヘッド
	struct module         *owner;   // module参照
} ____cacheline_aligned;


clock eventのユースケース(?)

どうやら、clock_sourceにしても、clock_eventにしても、登録時(*_register_device()呼び出し)にシステムで使えるかどうかのチェックが入っている模様。

FILE: kernel/time/clockevents.c
void clockevents_register_device(struct clock_event_device *dev)
{
	unsigned long flags;

	BUG_ON(dev->mode != CLOCK_EVT_MODE_UNUSED);
	if (!dev->cpumask) {
		WARN_ON(num_possible_cpus() > 1);
		dev->cpumask = cpumask_of(smp_processor_id());
	}

	raw_spin_lock_irqsave(&clockevents_lock, flags);

	list_add(&dev->list, &clockevent_devices);
	tick_check_new_device(dev);       // ★★★ここ。この先で使えると思ったCPUが奪っていく。
	clockevents_notify_released();

	raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}

FILE: kernel/time/tick-common.c
void tick_check_new_device(struct clock_event_device *newdev)
{
	struct clock_event_device *curdev;
	struct tick_device *td;
	int cpu;

	cpu = smp_processor_id();
	if (!cpumask_test_cpu(cpu, newdev->cpumask))
		goto out_bc;

	td = &per_cpu(tick_cpu_device, cpu);
	curdev = td->evtdev;

	/* cpu local device ? */
	if (!tick_check_percpu(curdev, newdev, cpu))
		goto out_bc;

	/* Preference decision */
	if (!tick_check_preferred(curdev, newdev))
		goto out_bc;

	if (!try_module_get(newdev->owner))
		return;

	/*
	 * Replace the eventually existing device by the new
	 * device. If the current device is the broadcast device, do
	 * not give it back to the clockevents layer !
	 */
	if (tick_is_broadcast_device(curdev)) {
		clockevents_shutdown(curdev);
		curdev = NULL;
	}
	clockevents_exchange_device(curdev, newdev);
	tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
	if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
		tick_oneshot_notify();
	return;

out_bc:
	/*
	 * Can the new device be used as a broadcast device ?
	 */
	tick_install_broadcast_device(newdev);
}

clock_eventの操作に関しては、ファイルスコープで閉じられている模様。
アクセサは関数で定義され、第一引数にオブジェクトのポインタが渡される(C++実装みたいなもん?)。
FILE: kernel/time/clockevents.c
/**
 * clockevents_set_mode - set the operating mode of a clock event device
 * @dev:	device to modify
 * @mode:	new mode
 *
 * Must be called with interrupts disabled !
 */
void clockevents_set_mode(struct clock_event_device *dev,
				 enum clock_event_mode mode)
{
	if (dev->mode != mode) {
		dev->set_mode(mode, dev);
		dev->mode = mode;

		/*
		 * A nsec2cyc multiplicator of 0 is invalid and we'd crash
		 * on it, so fix it up and emit a warning:
		 */
		if (mode == CLOCK_EVT_MODE_ONESHOT) {
			if (unlikely(!dev->mult)) {
				dev->mult = 1;
				WARN_ON(1);
			}
		}
	}
}
このモード、以下のヘッダで定義されている。コメントと使用している部位で期待している動作を抽出してみる。
(明文化されていないので、ざっくりとした判断。間違っていたら指摘していただきたい。)
  • >set_mode()呼び出しで、ナニを期待されているのか、という視点で書いています。
状態としては、単語のとおりでしょう。

FILE: include/linux/clockchips.h
Clock event mode commands
enum clock_event_mode
symbol value meaning
CLOCK_EVT_MODE_UNUSED0device登録時にこの値である必要がある。 clockevents_notify()でCLOCK_EVT_NOTIFY_CPU_DEADを受けた時もこれになる。
CLOCK_EVT_MODE_SHUTDOWN1動作を停止させる。スケジューリング予定も全てキャンセル
CLOCK_EVT_MODE_PERIODIC2周期タイマの初期化(周期は"HZ"で定義されているもの)と開始
CLOCK_EVT_MODE_ONESHOT3周期タイマ動作を止めて、タイマの設定を待つ。
CLOCK_EVT_MODE_RESUME4suspendからの復帰で呼ばれる。suspend前は、shutdownを呼ばれるけれど、改めてモード設定からやってくれる@

hrtimerとの関係

このへんで呼ばれてます。
FILE: kernel/hrtimer.c
void hrtimer_interrupt(struct clock_event_device *dev)
...
	/* Reprogramming necessary ? */
	if (expires_next.tv64 == KTIME_MAX ||
	    !tick_program_event(expires_next, 0)) {
		cpu_base->hang_detected = 0;
		return;
	}
...
	tick_program_event(expires_next, 1);
void hrtimer_peek_ahead_timers(void)
 static void __hrtimer_peek_ahead_timers(void)
 {
  td = &__get_cpu_var(tick_cpu_device);
  if (td && td->evtdev)
   hrtimer_interrupt(td->evtdev);
ざっくりこんな感じで呼ばれるので、デバイス自体はregisterしたときに割りつけられてそう。
足りなければ動かない、か?(エミュレーションデバイスが居るかも)


変数はヘッダで実体化されてる?あんまり美しくないような気がするけど、マクロも追いかけたほうがイイかな(´・ω・`)

FILE: include/linux/hrtimer.h
DECLARE_PER_CPU(struct tick_device, tick_cpu_device);

参考にしてみたドライバ:

FILE: drivers/clocksource/arm_global_timer.c

ARM-Cortex-Aシリーズで使われるSCU(Snoop Control Unit)に搭載されているglobal timerを使うもの。
中を見てみると、clocksourceとclockeventと、2つのドライバを登録している。
ハードウェアは64bit counter一発なので、経済的(?)。

コンパレータを搭載していて、カウンタの値と比較して、その値以上でマッチ条件を満たす。
r2p0より前のハードウェアマクロ(Hardware IP macro)であれば、マッチ条件が等号のみであり、
本ドライバの対象外となる。(設定した瞬間に時間が過ぎてしまうと、一周待つことになるからだろうネ(想像))


昔のマイコンで使われているタイマってのは、オートリロードがあれば御の字なアップダウンカウンタ持っているようなものだと思う。
コンペアマッチで停止するかリロードしてしまうやつね。

それはclockeventには使えても、このglobal timernのように、同時にclocksourceとしては使えない。
止まること無く実時間の進みに比例して?、単調な増減するカウンタが必要。ビット数は32〜64bitあれば良い。
bit幅が大きい方が、一発で時間設定ができるので、電力的に有利。

安物のタイマであれば、source/eventそれぞれにタイマを用意すれば対応できそうかな。
CPU事にevent timerが必要となってくるわけですが。
そこで比較するためのレジスタがCPU事に割付いている*1のが、global timerなのですね。。。
カウンタ自体はSoC内唯一の64bit counterで、どのCPUからも共通の時間を見ることができる、という設計かな。NTPや外部の時刻情報に対してリアルタイムクロックを補正していく機構は存在しているけれど、これをCPU間でどうシェアしているのか、かなぁ。補正自体は特定のCPUがやることになってるみたい。

※ソースコード引用していないのは、ほぼ想像とチラ見した程度の記憶なので、信憑性は無いという事にしてください。

*1 : ARM TRMではbankedと表記されています。

2013/11/01(金)jiffies更新を追いかける

Tickless kernelにより、既存の和文資料の示すところから離れだす。
仕方が無いのでソースコードを頼りに追いかけることにする。

主目的は最低限必要なタイマリソースはなにか、の確認。
scheduler、jiffiesの更新がなされれば、要件を満たすので、更新処理を遡る。

kernelはv3.10を参照した(LTSI候補だというのを見かけたはず?)

jiffies更新を追いかける

システム時刻:xtime だったはず

jiffies更新箇所
/*
 * The 64-bit jiffies value is not atomic - you MUST NOT read it
 * without sampling the sequence number in xtime_lock.
 * jiffies is defined in the linker script...
 */
void do_timer(unsigned long ticks)
{
	jiffies_64 += ticks;
	update_wall_time();
	calc_global_load(ticks);
}
コイツが大ボス. jiffiesそのものじゃないけどいいんか( system call "times"で返している)
jiffies更新は別か..

→一緒だった。物理メモリで同一アドレスにしてあるので、64の更新がjiffies更新と一致する(Little Endian)。
FILE: System.map
8xxxxxxx D jiffies
8xxxxxxx D jiffies_64

void xtime_update(unsigned long ticks) write_seqlock(&xtime_lock); do_timer(ticks); write_sequnlock(&xtime_lock);

FILE: kernel/time/tick-sched.c
/*
 * Must be called with interrupts disabled !
 */
static void tick_do_update_jiffies64(ktime_t now)
{
	unsigned long ticks = 0;
	ktime_t delta;

	/*
	 * Do a quick check without holding xtime_lock:
	 */
	delta = ktime_sub(now, last_jiffies_update);
	if (delta.tv64 < tick_period.tv64)
		return;

	/* Reevalute with xtime_lock held */
	write_seqlock(&xtime_lock);

	delta = ktime_sub(now, last_jiffies_update);
	if (delta.tv64 >= tick_period.tv64) {

		delta = ktime_sub(delta, tick_period);
		last_jiffies_update = ktime_add(last_jiffies_update,
						tick_period);

		/* Slow path for long timeouts */
		if (unlikely(delta.tv64 >= tick_period.tv64)) {
			s64 incr = ktime_to_ns(tick_period);

			ticks = ktime_divns(delta, incr);

			last_jiffies_update = ktime_add_ns(last_jiffies_update,
							   incr * ticks);
		}
		do_timer(++ticks);

		/* Keep the tick_next_period variable up to date */
		tick_next_period = ktime_add(last_jiffies_update, tick_period);
	}
	write_sequnlock(&xtime_lock);
}
tick_do_update_jiffies64()を呼び出すヒト.
 static void tick_nohz_update_jiffies(ktime_t now)
 void tick_nohz_stop_sched_tick(int inidle)
  tick_program_event()って?
 static void tick_nohz_restart(struct tick_sched *ts, ktime_t now)
 void tick_nohz_restart_sched_tick(void)
 static void tick_nohz_handler(struct clock_event_device *dev)  // The nohz low res interrupt handler
 static enum hrtimer_restart tick_sched_timer(struct hrtimer *timer) // high-res. timer持っているとき.
いずれも、ktime_get();で現時刻をとってきている(CPU寝ていても回っているカウンタを想定)


FILE: kernel/time/timekeeping.c
ktime_t ktime_get(void)
{
	unsigned int seq;
	s64 secs, nsecs;

	WARN_ON(timekeeping_suspended);

	do {
		seq = read_seqbegin(&xtime_lock);
		secs = xtime.tv_sec + wall_to_monotonic.tv_sec;
		nsecs = xtime.tv_nsec + wall_to_monotonic.tv_nsec;
		nsecs += timekeeping_get_ns();
		/* If arch requires, add in gettimeoffset() */
		nsecs += arch_gettimeoffset();

	} while (read_seqretry(&xtime_lock, seq));
	/*
	 * Use ktime_set/ktime_add_ns to create a proper ktime on
	 * 32-bit architectures without CONFIG_KTIME_SCALAR.
	 */
	return ktime_add_ns(ktime_set(secs, 0), nsecs);
}
EXPORT_SYMBOL_GPL(ktime_get);
ktime_get()は、xtimeを見ている。あれ..?

コチラを見ると、http://d.hatena.ne.jp/enakai00/20111117/1321508379
別の所でハードウェアリソースを参照していた。


FILE: kernel/time/timekeeping.c
/**
 * update_wall_time - Uses the current clocksource to increment the wall time
 *
 * Called from the timer interrupt, must hold a write on xtime_lock.
 */
static void update_wall_time(void)
{
	struct clocksource *clock;
	cycle_t offset;
	int shift = 0, maxshift;

	/* Make sure we're fully resumed: */
	if (unlikely(timekeeping_suspended))
		return;

	clock = timekeeper.clock;        // ★★コレがSoC全域で使える、cpu idleでも時間を刻むクロックソースを握っている。

#ifdef CONFIG_ARCH_USES_GETTIMEOFFSET
	offset = timekeeper.cycle_interval;
#else
	offset = (clock->read(clock) - clock->cycle_last) & clock->mask;
#endif
	timekeeper.xtime_nsec = (s64)xtime.tv_nsec << timekeeper.shift;

	/*
	 * With NO_HZ we may have to accumulate many cycle_intervals
	 * (think "ticks") worth of time at once. To do this efficiently,
	 * we calculate the largest doubling multiple of cycle_intervals
	 * that is smaller then the offset. We then accumulate that
	 * chunk in one go, and then try to consume the next smaller
	 * doubled multiple.
	 */
	shift = ilog2(offset) - ilog2(timekeeper.cycle_interval);
	shift = max(0, shift);
	/* Bound shift to one less then what overflows tick_length */
	maxshift = (8*sizeof(tick_length) - (ilog2(tick_length)+1)) - 1;
	shift = min(shift, maxshift);
	while (offset >= timekeeper.cycle_interval) {
		offset = logarithmic_accumulation(offset, shift);
		if(offset < timekeeper.cycle_interval<<shift)
			shift--;
	}

	/* correct the clock when NTP error is too big */
	timekeeping_adjust(offset);

	/*
	 * Since in the loop above, we accumulate any amount of time
	 * in xtime_nsec over a second into xtime.tv_sec, its possible for
	 * xtime_nsec to be fairly small after the loop. Further, if we're
	 * slightly speeding the clocksource up in timekeeping_adjust(),
	 * its possible the required corrective factor to xtime_nsec could
	 * cause it to underflow.
	 *
	 * Now, we cannot simply roll the accumulated second back, since
	 * the NTP subsystem has been notified via second_overflow. So
	 * instead we push xtime_nsec forward by the amount we underflowed,
	 * and add that amount into the error.
	 *
	 * We'll correct this error next time through this function, when
	 * xtime_nsec is not as small.
	 */
	if (unlikely((s64)timekeeper.xtime_nsec < 0)) {
		s64 neg = -(s64)timekeeper.xtime_nsec;
		timekeeper.xtime_nsec = 0;
		timekeeper.ntp_error += neg << timekeeper.ntp_error_shift;
	}


	/*
	 * Store full nanoseconds into xtime after rounding it up and
	 * add the remainder to the error difference.
	 */
	xtime.tv_nsec =	((s64) timekeeper.xtime_nsec >> timekeeper.shift) + 1;
	timekeeper.xtime_nsec -= (s64) xtime.tv_nsec << timekeeper.shift;
	timekeeper.ntp_error +=	timekeeper.xtime_nsec <<
				timekeeper.ntp_error_shift;

	/*
	 * Finally, make sure that after the rounding
	 * xtime.tv_nsec isn't larger then NSEC_PER_SEC
	 */
	if (unlikely(xtime.tv_nsec >= NSEC_PER_SEC)) {
		int leap;
		xtime.tv_nsec -= NSEC_PER_SEC;
		xtime.tv_sec++;
		leap = second_overflow(xtime.tv_sec);
		xtime.tv_sec += leap;
		wall_to_monotonic.tv_sec -= leap;
		if (leap)
			clock_was_set_delayed();
	}

	timekeeping_update(false);
}

FILE: kernel/time/timekeeping.c
/**
 * timekeeper_setup_internals - Set up internals to use clocksource clock.
 *
 * @clock:		Pointer to clocksource.
 *
 * Calculates a fixed cycle/nsec interval for a given clocksource/adjustment
 * pair and interval request.
 *
 * Unless you're the timekeeping code, you should not be using this!
 */
static void timekeeper_setup_internals(struct clocksource *clock)
{
	cycle_t interval;
	u64 tmp, ntpinterval;

	timekeeper.clock = clock;
	clock->cycle_last = clock->read(clock);
以下略
この関数でセットしている。張り替えもできる模様。
static int change_clocksource(void *data)
FILE: kernel/time/timekeeping.c
/*
 * timekeeping_init - Initializes the clocksource and common timekeeping values
 */
void __init timekeeping_init(void)
{
	struct clocksource *clock;
	unsigned long flags;
	struct timespec now, boot;

	read_persistent_clock(&now);
	read_boot_clock(&boot);

	write_seqlock_irqsave(&xtime_lock, flags);

	ntp_init();

	clock = clocksource_default_clock();
	if (clock->enable)
		clock->enable(clock);
	timekeeper_setup_internals(clock);
...
おうふ...
clocksource_default_clock()のデフォルトは jiffiesを返すだけの論理的なクロックソースだ。
kernel/time/jiffies.c

ドライバの初期化処理で、タイマリソースを追加して、張り替える処理が走っていると想像。
/**
 * timekeeping_notify - Install a new clock source
 * @clock:		pointer to the clock source
 *
 * This function is called from clocksource.c after a new, better clock
 * source has been registered. The caller holds the clocksource_mutex.
 */
void timekeeping_notify(struct clocksource *clock)
{
	if (timekeeper.clock == clock)
		return;
	stop_machine(change_clocksource, clock, NULL);
	tick_clock_notify();
}
これか. inline関数になってる。SMPとSTOP_MACHINEが定義されていなければ関数呼び出しに置換される。
FILE: include/linux/stop_machine.h
change_clocksource( clock );


FILE: kernel/time/clocksource.c
#ifndef CONFIG_ARCH_USES_GETTIMEOFFSET

/**
 * clocksource_select - Select the best clocksource available
 *
 * Private function. Must hold clocksource_mutex when called.
 *
 * Select the clocksource with the best rating, or the clocksource,
 * which is selected by userspace override.
 */
static void clocksource_select(void)
コレ呼び出しているところが多数ある。。
clocksource_listの先頭がbest ratingになるらしい?
→追加するときに大きい物順になるようにコーディングしてある。
static void clocksource_enqueue(struct clocksource *cs)


カウントオーバーするまでに、起き上がる保証は?

なんとなく、登録情報で上限が定まるから、ここからタイマーを張っているような気がする。
タイマを貼ってしまったらTicklessじゃないような気がしないでもないよなぁ。

FILE: include/linux/clocksource.h
/*
 * Don't call __clocksource_register_scale directly, use
 * clocksource_register_hz/khz
 */
extern int
__clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq);
extern void
__clocksource_updatefreq_scale(struct clocksource *cs, u32 scale, u32 freq);

static inline int clocksource_register_hz(struct clocksource *cs, u32 hz)
{
	return __clocksource_register_scale(cs, 1, hz);
}
FILE: kernel/time/clocksource.c
/**
 * __clocksource_register_scale - Used to install new clocksources
 * @t:		clocksource to be registered
 * @scale:	Scale factor multiplied against freq to get clocksource hz
 * @freq:	clocksource frequency (cycles per second) divided by scale
 *
 * Returns -EBUSY if registration fails, zero otherwise.
 *
 * This *SHOULD NOT* be called directly! Please use the
 * clocksource_register_hz() or clocksource_register_khz helper functions.
 */
int __clocksource_register_scale(struct clocksource *cs, u32 scale, u32 freq)
{

	/* Initialize mult/shift and max_idle_ns */
	__clocksource_updatefreq_scale(cs, scale, freq);   // ★★★ここでcs->max_idle_nsに最長時間を設定してくれる。

	/* Add clocksource to the clcoksource list */
	mutex_lock(&clocksource_mutex);
	clocksource_enqueue(cs);
	clocksource_enqueue_watchdog(cs);
	clocksource_select();
	mutex_unlock(&clocksource_mutex);
	return 0;
}
EXPORT_SYMBOL_GPL(__clocksource_register_scale);
この最長時間を返す関数が以下にある。(timerクロックリソースを保持するオブジェクト(ファイルスコープ))
FILE: kernel/time/timekeeping.c
u64 timekeeping_max_deferment(void)
これを使っていて、スケジューラ絡みのソースは以下。
FILE: kernel/time/tick-sched.c
/**
 * tick_nohz_stop_sched_tick - stop the idle tick from the idle task
 *
 * When the next event is more than a tick into the future, stop the idle tick
 * Called either from the idle loop or from irq_exit() when an idle period was
 * just interrupted by an interrupt which did not cause a reschedule.
 */
void tick_nohz_stop_sched_tick(int inidle)
{

		if (ts->nohz_mode == NOHZ_MODE_HIGHRES) {
			hrtimer_start(&ts->sched_timer, expires,
				      HRTIMER_MODE_ABS_PINNED);
			/* Check, if the timer was already in the past */
			if (hrtimer_active(&ts->sched_timer))
				goto out;
		} else if (!tick_program_event(expires, 0))
				goto out;
tickを止める前に、タイマを貼ってますなぁ。ハイレゾが有効ならそれ。違えば普通の。

ハイレゾタイマが存在している場合。
FILE: kernel/hrtimer.c
/**
 * hrtimer_start_range_ns - (re)start an hrtimer on the current CPU
 * @timer:	the timer to be added
 * @tim:	expiry time
 * @delta_ns:	"slack" range for the timer
 * @mode:	expiry mode: absolute (HRTIMER_ABS) or relative (HRTIMER_REL)
 *
 * Returns:
 *  0 on success
 *  1 when the timer was active
 */
int hrtimer_start_range_ns(struct hrtimer *timer, ktime_t tim,
		unsigned long delta_ns, const enum hrtimer_mode mode)
{
	return __hrtimer_start_range_ns(timer, tim, delta_ns, mode, 1);
}
EXPORT_SYMBOL_GPL(hrtimer_start_range_ns);
"nohz_mode = NOHZ_MODE_HIGHRES"を設定しているのは、以下。
/**
 * tick_setup_sched_timer - setup the tick emulation timer
 */
void tick_setup_sched_timer(void)
{
	struct tick_sched *ts = &__get_cpu_var(tick_cpu_sched);
	ktime_t now = ktime_get();

	/*
	 * Emulate tick processing via per-CPU hrtimers:
	 */
	hrtimer_init(&ts->sched_timer, CLOCK_MONOTONIC, HRTIMER_MODE_ABS);
	ts->sched_timer.function = tick_sched_timer;

	/* Get the next period (per cpu) */
	hrtimer_set_expires(&ts->sched_timer, tick_init_jiffy_update());

	for (;;) {
		hrtimer_forward(&ts->sched_timer, now, tick_period);
		hrtimer_start_expires(&ts->sched_timer,
				      HRTIMER_MODE_ABS_PINNED);
		/* Check, if the timer was already in the past */
		if (hrtimer_active(&ts->sched_timer))
			break;
		now = ktime_get();
	}

#ifdef CONFIG_NO_HZ
	if (tick_nohz_enabled) {
		ts->nohz_mode = NOHZ_MODE_HIGHRES;
		printk(KERN_INFO "Switched to NOHz mode on CPU #%d\n", smp_processor_id());
	}
#endif
}
#endif /* HIGH_RES_TIMERS */

普通の。
/**
 * tick_program_event
 */
int tick_program_event(ktime_t expires, int force)
{
	struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);

	return tick_dev_program_event(dev, expires, force);
}

タイマリソースの張替えが自由にできてしまうので、
AMPで同一カウンタを参照するような実装をしたければ、
抜けないようにしておくのが良い。stop requestでnack返せばいいかなぁ?(要確認)





ハイレゾタイマがあれば、それを使ってしまうぽい?
FILE: kernel/timer.c
/*
 * This function runs timers and the timer-tq in bottom half context.
 */
static void run_timer_softirq(struct softirq_action *h)
{
	struct tvec_base *base = __this_cpu_read(tvec_bases);

	hrtimer_run_pending();

	if (time_after_eq(jiffies, base->timer_jiffies))
		__run_timers(base);
}
void __init init_timers(void)
{
	int err = timer_cpu_notify(&timers_nb, (unsigned long)CPU_UP_PREPARE,
				(void *)(long)smp_processor_id());

	init_timer_stats();

	BUG_ON(err != NOTIFY_OK);
	register_cpu_notifier(&timers_nb);
	open_softirq(TIMER_SOFTIRQ, run_timer_softirq);
}

FILE: kernel/timer.c
/*
 * Called from timer softirq every jiffy, expire hrtimers:
 *
 * For HRT its the fall back code to run the softirq in the timer
 * softirq context in case the hrtimer initialization failed or has
 * not been done yet.
 */
void hrtimer_run_pending(void)
{
	if (hrtimer_hres_active())   //★★ ->hres_activeを返す。
		return;

	/*
	 * This _is_ ugly: We have to check in the softirq context,
	 * whether we can switch to highres and / or nohz mode. The
	 * clocksource switch happens in the timer interrupt with
	 * xtime_lock held. Notification from there only sets the
	 * check bit in the tick_oneshot code, otherwise we might
	 * deadlock vs. xtime_lock.
	 */
	if (tick_check_oneshot_change(!hrtimer_is_hres_enabled())) // hrtimer_is_hres_enabled()は真になりやすい(hrtimer_hres_enabledを返す)
		hrtimer_switch_to_hres();
}
hrtimer_hres_active()が
/*
 * Switch to high resolution mode
 */
static int hrtimer_switch_to_hres(void)
{
	int i, cpu = smp_processor_id();
	struct hrtimer_cpu_base *base = &per_cpu(hrtimer_bases, cpu);
	unsigned long flags;

	if (base->hres_active)
		return 1;

	local_irq_save(flags);

	if (tick_init_highres()) {
		local_irq_restore(flags);
		printk(KERN_WARNING "Could not switch to high resolution "
				    "mode on CPU %d\n", cpu);
		return 0;
	}
	base->hres_active = 1;
	for (i = 0; i < HRTIMER_MAX_CLOCK_BASES; i++)
		base->clock_base[i].resolution = KTIME_HIGH_RES;

	tick_setup_sched_timer();
	/* "Retrigger" the interrupt to get things going */
	retrigger_next_event(NULL);
	local_irq_restore(flags);
	return 1;
}
clocksourceとclockeventとhrtimerと。
clockeventがhrtimerの代わりになれる?
clocksourceは、永続的に回るカウンタで、NO_HZに必要、か。
起き上がるのに別途タイマが必要になってくる。
(compare "以上"で割込みが出せるなら、一発でいける、か?)
hrtimerとしては登録しない

参考
 http://www.spinics.net/lists/linux-rt-users/msg06427.html


clocksource
 カレンダ時計みたいなもん。NO_HZで起き上がった時にデルタを見るために使う。
登録したratioの最大値のものを採用する(名前を指定して動的にもとれる)
デフォルトがjiffiesになっているので、昔ながらの定周期割込みでも運用できるぽい。

clockevent
 hrtimerにも使われる(どこでそうしているか未確認)
 属性をしっかりと設定して、PERIODIC,ONESHOTがアレば良さそう。
 インタフェースと使われ方を照会して、インプリする。

 ドキュメントはあまりない
(2013/03のコミットで、timerまわりの設計思想はthomasが書いているけど、ドライバの実装の話はナシ)



これ、ticklessだと?無効ですよ..
FILE: arm/kernel/time.c
#ifndef CONFIG_GENERIC_CLOCKEVENTS
/*
 * Kernel system timer support.
 */
void timer_tick(void)
{
	profile_tick(CPU_PROFILING);
	do_leds();
	xtime_update(1);
#ifndef CONFIG_SMP
	update_process_times(user_mode(get_irq_regs()));
#endif
}
#endif


別件

ARMのbootメモは、以下も詳しい。なぞる感じになるなぁ
http://amitshah.bizhat.com/arm/arm_linux_boot-1.html