091014

|


SH4A続き。簡単なデマンドページングのVMを作ってみます。

デマンドページングの入口となるのはTLBミス例外。今回はカーネルモードのみ、
レジスタバンク1のみを使っている。なので例外に入った時のバンク切換えはないのでそのままそのスタックに退避している。例外が禁止
のまま実行です。ということでこの例外処理の間にさらにTLBミスすることはさ
せない方針です。これは単純化のため。

#include <asm/asm.h>
	.section	.vector_tlbmiss, "ax"	// "A"llocate. e"X"ecute.
	.align	2
	mov.l	r0,	@-r15
	mov.l	r1,	@-r15
	mov.l	r2,	@-r15
	mov.l	r3,	@-r15
	mov.l	r4,	@-r15
	mov.l	r5,	@-r15
	mov.l	r6,	@-r15
	mov.l	r7,	@-r15
	mov.l	r8,	@-r15
	mov.l	r9,	@-r15
	mov.l	r10,	@-r15
	mov.l	r11,	@-r15
	mov.l	r12,	@-r15
	mov.l	r13,	@-r15
	mov.l	r14,	@-r15
	sts.l	macl,	@-r15
	sts.l	mach,	@-r15
	sts.l	pr,	@-r15

	// All register saved.
引数にTLBミスを起こしたアドレスのVPNを渡します。
	mov.l	.L_PTEH, r0
	mov.l	.L_pager_load_page, r1
	jsr	@r1
	 mov.l	@r0,	r4
	lds.l	@r15+,	pr
	lds.l	@r15+,	mach
	lds.l	@r15+,	macl
	mov.l	@r15+,	r14
	mov.l	@r15+,	r13
	mov.l	@r15+,	r12
	mov.l	@r15+,	r11
	mov.l	@r15+,	r10
	mov.l	@r15+,	r9
	mov.l	@r15+,	r8
	mov.l	@r15+,	r7
	mov.l	@r15+,	r6
	mov.l	@r15+,	r5
	mov.l	@r15+,	r4
	mov.l	@r15+,	r3
	mov.l	@r15+,	r2
	mov.l	@r15+,	r1
	rte
	 mov.l	@r15+,	r0
	.align	2
.L_PTEH:
	.long	0xff000000
.L_pager_load_page:
	.long	_pager_load_page


そのハンドリングは

#include <system.h>
#include <console.h>
#include <fpool.h>
#include <pte.h>
#include <vm.h>
#include <cpu.h>
#include <mmu.h>
#include <string.h>

アドレス変換されるP0領域とP3にそれぞれページテーブルページのディレクト
リを作ります。
struct pte ptp_slot_p0[PTP_ENTRY];
struct pte ptp_slot_p3[PTP_ENTRY];

void pte_setup (struct pte *, paddr_t);
void pager_tlb_update (struct pte *, vaddr_t);
void alloc_page_p3 (struct vm_page **, struct pte *);

#define	PTP_INDEX_OF(x) (((x) >> PTP_INDEX_SHIFT) & PTP_INDEX_MASK)

void
pager_load_page (vaddr_t vaddr)
{
  struct pte *ptp;
  struct vm_page *vm;

  iprintf ("%s va=%x ", __FUNCTION__, vaddr);

例外を起こしたアドレスがP0かP3かを分けます。
  if (vaddr & 0x80000000)
    {
      iprintf ("P3\n");
P3の時は0xc0000000からなので、マスクします。
      ptp = &ptp_slot_p3[PTP_INDEX_OF (vaddr & 0x1fffffff)];
    }
  else
    {
      iprintf ("P0\n");
      ptp = &ptp_slot_p0[PTP_INDEX_OF (vaddr)];
    }

  if (ptp->ptel == 0)	// Page table page not allocated.
    {
ページテーブルページが確保されてなかったらこれから確保します。
これはP3に任意にマップします。(P0は2G全てマップできるように)
      alloc_page_p3 (&vm, ptp);
      iprintf ("PTP allocated pa=%x va=%x\n", vm->pa, vm->va);
    }
  else
    {
すでにページテーブルページがあったのなら、その仮想アドレスを
とります。ここで参照しているけれど、結構ロス。PTEに仮想アドレスも
乗せてしまった方が効率的かも。

 物理アドレス29bitモードだけだったSH4までなら、全てのメモリはP1/P2から
アクセスできたので、物理アドレスがわかれば対応するP1/P2アドレスに変換し
てアクセスという手段が使えた(NetBSDの実装はそう)。しかしSH4Aの物理アド
レス32bitモードになると、必ずしもP1/P2からアクセスできるとは限らないの
で、仮想アドレスでアクセスする必要がある。あるいは、P1,P2でアクセスでき
る領域のアロケータを用意してそこからひっぱるか。
      vm = vm_lookup_by_paddr (ptp->ptel & PTEL_PPN);

このルーチンではTLBミスを起こさないという前提なので、確実にTLBに乗せま
す。もし既に乗っていたらTLB多重ヒット例外を起こしてしまうので、まず確実
にTLBから落とします。
      sh4a_tlb_invalidate_addr (0, vm->va);
そして乗せます。これでページテーブルページがアクセスできるようになった。
      pager_tlb_update (ptp, vm->va);
      iprintf ("PTP found. pa=%x va=%x\n", ptp->ptel & PTEL_PPN, vm->va);
    }
  // Page table page is setuped.

  // Target page table entry.
これは例外を起こしたアドレスに該当するページのエントリです。
  struct pte *pte = (struct pte *)vm_vaddr (vm) +
    ((vaddr >> PTP_OFFSET_SHIFT) & PTP_OFFSET_MASK);
  iprintf ("PTE=%x\n", pte);

そのページがまだ登録されてなければ、物理ページを拾ってきてマップします。
  if (pte->ptel == 0)
    {
      // not mapped area.
      paddr_t pa = fpool_get_page ();
      pte_setup (pte, pa);
      vm = vm_page_allocate ();
      vm_page_register (vm, vaddr, pa);
      iprintf ("New map pa=%x va=%x\n", pa, vaddr);
   }

そしてTLBに乗せます。ここで例外から終了して、この仮想アドレスにアクセス
できる。
  pager_tlb_update (pte, vaddr);
}

