2014/11/26(水)[ARM][Linux]copy_{from|to}_user()

Linux Advent Calendar 2014の19日目に登録させていただきました。

copy_to_user()

kernel空間からusesr空間への安全なメモリコピー、と見てもらえれば良いでしょう。
単純なメモリコピーは、memcpy()を使いますが、これはユーザ空間のアドレスを
指定できることに意義があるもの。

ユーザ空間のメモリは必ずしも物理メモリに割りつけられているとは限らない。
まぁこれだけではないので、swap未使用であったりメモリが多くても使いましょう。
NAME
 copy_to_user - Copy a block of data into user space.

SYNOPSIS
 unsigned long copy_to_user(void __user * to, const void * from, unsigned long n);

ARGUMENTS
 to
   Destination address, in user space.

 from
   Source address, in kernel space.

 n
   Number of bytes to copy.

CONTEXT
 User context only. This function may sleep.

DESCRIPTION
 Copy data from kernel space to user space.
 Returns number of bytes that could not be copied. On success, this will be zero.


実装を追う

実際にコードを追った順序と記載順序が異なるので、変な感じです。

関数の実態は以下にあります。
アライメントを考慮した最適な転送を行うためのテンプレでコードが書かれており、
そこで使用するデータ転送の実態をcopy_to_userとcopy_from_userでマクロ定義し、
同一templateでコードを生成させています。
FILE: arch/arm/lib/copy_to_user.S
FILE: arch/arm/lib/copy_template.S

で、ここからが本題。strusrマクロでは、abort引数がついています。
これが何しているのか、何となく想像はつきますが、理解できなかった。
バイナリを逆アセンブルすると普通のstr命令が並ぶだけで、memcpyとの差異が判らず。
実コードと併せて定数テーブルを作っていることに気がつき、
このセクション名で検索したところ仕組みがわかったという流れでした。

FILE: copy_to_user.S
	.macro str1w ptr reg abort
	strusr	\reg, \ptr, 4, abort=\abort
	.endm
FILE: archinc/asm/assembler.h
	.macro	strusr, reg, ptr, inc, cond=al, rept=1, abort=9001f
	usracc	str, \reg, \ptr, \inc, \cond, \rept, \abort
	.endm

	.macro	usracc, instr, reg, ptr, inc, cond, rept, abort, t=TUSER()
	.rept	\rept
9999:
	.if	\inc == 1
	\instr\cond\()b\()\t \reg, [\ptr], #\inc
	.elseif	\inc == 4
	\instr\cond\()\t \reg, [\ptr], #\inc
	.else
	.error	"Unsupported inc macro argument"
	.endif

	.pushsection __ex_table,"a"
	.align	3
	.long	9999b, \abort
	.popsection
	.endr
	.endm

usracc str, \reg, \ptr, \inc(=4), \cond(=none,al), \rept(=none,1), \abort(=pop)
	stral reg, [ptr], #inc
	.pushsection __ex_table,"a"
	.align	3
	.long	9999b, \abort

