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経由の設定に不一致は存在しない。

kexecの実装を調べる(ARM編)

2014/03/26linux::ARMimport

kexecを追う

ユーザランドでは、kexec-toolsを用いる。仕組みがわかっていれば、
システムコールを自前で叩いて準備も出来そう。

実際の処理はアーキテクチャ依存部にて行われる。
ここではARMについて調査をすすめる。

FILE: kernel/kexec.c

SYSCALL_DEFINE4(kexec_load, unsigned long, entry, unsigned long, nr_segments,
		struct kexec_segment __user *, segments, unsigned long, flags)
{
...
			result = kimage_normal_alloc(&image, entry,
							nr_segments, segments);
...
		result = machine_kexec_prepare(image);
...
		for (i = 0; i < nr_segments; i++) {
			result = kimage_load_segment(image, &image->segment[i]);
...
		kimage_terminate(image);
kernel imageやinitrdのイメージを物理ページへ転送して起動準備。

load image用メモリ確保(kernel/kexec.c)

static int kimage_normal_alloc(struct kimage **rimage, unsigned long entry,
				unsigned long nr_segments,
				struct kexec_segment __user *segments)
{
...
	image->control_code_page = kimage_alloc_control_pages(image,
					   get_order(KEXEC_CONTROL_PAGE_SIZE));

image->control_code_page には、連続した4096page=16MBの物理連続空間が割り当てられる。
以下の順序で関数が呼ばれて取得できる。この領域はreboot用につかう。
struct page *kimage_alloc_control_pages(struct kimage *image,
					 unsigned int order)
{
	struct page *pages = NULL;

	switch (image->type) {
	case KEXEC_TYPE_DEFAULT:
		pages = kimage_alloc_normal_control_pages(image, order);
		break;
DEFAULTとCRASH時の2種類あり。今はDEFAULTを追う。
static struct page *kimage_alloc_normal_control_pages(struct kimage *image,
							unsigned int order)
{

		pages = kimage_alloc_pages(GFP_KERNEL, order);
...
		if ((epfn >= (KEXEC_CONTROL_MEMORY_LIMIT >> PAGE_SHIFT)) ||
			      kimage_is_destination_range(image, addr, eaddr)) {
			list_add(&pages->lru, &extra_pages);
			pages = NULL;
		}
	} while (!pages);
...
	kimage_free_page_list(&extra_pages);
物理的に連続するページを確保して、転送先アドレスとの重複チェックを行う。
該当する場合は改めて取得を試みる。最後に未使用分は開放する。



範囲チェック(FILE: arch/arm/kernel/machine_kexec.c)

int machine_kexec_prepare(struct kimage *image)
...
memblock_is_region_memory(current_segment->mem,current_segment->memsz)
現在のkernelが把握している memory空間の有効範囲が指示されているかをチェックしているようだ。

ロード

static int kimage_load_segment(struct kimage *image,
				struct kexec_segment *segment)
{
...
	case KEXEC_TYPE_DEFAULT:
		result = kimage_load_normal_segment(image, segment);

static int kimage_load_normal_segment(struct kimage *image,
					 struct kexec_segment *segment)
{


FILE:kernel/reboot.c

SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
		void __user *, arg)
{
#ifdef CONFIG_KEXEC
	case LINUX_REBOOT_CMD_KEXEC:
		ret = kernel_kexec();
kexecシステムコールで保持したイメージデータは、
rebootシステムコールのKEXEC要求により発動する。


FILE: kernel/kexec.c

/*
 * Move into place and start executing a preloaded standalone
 * executable.  If nothing was preloaded return an error.
 */
int kernel_kexec(void)
{
	{
		kexec_in_progress = true;
		kernel_restart_prepare(NULL);
		migrate_to_reboot_cpu();
		printk(KERN_EMERG "Starting new kernel\n");
		machine_shutdown();
	}

	machine_kexec(kexec_image);

ドライバ・ユーザヘルパ?の終了をしつつ(kernel_restart_prepare(NULL))、
CPU0のみ稼働状態(ほかは停止へ遷移させる)
FILE: kernel/reboot.c
void kernel_restart_prepare(char *cmd)
{
	blocking_notifier_call_chain(&reboot_notifier_list, SYS_RESTART, cmd);
	system_state = SYSTEM_RESTART;
	usermodehelper_disable();
	device_shutdown();
}


FILE: arch/arm/kernel/machine_kexec.c
void machine_kexec(struct kimage *image)
{
...
	unsigned long reboot_entry = (unsigned long)relocate_new_kernel;
...
	/* copy our kernel relocation code to the control code page */
	reboot_entry = fncpy(reboot_code_buffer,
			     reboot_entry,
			     relocate_new_kernel_size);
★imageで渡されたロードイメージをページ単位で保存し、
 そのPFNをLookUpTableとして管理している。
 そのポインタを受け取って、指定された物理アドレスへイメージを
 転送するアセンブラコード本体を、コレでコピーする。
 コードはentry pointへのjumpも含まれている。
...
	printk(KERN_INFO "Bye!\n");

	if (kexec_reinit)
		kexec_reinit();

	soft_restart(reboot_entry_phys);
}

FILE: arch/arm/kernel/relocate_kernel.S
ENTRY(relocate_new_kernel)
...
	/* Jump to relocated kernel */
	mov lr,r1
	mov r0,#0
	ldr r1,kexec_mach_type
	ldr r2,kexec_boot_atags
 ARM(	mov pc, lr	)
 THUMB(	bx lr		)

	.align

	.globl kexec_start_address
kexec_start_address:
	.long	0x0

	.globl kexec_indirection_page
kexec_indirection_page:
	.long	0x0

	.globl kexec_mach_type
kexec_mach_type:
	.long	0x0

	/* phy addr of the atags for the new kernel */
	.globl kexec_boot_atags
kexec_boot_atags:
	.long	0x0

ENDPROC(relocate_new_kernel)

FILE: arch/arm/kernel/process.c
void soft_restart(unsigned long addr)
{
	u64 *stack = soft_restart_stack + ARRAY_SIZE(soft_restart_stack);
	/* Disable interrupts first */
...
	/* Disable the L2 if we're the last man standing. */
...
	/* Change to the new stack and continue with the reset. */
	call_with_stack(__soft_restart, (void *)addr, (void *)stack);
FILE: arch/arm//lib/call_with_stack.S
ENTRY(call_with_stack)
	str	sp, [r2, #-4]!
	str	lr, [r2, #-4]!

	mov	sp, r2
	mov	r2, r0
	mov	r0, r1

	adr	lr, BSYM(1f)
	mov	pc, r2

1:	ldr	lr, [sp]
	ldr	sp, [sp, #4]
	mov	pc, lr
ENDPROC(call_with_stack)
ARM-EABI仕様にしたがい、r0にLUTへのポインタ(PFN/dest-adr/end-mark/next-LUTの入った先頭ページ)、r1に関数アドレス、r2にスタック(static変数)が渡されてくる。
ここで使われるスタック領域が気がかりであり、旧kernelのstatic変数領域なのだが、
ロードイメージがその領域を侵食しない保証がないのではなかろうか。
control_iamgeの後半から使うとか、予め1枚用意しておくなどしたほうが良い気はする。

まとめ

以上より、以下のことが解った。
  • 新しいイメージの保存先は、転送先アドレスにぶつからないように作られている。
  • 渡されたイメージは、任意の物理アドレスにコピーされ、entryへのjumpも行われる。\→kernel imageに限らず任意の領域に任意のデータをおけそう。
  • 最終的にcpu0/割り込み全部禁止状態で、entryへ飛ぶ
  • reboot前の処理として、なにか作りこみたい場合は、以下の関数ポインタへ適切な関数へのアドレスをセットする。\設定方法は、mach-*のあたりで、直接代入するしかなさそうだ(mach-kirkwoodのみ該当@3.14-rc8)。
    void (*kexec_reinit)(void);
    
kexecに失敗するようならば、以下の観点が必要。
cpu0以外が生きている状態で切り替えようとしていないか。
通常のpower on resetと同じ状態にするのが望ましい。
これはペリフェラルに関しても同じことが言える。power managementを有効化して、
ドライバはpower save有効と指示されれば、ペリフェラルを極力寝かせる。
状態としても、ほぼPOR直後と同じ状態にしておくことが望ましい。
ソフト的な状態保持は影響を受けないので、
制御レジスタ・外部デバイスの設定値が効いてくるだろう。

もしくは、初期化処理で、既に初期化された状態でも正常に
動作するような実装であれば良い。

2014/02/22(土)shell芸?

all-ffのバイナリデータを作る

/dev/zeroがあるのに、/dev/ffみたいなのがないぽい。
ちょっとしたダミーデータとして、all-ffのデータが急に欲しくなったときにどうぞ(ねぇよ*1)
$ echo -ne \\xff > foo
$ cp foo bar; cat bar >> foo
$ cp foo bar; cat bar >> foo
$ cp foo bar; cat bar >> foo
$ cp foo bar; cat bar >> foo
$ cp foo bar; cat bar >> foo
・・・

*1 : あったから書くのです(;´Д`

2014/02/16(日)URL勝手に紹介

筑波大 新城先生の授業関係のWEBがとても良くまとまってます。
この科目、週に一度のペースだとすると、ついていけている人が居るようなら紹介して欲しいですね(ぉ

細かい部分、x86アーキ以外となると、自力で読まないとダメですが、大枠を見るには良いですネ。
自分で呼んだ時にも引用させていただこうと思います。いやポイントするだけで・・・