void
pte_setup (struct pte *pte, paddr_t pa)
{
  uint32_t ptel, ptea;

  ptel = pa & PTEL_PPN;
  ptea = 0;
  sh4a_tlb_entry_pagesize (PG_8K, &ptel, &ptea);
  sh4a_tlb_entry_protect (P_RW, &ptel, &ptea);
  sh4a_tlb_entry_cache (WRITEBACK, &ptel, &ptea);
さらにTLBミスを起こさない(PTE_V)、書き込み可(PTE_D)設定に。
  pte->ptel = ptel | PTEL_D | PTEL_V;
  pte->ptea = ptea;
}

void
pager_tlb_update (struct pte *pte, vaddr_t va)
{

このカウンタのインクリメントはいらないかも。これは連続してLDTLBをすると
同じエントリを上書きしてしまうのを防ぐためのもの。
  // Increment UTLB counter.
  int cnt = (sh4a_tlb_counter_get () + 1) & MMUCR_URC_MASK;
  sh4a_tlb_counter_set (cnt);

  *SH4A_PTEH = va & PTEH_VPN;
  *SH4A_PTEL = pte->ptel;
#ifdef SH4A_EXT_MMU
  *SH4A_PTEA = pte->ptea;
#endif
  __asm volatile ("ldtlb");
  CPU_SYNC_RESOURCE ();
}

void
alloc_page_p3 (struct vm_page **vmp, struct pte *ptep)
{
  struct vm_page *vm;
  struct pte *pte;
  paddr_t pa;
  vaddr_t va;

  pa = fpool_get_page ();適当に物理ページを拾ってきて、
  vm = vm_page_allocate ();仮想ページの管理構造体を確保。
  va = vm_page_map_p3 (vm);ここで適当なP3のアドレスを拾って設定。
  vm_page_register (vm, va, pa);物理-仮想を確定。
  // Load entry to the TLB. この設定をTLBに設定。
  pte = &ptp_slot_p3[PTP_INDEX_OF (va & 0x1fffffff)];
  pte_setup (pte, pa);
  pager_tlb_update (pte, va);
  // Clear page table page.
  memset ((void *)va, 0, PAGE_SIZE);

  *vmp = vm;
  *ptep = *pte;
}


vmはまず簡単にこの程度。
struct vm_page __vm_pool[1024];プールの数はとりあえず適当に十分なところで。
int __vm_pool_index;
vaddr_t vm_base = 0xc0000000;このVMから任意にマップされる仮想アドレスはP3です。

struct vm_page *
vm_page_allocate ()
{
  struct vm_page *vm = __vm_pool + __vm_pool_index++;

  return vm;
}

vaddr_t
vm_page_map_p3 (struct vm_page *vm)
{

  vm->va = vm_base;
  vm_base += PAGE_SIZE;

  return vm->va;
}

struct vm_page *
vm_lookup_by_paddr (paddr_t paddr)
{
  int i;

  for (i = 0; i < 1024; i++)
    if (__vm_pool[i].pa == paddr)
      {
	iprintf ("%s pa %x -> va %x\n", __FUNCTION__,
		 __vm_pool[i].pa, __vm_pool[i].va);
	return __vm_pool + i;
      }

  return 0;
}

void
vm_page_register (struct vm_page *vm, vaddr_t va, paddr_t pa)
{

  vm->va = va;
  vm->pa = pa;
}

vaddr_t
vm_vaddr (struct vm_page *vm)
{

  return vm->va;
}


物理アドレスの固定長プールも簡単にこの程度。ただ順々にページサイズごと
に出すだけ。