セクション "__ex_table"に、{exceptionが発生するアドレス、返りアドレス}を設定する。
ldscriptにて、同名のセクションを定義、その前後のアドレスをを拾えるようにしてある。
FILE: /kernel/linux-stable/arch/arm/kernel/vmlinux.lds.S
	. = ALIGN(4);
	__ex_table : AT(ADDR(__ex_table) - LOAD_OFFSET) {
		__start___ex_table = .;
#ifdef CONFIG_MMU
		*(__ex_table)
#endif
		__stop___ex_table = .;

テーブルの先頭が変数名として判ったので、コレを探す。
これを参照しているのは、以下の関数。
/* Given an address, look for it in the exception tables. */
const struct exception_table_entry *search_exception_tables(unsigned long addr)
さらにこれを使っているところを探すと、アーキ依存部のpage fault処理部分が見つかる。

FILE: kernel/linux-stable/arch/arm/mm/extable.c
int fixup_exception(struct pt_regs *regs)
{
	const struct exception_table_entry *fixup;

	fixup = search_exception_tables(instruction_pointer(regs));
	if (fixup) {
		regs->ARM_pc = fixup->fixup;
#ifdef CONFIG_THUMB2_KERNEL
		/* Clear the IT state to avoid nasty surprises in the fixup */
		regs->ARM_cpsr &= ~PSR_IT_MASK;
#endif
	}

	return fixup != NULL;
}
さらにさらに、これを使っているところを探すと、
アーキ依存部のpage fault処理部分が見つかる。


FILE: kernel/linux-stable/arch/arm/mm/fault.c
/*
 * Oops.  The kernel tried to access some page that wasn't present.
 */
static void
__do_kernel_fault(struct mm_struct *mm, unsigned long addr, unsigned int fsr,
		  struct pt_regs *regs)
{
	/*
	 * Are we prepared to handle this kernel fault?
	 */
	if (fixup_exception(regs))
		return;
ここに来れば faultが発生しても、Oopsを吐かずに指定されたアドレスへ
帰って行くという仕組みになっている。

copy_to_user()に関しては、エラーハンドリングは特に行っていない。
失敗すればコピーを中断して返る、という実装になっている。
そのため、返値はコピーバイト数の残り、ということにしたのであろう。

で、この__do_kernel_fault()はどこから来たのかなぁと見ていくと、
fault handlerのほうへと流れ着くことになる。
ここらへんまできて、ちょっと深みにはまりそうだな、と感じたので
別記事にすることにします。

FILE: kernel/linux-stable/arch/arm/mm/fault.c
static int __kprobes
do_page_fault(unsigned long addr, unsigned int fsr, struct pt_regs *regs)

2014/04/04(金)ioremapで死ぬ件

これがでた要因。memory空間をremapしようとして、属性が異なる、と、検閲にひっかかった。
当該チェックは次のコミットで追加されたコードで行われている。

kernel parameterで mem=...を指定した際に、属性が設定されてしまったことが要因だったと思う。
ずいぶん前の事象なので、記憶から欠損している・・・ので、ゴミですね。
とりあえず、後日自分で引っかかるかもしれないので残しておく・・・。

たぶん、コレにひっかかっていたのだと思う・・・。たぶん。
void __iomem * __arm_ioremap_pfn_caller(unsigned long pfn,
/*
 * Don't allow RAM to be mapped - this causes problems with ARMv6+
 */
if (WARN_ON(pfn_valid(pfn)))
        return NULL;


linux kernelコミットログを漁る

commit 576d2f2525612ecb5af029a76f21f22a3b82563d
Author: Nicolas Pitre <nicolas.pitre@linaro.org>
Date:   Fri Sep 16 01:14:23 2011 -0400

    ARM: add generic ioremap optimization by reusing static mappings
    
    Now that we have all the static mappings from iotable_init() located
    in the vmalloc area, it is trivial to optimize ioremap by reusing those
    static mappings when the requested physical area fits in one of them,
    and so in a generic way for all platforms.
    
    Signed-off-by: Nicolas Pitre <nicolas.pitre@linaro.org>
    Tested-by: Stephen Warren <swarren@nvidia.com>
    Tested-by: Kevin Hilman <khilman@ti.com>
    Tested-by: Jamie Iles <jamie@jamieiles.com>
この手前で、vmlistの領域に引っかかっていれば、そのアドレスを返している(ioremapした空間だと思われ)。
上記コミットは最適化の結果なので、これより前にRussel氏がARMv6以降のmemory領域を
remapすることはNGだというコミットを出している(下記).
論理-物理mapのaliaseを設けるときに設定が異なるとマズイと言っている..のだな.


commit 309caa9cc6ff39d261264ec4ff10e29489afc8f8
Author: Russell King <rmk+kernel@arm.linux.org.uk>
Date:   Mon Jun 21 21:03:18 2010 +0100

    ARM: Prohibit ioremap() on kernel managed RAM
    
    ARMv6 and above have a restriction whereby aliasing virtual:physical
    mappings must not have differing memory type and sharability
    attributes.  Strictly, this covers the memory type (strongly ordered,
    device, memory), cache attributes (uncached, write combine, write
    through, write back read alloc, write back write alloc) and the
    shared bit.
    
    However, using ioremap() and its variants on system RAM results in
    mappings which differ in these attributes from the main system RAM
    mapping.  Other architectures which similar restrictions approch this
    problem in the same way - they do not permit ioremap on main system
    RAM.
    
    Make ARM behave in the same way, with a WARN_ON() such that users can
    be traced and an alternative approach found.
    
    Signed-off-by: Russell King <rmk+kernel@arm.linux.org.uk>
FILE: arch/arm/mm/ioremap.c

static mapping属性を追加@mm.h して,...

総論

ざっとarch/armのあたりを中心に探ってみた。ioremapでそのページ(セクションもありえる)の
属性(PTE)を設定しているので、二度目の設定がなされると、上記の問題に当たる。

Memory領域は(おそらく)、kernelが好きなようにいじってしまうので、
ioremapで任意の設定にすることが嫌われるのだと思う。
(だとすると、ioremapでマップしたメモリのcache設定とかは..どうなるのか)


mem=xxx@xxxで領域を指定した場合、kernelが論理物理マップをとるのがその範囲になる(はずな)ので、
MMUに対して属性の設定がされていない状態となる(少なくともそのCPUから見て)。
したがって、ioremap()が成功している時点で、そのCPUからみたMMU経由の設定に不一致は存在しない。

[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] 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