sunsetyang
驱动小牛
驱动小牛
  • 注册日期2001-03-23
  • 最后登录2007-03-06
  • 粉丝0
  • 关注0
  • 积分0分
  • 威望0点
  • 贡献值0点
  • 好评度0点
  • 原创分0分
  • 专家分0分
阅读:3286回复:0

booting linux for ia64 [frowarded from USTC bbs]

楼主#
更多 发布于:2002-01-30 02:14
Booting Linux IA64...( No need for Ia64 basic yet)
---------------------
* Relative Files:
 $/arch/ia64/boot/
 + bootloader.c
 + bootloader.lds
 
 $/arch/ia64/
 + vmlinux.ld.S

*
  First, Let\'s look into vmlinux.ld.S; It\'s ld script,which
is used when linking target \"vmlinux\";we can get kernel image
map from it; I don\'t think there is anything special here.
But please note that many AT((.section) - PAGE_OFFSET); From
ld manual, we know that it means the section will be loaded at
result of AT((.section) - PAGE_OFFSET)), while it\'s relocatation(virtual)
address still starts from PAGE_OFFSET;
  //Mabye here, we should ask for why?


*
 Let\'s looking into bootloader.c; As we know, it\'s used to load
kernel image(vmlinux) into memory; and transfer control to vmlinux;
that\'s how kernel is really started;

 I would like to talk about these lines:
    ...
    for (i = 0; i < e_phnum; ++i) {
        //Note 1
        req.len = sizeof(*elf_phdr);
        req.addr = (long) mem;
        ssc(fd, 1, (long) &req, e_phoff, SSC_READ);
        ssc((long) &stat, 0, 0, 0, SSC_WAIT_COMPLETION);
        if (stat.count != sizeof(*elf_phdr)) {
            cons_write(\"failed to read phdr\\n\");
            return;
        }
        e_phoff += sizeof(*elf_phdr);

        elf_phdr = (struct elf_phdr *) mem;
        
        req.len = elf_phdr->p_filesz;
        req.addr = __pa(elf_phdr->p_vaddr); // Note2
        ssc(fd, 1, (long) &req, elf_phdr->p_offset, SSC_READ);
        ssc((long) &stat, 0, 0, 0, SSC_WAIT_COMPLETION);
        memset((char *)__pa(elf_phdr->p_vaddr) +
elf_phdr->p_filesz, 0,
            elf_phdr->p_memsz - elf_phdr->p_filesz);
    }
    ...

 Note 1 : Read ELF program header, to determine the size, address of
sections
 Note 2 : Read section into __pa(.->p_vaddr); that\'s same as
AT(.(section) -..);
      then set rest of section to zero;

      +----------------------+
      |    xxx    | zero     |
      +----------------------+
      |-- filesz --|
      |------- memsz --------|

 
 and then, starting kernel now, jump to e_entry;
 ...
 e_entry = elf->e_entry;
 ...
 asm volatile (\"mov sp=%2; mov r28=%1; br.sptk.few %0\"
   :: \"b\"(e_entry), \"r\"(bp), \"r\"(__pa(&stack)));
 ...
 
 and in vmlinux.ld.S
 ...
 ENTRY(_start)
 OUTPUT_ARCH(ia64)
 ENTRY(phys_start)
 SECTIONS
 {
  /* Sections to be discarded */
  /DISCARD/ : {
    *(.text.exit)
    *(.data.exit)
    *(.exitcall.exit)
    }

  v = PAGE_OFFSET;   /* this symbol is here to make debugging
easier... */
  phys_start = _start - PAGE_OFFSET;

  . = KERNEL_START;
  .....

  And things get clear now:
  * Kernel is loaded at KERNEL_START - PAGE_OFFSET
  * entry is _start \'s load address
  * jump to e_entry is jump to _start
  *
  +------------+ 0x0
  |        |
  | 68*4k*4k    | <- (from bootloader.lds, we know that bootloader will be
loaded at 0x100000)
  | (???)    |
  |        |
  +------------+ 0x00004400000
  |...        |
  |...        |
  |_start:    |
  |...        |
  |...        |
  |        |
  +------------+

  and we could find \"_start\" in arch/ia64/kernel/head.S easy;
  XXX I find there are very different here between different kernel
verisons, but basicly,
  XXX they do the same things, only in different ways ;-)

* What\'s more?
 Here, we don\'t talk about how bootloader is loaded and executed. Some
guys know anything about it?
 share it!
Booting Linux IA64 ...
----------------------

* Relative files:
 $/arch/ia64/
 +vmlinux.lds.S

 $/arch/ia64/kernel/
 + head.S
 + init_task.c

 $/init/
 + main.c

* Switch into Virutal Mode:

  As we known, $/arch/ia64/boot/bootloader.c, will transfer CPU control
to _start in head.S (via jump); When control is transferred to _start, the
bootload has already loaded us to the correct address. All that\'s left to do
here is to set up the kernel\'s global pointer and jump to the kernel entry point.
 
  Kernel Image Map ( Physical addressing):

  +----------+ <- 0x000
  |  xxx  |        
  ---------- <- 0x100000 (boot loader)
  |     |
  | .text  |
  |     |
  | .data  |<- static variables \"stack\", \"memory\", \"buffer\" is located here
  |     | 
  +----------+
   ...
  +----------+ <- 0x440,0000 (68*4k*4k)
  |     |
  |  xxx  | <- kernel image
  |     | 
  +----------+

  Before switch to virtual mode, we have to prepare some processor registers,
kernel mapping, so that kernel could get to work correctly; So we have to setup 
mapping of kernel data and text;
 
    /*
     * Initialize the region register for region 7 and install a translation register
     * that maps the kernel\'s text and data:
     */
  1  rsm psr.i | psr.ic
  2  mov r16=((ia64_rid(IA64_REGION_ID_KERNEL, PAGE_OFFSET) << 8) | (_PAGE_SIZE_64M << 2))
  3  ;;
  4  srlz.i
  5  mov r18=_PAGE_SIZE_64M<<2
  6  movl r17=PAGE_OFFSET + 64*1024*1024
  7  ;;
  8  mov rr[r17]=r16
  9  mov cr.itir=r18
  10  mov cr.ifa=r17
  11  mov r16=IA64_TR_KERNEL
  12  movl r18=(64*1024*1024 | PAGE_KERNEL)
  13  ;;
  14  srlz.i
  15  ;;
  16  itr.i itr[r16]=r18
  17  ;;
  18  itr.d dtr[r16]=r18
  19  ;;
  20  srlz.i

  Here, if you want to understand what these codes do, pls see ia64 manual
(vol2, ch.4) detailly; We need to know the following things:
  * Kernel Page Size is 64M ( It\'s big engouh,one single page contains
   whole kernel image)
  * Kernel Image is mapped starting from physical address 64M to virtual
   address PAGE_OFFSET+64M

 
  Now ready to switch now, and the switching process is simple;

    /*
    * Switch into virtual mode:
    */
 1   movl r16=(IA64_PSR_IT|IA64_PSR_IC|IA64_PSR_DT|IA64_PSR_RT|IA64_PSR_DFH|IA64_PSR_BN)
 2   ;;
 3   mov cr.ipsr=r16
 4   movl r17=1f
 5   ;;
 6   mov cr.iip=r17
 7   mov cr.ifs=r0
 8   ;;
 9   rfi
    1://label
    ...
   We know that and ipsr,iip,ifs is used to restore processor state on
Return From interrupt(rfi); and ipsr is used to reload psr; iip is used to
restore ip; ifs is used to reload current stack frame(CFM); More details
see IA64 manual;
   1-3 Setup ipsr,
     -- with enabling IA64_PSR_IT, IA64 virtual instruction address are
      translated and access with rights check, and before this,
      instruction access use physical addressing;
     -- with enabling IA64_PSR_DT, IA64 virtual data address are translated
      and access with rights check; and before this, data access use
       physical addressing;
     -- with enabling IA64_PRS_RT, IA64 register stack are translated and
      access rights are checked; if disabled,the access use physical
      addressing;
     -- with enabling IA64_PRS_IC, that\'s enbling Interrupt colleciton;
      when interruption occurs, current state of processor is loaded in
      IIP, IPSR, IIM, and IFS;
     -- with enabling IA64_PSR_DFH, will disable high part of float-point
      registers sets (f32-f127); that means,we could only use lower part of
      float-pointer register files in kernel; this should consider for
      effective task switching;
     -- with enabling IA64_PSR_BN, GR16-32 will be accessable for bank 1;

     Pls see IA64 manual for more details; These bits are really seriously affect
     following kernel action in future;

   6  Setup iip to //label ( next instruction to be executed)
   7  Setup ifs to zero
   9  return from interrupt, the above registers are loaded into corresponding registers;


    and now ...
   
* Welcome to new world!
 So where we are? we are now in virtual mode now; What\'s more need to do?
 * Setup interrupt handler vector (IVT-based), (??)
 * Setup global data pointer, so that function calls & global variables could
  be referenced correctly

 From linux for ia6 kernel design document ,we should know
 * r13  -- contains current task pointer, (see current.h); (in user-model,
       as defined is software convention, r13 is reserved for thread
       pointer);
 * ar.k6 -- physical address of current task structure
 * ar.k7 -- physical address of page table

 And with these hints, let\'s come to the code;
 ...
 #ifdef CONFIG_SMP
    /*
     * Find the init_task for the currently booting CPU. At poweron, and in
     * UP mode, cpucount is 0.
     */
    movl r3=cpucount
    ;;
    ld4 r3=[r3]       // r3 <- smp_processor_id()
    movl r2=init_tasks
    ;;
    shladd r2=r3,3,r2
    ;;
    ld8 r2=[r2]
  #else
    mov r3=0
    movl r2=init_task_union
    ;;
  #endif
  ...
 
  (DON\'T care SMP or UP here), r3 contain current processor_id(zero if in
  UP-mode),and r2 contains idle process task_struct pointer; it\'s
  virtual address;


  ...
  // -- Note 1
  cmp4.ne isAP,isBP=r3,r0
  ;;          // RAW on r2
  extr r3=r2,0,61       // r3 == phys addr of task struct
  ;;
    
  // -- Note 2
  // load the \"current\" pointer (r13) and ar.k6 with the current task
  mov r13=r2
  mov IA64_KR(CURRENT)=r3       // Physical address

  // -- Note 3
  // initialize k4 to a safe value (64-128MB is mapped by TR_KERNEL)
  mov IA64_KR(CURRENT_STACK)=1

  // -- Note 4
  /*
 * Reserve space at the top of the stack for \"struct pt_regs\". Kernel threads
 * don\'t store interesting values in that structure, but the space still needs
 * to be there because time-critical stuff such as the context switching can
 * be implemented more efficiently (for example, __switch_to()
 * always sets the psr.dfh bit of the task it is switching to).
  */
  addl r12=IA64_STK_OFFSET-IA64_PT_REGS_SIZE-16,r2
  addl r2=IA64_RBS_OFFSET,r2  // initialize the RSE
  mov ar.rsc=0     // place RSE in enforced lazy mode
  ;;
  loadrs            // clear the dirty partition
  ;;
  mov ar.bspstore=r2  // establish the new RSE stack
  ;;
  mov ar.rsc=0x3        // place RSE in eager mode
  ...
 
  Note 1:
        
  and here is something interesting:
  $/arch/ia64/kernel/init_task.c
  ...
  /*
  * Initial task structure.
  *
  * We need to make sure that this is page aligned due to the way
  * process stacks are handled. This is done by having a special
  * \"init_task\" linker map entry..
  */
  union task_union init_task_union
    __attribute__((section(\"init_task\"))) =
        { INIT_TASK(init_task_union.task) };

  $/arch/ia64/vmlinux.ld.S
  ...
  /* The initial task and kernel stack */
  init_task : AT(ADDR(init_task) - PAGE_OFFSET)
      { *(init_task) }
  ...
  
  So , \"extra r3=r2, 0 ,61 \" simply got rid of high 3-bit RID; and r3 now
  contains the physical address of init_task\'s task_struct pointer;

  
  Note 2:
  It seems the comments make things clear engouh ;-) But pls note the
  difference from r13 and ar.k6:
     -- r13 contains virtual address; and ar.k6 contains physical address.
  (Question: why two register is used here?)
 
  Note 3:
  (Question: 1?)
  Note 4:
  You may found this map in the source code, but let\'s make it ourself;
    r12 -- by software convention, contains thread stack
  and we must remember here , the address used here are all physical,
  not virtual.

  (init_task)
  +--------------+  <- physical address (low)
  | task_struct |
  +--------------+  <- BSP ( down)
  |  ///    |   
  |  ///    |   
  +--------------+  <- stack pointer(up)
  | pt_regs   |
  +--------------+            (high)


* 
  Then _start call start_kernel() to transfer control to machine-independant
part of kernel, which is located in $/init/main.c;

    ...
    br.call.sptk.few rp=start_kernel
    ...

  and it should never return; if there is something wrong, kernel will print
out halt_msg(\"halting kernel\"), and result in endless loop;

    ...
    .ret2: addl r2=@ltoff(halt_msg),gp //Note 1
        ;;
        ld8 out0=[r2]
        br.call.sptk.few b0=console_print
    self:  br.sptk.few self        // endless loop
    ...

  Note 1:
    I want to point out something used frequently in IA64 assembly code; you may
  find more informations in IA64 manual; Here i just present your some ideas:
    gp --> global data pointer, actually, it\'s another name of R1; DON\'t ASK WHY, it\'s
     just convention compiler should obey;

   @ltoff(expr) --> 
     The current instruction that instructs the linker to create a linkage
    table entry for expr, and calculates the gp-relative offset to the new linkage
    table entry.add long immediate instructions.

    so here, this instruction is to load halt_msg address into r2;

XXX if you don\'t understand what it means here, it means you need to read some artciles
XXX about linker ;-)

 And here we won\'t get involved into start_kernel(), cause there are a lot
of documents have describe it in detail;
 
*Summary
 Ok, now kernel is really started; And we get to know one interface between
 machine-dependant and machine-independant: start_kernel;
 
 Before start_kernel(), the machine-dependant part does:
 * Load Kernel image into indicated location (bootloader)
 * Setup address translation (mm)
 * Setup interrupts and faults handling (bottom half of kernel service)
 * \"Create\" IDLE process avaible (process manangement, without it init process
  could not be created )
 
 Any way, I never get chance of porting linux to new target, just something in mind,
it\'s in-complete, pseudo,and buggy ;-)

*What\'s next?
 I will dive into IA64 address model and interrupt handling with codes step by
 step; I hope i could draw sketch between machine-dependant(arch) part and
 machine-independant part via analyzing;


*Questions
 -- As i know, one single 64M page is mapped for kernel image, memory access
  in this range is ok; but how about other areas? in fact, i don\'t find the
  related code that maps the kernel space to physical memory(like IA32).
  so some kind guys would tell me?

 -- why kernel image is loaded at 68M, not 64M? this make a 4M hole!

--
※ 来源: 中国科大BBS站 [bbs.ustc.edu.cn]

[color=red]Optimization[/color] In Progress . . . Welcome to http://mail.ustc.edu.cn/~chyang/
游客

返回顶部