static paddr_t pool;
static psize_t limit;

void
fpool_load_memory (paddr_t a, psize_t sz)
{
  pool = ROUND_PAGE (a);
  limit = TRUNC_PAGE (a + sz);
  iprintf ("pool memory %x-%x\n", pool, limit);
}

paddr_t
fpool_get_page ()
{
  paddr_t a = 0;

  if (pool < limit)
    {
      a = pool;
      pool += PAGE_SIZE;
    }

  return a;
}

テスト。

=> go 0x89000000
## Starting application at 0x89000000 ...
stack_start: 0x89100000
RAM data: 0x8900819c-0x89008708 1388byte
bss: 0x89008720-0x8900f4b0 28048byte
Privilege-mode, bank 1, Exception disabled, FPU enabled, IMASK=0xf
bss memory check passed.
PMB page size 16MB mask=ffffff VPN:a4000000 PPN:4000000
PMB page size 128MB mask=7ffffff VPN:90000000 PPN:58000000
md_thread_create: [2]:uart recv pc=89004924 sp=8900bf78 stack=1024byte
thread_start: [2]
md_thread_create: [3]:uart send pc=890048b4 sp=8900c450 stack=1024byte
thread_start: [3]
md_thread_create: [4]:test pc=8900199c sp=8900b27c stack=1024byte
thread_start: [4]
test_thread:1234abcd
INTC: fffffff6 1f000000
hello 0
ここでVMをスタート。
test4> vm
pool memory 5c000000-60000000
この段階ではTLBにはなにもなし。
test4> tlbdump                                                  
MMUCR: 2c000081 PTEH: 0 PTEL: 0 TTB: 0 TEA: 0 PTEA: 0
--------------------ITLB--------------------
(-|V)  16M -- WB VPN:89000000 PPN:9000000 PMB [3]
--------------------UTLB--------------------
ここで仮想アドレス0をアクセス。
test4> mem r 0
TLBミス例外が起きて
pager_load_page va=0 P0
ページテーブルページを確保して、
PTP allocated pa=5c000000 va=c0000000
PTE=c0000000
そこに物理ページ5c002000を拾ってきて仮想アドレス0にマップ
New map pa=5c002000 va=0
例外終了。
(r)0: ac1dcafe
test4> tlbdump
MMUCR: 2c003881 PTEH: 0 PTEL: 5c00210c TTB: 0 TEA: 0 PTEA: 0
--------------------ITLB--------------------
(-|V)  16M -- WB VPN:89000000 PPN:9000000 PMB [3]
--------------------UTLB--------------------
これはP3のページテーブルページ。
(D|V) [rwx][---]   8K -- WB -- VPN:c0000000 PPN:5c000000 ASID:0 [1]
これはP0に今回新しくマップしたところ。
(D|V) [rwx][---]   8K -- WB -- VPN:0 PPN:5c002000 ASID:0 [13]
既にTLBに乗っているので例外は起きない。
test4> mem r 0
(r)0: ac1dcafe
ここでTLBを全てクリア。
test4> tlbclear
test4> tlbdump                                                  
MMUCR: 78003c81 PTEH: 0 PTEL: 5c00210c TTB: 0 TEA: 0 PTEA: 0
--------------------ITLB--------------------
(-|V)  16M -- WB VPN:89000000 PPN:9000000 PMB [2]
--------------------UTLB--------------------
もう一度仮想アドレス0を読んでみます。
test4> mem r 0
pager_load_page va=0 P0
既にページテーブルページはあるので、そこをTLBに乗せて、
vm_lookup_by_paddr pa 5c000000 -> va c0000000
PTP found. pa=5c000000 va=c0000000
PTE=c0000000
もう一度TLBの乗せます。
(r)0: ac1dcafe
新しく遠いところをマップしてみます。
test4> mem r 70000000                                           
pager_load_page va=70000000 P0
PTP allocated pa=5c004000 va=c0002000
PTE=c0002000
New map pa=5c006000 va=70000000
(r)70000000: 5c00810d
test4> mem r 70000000
(r)70000000: 5c00810d
test4> tlbdump                                                  
MMUCR: 78009081 PTEH: 70000000 PTEL: 5c00610c TTB: 0 TEA: 70000000 PTEA: 70000000
--------------------ITLB--------------------
(-|V)  16M -- WB VPN:89000000 PPN:9000000 PMB [2]
--------------------UTLB--------------------
(D|V) [rwx][---]   8K -- WB -- VPN:c0000000 PPN:5c000000 ASID:0 [16]
(D|V) [rwx][---]   8K -- WB -- VPN:0 PPN:5c002000 ASID:0 [20]
(D|V) [rwx][---]   8K -- WB -- VPN:c0002000 PPN:5c004000 ASID:0 [22]
(D|V) [rwx][---]   8K -- WB -- VPN:70000000 PPN:5c006000 ASID:0 [34]
test4>