[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と表記されています。

[ARM] compressed kernel

2013/10/20linux::ARMimport

zImageの展開処理

調査対象は、【kernel 3.0.39 "Sneaky Weasel"】です。

例として、u-bootから起動する場合を考えます。
ここで上げるのは、とあるボードへの移植時に調査した値です。
ボード依存部の値は、デタラメに書いている可能性があるので、注意してください。


NOR bootの場合は、ROM上のコード実行が可能ですが、昨今の方式*1ではbootloaderがDRAM上に展開してから実行することが多いでしょう。

u-bootが読み込むファイル、uImageは、zImageにuImageヘッダがついたもの。
図にしてみると以下のイメージ。(AAでスミマセン)
+------------------------+
| u-boot header(64oct.)  |
+------------------------+
| zImage decompress code |
| .text                  |
| (with compressed data )|
| .got                   |
| .got.plt               |
| .bss                   |
+------------------------+
このデータの読み込みは、u-bootによって行われるため、任意のアドレスに展開されます。


ldscriptを見ると、ざっくりとこんな感じ。
  _text = .;
  .text : {}
  _etext = .;
  _got_start = .;
  .got			: { *(.got) }
  _got_end = .;
  .got.plt		: { *(.got.plt) }
  _edata = .;

  __bss_start = .;
  .bss			: { *(.bss) }
  _end = .;

*1 : NAND/SDから2nd boot programを読み出して、本当のローダを呼び出す。

事前準備

実際には、コードを読みながら参照していった結果です。

FILE: arch/arm/boot/compressed/Makefile
# Supply ZRELADDR to the decompressor via a linker symbol.
ifneq ($(CONFIG_AUTO_ZRELADDR),y)
LDFLAGS_vmlinux += --defsym zreladdr=$(ZRELADDR)
ZRELADDRは、以下で定義されます。
FILE: arch/arm/boot/Makefile
ZRELADDR    := $(zreladdr-y)
右辺は、SoC依存部にて定義されています。
FILE: arch/arm/mach-*/Makefile.boot
zreladdr-y	:= 0x40008000
params_phys-y	:= 0x40000100
initrd_phys-y	:= 0x40800000


zImage先頭から

エントリーポイントはここ。

bootloaderから飛んでくるところ

アドレスは任意。

FILE: ./boot/compressed/head.S
start:
 ※cache有効, TLBはVA-PA一対一対応, 

restart:
		.align	2
		.type	LC0, #object
LC0:
		.word	LC0					@ r1
		.word	__bss_start			@ r2
		.word	_end				@ r3
		.word	_edata				@ r6
		.word	input_data_end - 4	@ r10 (inflated size location)
		.word	_got_start			@ r11
		.word	_got_end			@ ip(r12)

		.word	.L_user_stack_end	@ sp

		.size	LC0, . - LC0

		/*
		 * We might be running at a different address.  We need
		 * to fix up various pointers.
		 */
		sub	r0, r0, r1		@ calculate the delta offset
		add	r6, r6, r0		@ _edata
		add	r10, r10, r0	@ inflated kernel size location
LC0(DRAMの位置)から、"LC0"の配置した位置(ldscriptでは先頭0、コード配置後のLC0の位置)を差っ引いて、r0に保持する。
圧縮カーネルの、展開後のサイズは、build systemによって、圧縮カーネルの後ろにLEで付与される。したがって、r10へloadする値は、input_data_endから4引いてある。(32bit決め打ちだね)
spも、relocate後のアドレスをセット。r10にsp+64kのアドレスをセット(malloc用に64k確保)(CONFIG_ZBOOT_ROM未定義の場合。定義時はコード本体がROMに入るから、_edataの後ろに配置する)

このイメージがロードされる位置がどこでも動けるようになっている。

展開後のkernel位置と、この展開コード、edata類の空間が重なっているかをチェックする。重複している場合、reset〜edataまでを、後ろからコピーする(memmove相当)。その後、コピーした後のrestartへjumpする。(これで重複しない位置で再度relocateする)

※ユーザモードで入ってきた場合、SVCで特権を得るようになってます。
ヴェクタ位置へのロードを期待しているようですが、必ずしもそうではないから、ちょっと不味そうな気がするネ。

relocate後

decompress処理のソフトと、圧縮イメージとを再配置して、kernel imageを展開できる状態になって、wont_overwriteへたどり着く。前述のように、最初のバイナリがロードされる位置によって、ここへ直接来ることもあれば、relocateすることもありますね。
wont_overwrite:
/*
 * If delta is zero, we are running at the address we were linked at.
 *   r0  = delta
 *   r2  = BSS start
 *   r3  = BSS end
 *   r4  = kernel execution address
 *   r7  = architecture ID
 *   r8  = atags pointer
 *   r11 = GOT start
 *   r12 = GOT end
 *   sp  = stack pointer
 */
・GOT書き換え(オフセット分加算して、飛び先がrelocate先のアドレスになるように)
・BSSをゼロクリア


本来のImage(vmlinux)へ

kernelの展開先アドレスを第一引数、spからsp+64kを第二〜三引数に渡して、第四引数にアーキテクチャIDをのせてcall。
void decompress_kernel(unsigned long output_start, unsigned long free_mem_ptr_p, unsigned long free_mem_ptr_end_p, int arch_id)
展開が終わったら、cache clean、cache-offしてから、圧縮前のkernel先頭へ飛ぶ。