[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);

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] kernel先頭から。その1

2013/07/17Linux::ARMimport

ARMv7 ARCHのARMから...

MPcoreから追加されている話も新規として扱います. 脳内はARM928EJ-Sあたりで停止していましたので..
メモリ空間の属性は、
  • ストロングリオーダ(Strongly ordered)
  • ノーマル()\メモリ属性は以下がある。
    • (Outer Shareable)
    • (Inner Shareable)
    • (Non shareable)
  • デバイス
    • (shareable)
    • (Non shareable)
ここでいうshareは、プロセッサ間で共有アクセスの有無を示す。
Outer/Inter sharebleの差異は、Outerが実装定義でレベルをつけたアクセス制御が入るらしい???
A3-27あたりからatomic accessに関する記載がある。

mm初期化処理を追う... ために, kernel起動先頭から少し追ってみる.

vmlinuxをdisasすると entry pointが "stext"なのがわかる.*1
entry pointには、以下のコメントがあり、展開される位置、状態、レジスタ値について記載がある。

・・・どのみちすぐにldscript読まなあかんかった。
FILE:arch/arm/kernel/vmlinux.lds
 .init.pv_table : {
  __pv_table_begin = .;
  *(.pv_table)
  __pv_table_end = .;
 }

 Kernel startup entry point.
 ---------------------------

 This is normally called from the decompressor code.  The requirements
 are: MMU = off, D-cache = off, I-cache = dont care, r0 = 0,
 r1 = machine nr, r2 = atags or dtb pointer.

 This code is mostly position independent, so if you link the kernel at
 0xc0008000, you call this at __pa(0xc0008000).

 See linux/arch/arm/tools/mach-types for the complete list of machine
 numbers for r1.

 We're trying to keep crap to a minimum; DO NOT add any machine specific
 crap here - that's what the boot loader (or in extreme, well justified
 circumstances, zImage) is for.
	/*
	 * r1 = machine no, r2 = atags or dtb,
	 * r8 = phys_offset, r9 = cpuid, r10 = procinfo
	 */
	bl	__vet_atags			// ゴミデータなら r2=NULL と修正される。
#ifdef CONFIG_SMP_ON_UP
	bl	__fixup_smp
#endif
#ifdef CONFIG_ARM_PATCH_PHYS_VIRT //enable
	bl	__fixup_pv_table	//
#endif
	bl	__create_page_tables
kernel imageがロードされた物理アドレス と kernel空間を設置しようとしている論理アドレス、との
アドレス値の差分を r8に計算、保持する。
__fixup_pv_tableを呼び出して、"arch/arm/include/asm/memory.h"で定義した関数・マクロの
論理ー物理変換関数が参照する変数を書き換える。
ロード位置が固定されていればいらないのかもしれないけれど,headの記述からするとそうもいかんような.
このheadは展開後のkernel start位置なので、compressed kernelの先頭は別のところ。*2

*1 : 本当は順序建てて ldscriptから..のほうがいいけれど, この手順でやってもうたので、こう記しておく。

*2 : たぶん、これだけど、未検証:arch/arm/boot/compressed/head.S

sched_switchの出力フォーマットに関するメモ

2013/03/10Linux::kernelimport

sched_switchの出力フォーマットに関するメモ

ステータス表示

"RMSDTtZXxKW"
char status
Rrunning
Mrunning-mutex
Ssleeping
Ddisk sleep
Tstopped
ttracing stop
Zzombie
Xdead
xdead
Kwakekill
Wwaking

FILE: linux-2.6/kernel/trace/trace_output.c
/* TRACE_CTX an TRACE_WAKE */
static enum print_line_t trace_ctxwake_print(struct trace_iterator *iter,
                         char *delim)

    T = task_state_char(field->next_state);
    S = task_state_char(field->prev_state);
    trace_find_cmdline(field->next_pid, comm);
    if (!trace_seq_printf(&iter->seq,
                  " %5d:%3d:%c %s [%03d] %5d:%3d:%c %s\n",
                  field->prev_pid,
                  field->prev_prio,
                  S, delim,
                  field->next_cpu,
                  field->next_pid,
                  field->next_prio,
                  T, comm))
        return TRACE_TYPE_PARTIAL_LINE;


static enum print_line_t trace_ctx_print(struct trace_iterator *iter, int flags)
{
    return trace_ctxwake_print(iter, "==>");
}

static enum print_line_t trace_wake_print(struct trace_iterator *iter,
                      int flags)
{
    return trace_ctxwake_print(iter, "  +");
}