顯示具有 Linux Development 標籤的文章。 顯示所有文章
顯示具有 Linux Development 標籤的文章。 顯示所有文章

星期三, 5月 16, 2012

ARM Linux booting

arm linux Kernel 啟動流程:

(* arch/arm/boot/bootp/init.S - 有人說會先執行這支程式, 目前不清楚這支程式在什麼情形下會執行, 在我的平台上沒有.)



要瞭解一支程式架構,先從目錄結構與 Makefile 下手是最快的方法,kernel 也不例外,與 arm 相關的開機程式碼,會放在 arch/arm/boot 裡面。

linux kernel image 大致上來說可分為無壓縮跟有壓縮兩種,通常無壓縮格式是沒在使用的。

Linux kernel image 常見格式表
FormatDescription
zImagezipped image
xipImageexecute in place image
uImageuboot wrapped image

在 ARM 平台裡,zImage 為最常見的格式,從 boot 目錄裡的 Makefile,可以知道與 zImage 相關的程式是放在 arch/arm/boot/compressed 裡面,從 compressed 目錄裡的 Makefile 可以知道 zImage 主要是由 head.o + misc.o + piggy.o 還有一些與平台相關的程式所組成,其中比較重要的檔案為:

FileDescription
head.okernel 進入點
misc.o解壓縮副函式
piggy.o壓縮的 kernel image
*.o其他與平台相關的程式碼



參考 Documentation/arm/booting.txt 文件,
可以知道 boot loader 在呼叫 kernel 之前,必需有以下條件:

- 關畢所有 DMA 相關的設備,以免記憶體被無意義的網路封包或資碟資料干擾。這會省下你許多小時的除錯時間。

- CPU 暫存器設定
r0 = 0
r1 = machine type ID
r2 = 指向 tagged list 在 RAM 的實體位址

- CPU 模式
所有中斷都必需關畢 (IRQs and FIQs)
CPU 必須在 SVC mode (Angel 是一個特別的例外)

- Caches, MMUs
MMU 必須關畢
Instruction cache (I-cache) 可以開啟或關畢
Data cache (D-cache) 必須關畢

- boot loader 被預期呼叫 kernel image 的方法是,直接跳到 kernel image 的第一個指令。



以下是各檔案的執行順序與大致上會做的事:
  • arch/arm/boot/compressed/head.S
    1. 從 boot loader 跳過來後,第一個執行的位置會是在 start: 這個 label。
    2. 為了保留不使用 boot loader 能開機的能力,會先 nop 八次來保留 ARM 的中斷向量表。
    3. 保存 r1、r2 的數值到 r7 與 r8。
    4. 關畢 FIQ 與 IRQ。
    5. link 與平台相關的程式來執行,如 "head-xscale.S"。
    6. 記算計憶體裡的偏移量並修正各節區。
    7. 配置 C 的 runtime environment。
    8. 設置與開啟 mmu。
    9. 檢查 compressed image size.
    10. 設置參數到 r0 到 r3, 呼叫 arch/arm/boot/compressed/misc.c 裡的 decompress_kernel.
  • arch/arm/boot/compressed/misc.c
    1. 此檔案主要提供 decompress_kernel 函式模式供 arch/arm/boot/compressed/head.S 呼叫
    2. 宣告印字元與印字串等函式, 在 decompress_kernel 中透過這幾個函式印出 decompress kernel 等字串.
    3. 呼叫 lib/inflate.c 裡的 gunzip 函式來解壓縮 kernel image
    4. 返回 arch/arm/boot/compressed/head.S
  • arch/arm/kernel/head.S
      ---> 判斷 processor & machine ID
  • arch/arm/kernel/head-common.S
      ---> 判斷 machine ID 錯誤顯示函式 (low level debug 開啟時)
  • init/main.c
      ---> start_kernel


詳細的追蹤內容:
arch/arm/boot/compressed/head.S

  .section ".start", #alloc, #execinstr
/*
 * sort out different calling conventions
 */
  .align
start:
  .type start,#function
  .rept 8
  mov r0, r0
  .endr

  b 1f
  .word 0x016f2818  @ Magic numbers to help the loader
  .word start   @ absolute load/run zImage address
  .word _edata   @ zImage end address
1:  mov r7, r1   @ save architecture ID
  mov r8, r2   @ save atags pointer


註解說為了不同的呼叫慣例, 所以要 .align 以供對齊,
參考: http://wiki.debian.org/ArmEabiPort#Struct_packing_and_alignment

一開始程式會執行 mov r0, r0 (即 nop)八次 (.rept = repeat, .endr = end of repeat),
這部份有兩種說法,一個是因為 ARM11 有 8 stages pipeline,另一種說法是保留中斷向量表(interrupt vector table)。
我個人是認為要保留 Linux kernel 能直接開機的能力而保留中斷向量表的空間,因為要 flush pipeline 不需要用到八個 nop。

接下來指令是 b 1f (f = forward) 跳到後面的 lable "1:"

在 lable "1:" 之前, 有三個 .word 資訊如下:

zImage 檔頭資訊
zImage 偏移位置內容說明
0x240x016F2818用來識別 ARM Linux zImage 的 Magic number
0x28startzImage 開始位置(通常為0)
0x2C_edatazImage 結束位置(通常為檔案大小)

參考 http://www.simtec.co.uk/products/SWLINUX/files/booting_article.html

從 label "1:" 開始會將 r1 (architecture ID) 跟 r2 (atags pointer) 的值保存在 r7 跟 r8.

#ifndef __ARM_ARCH_2__
  /*
   * Booting from Angel - need to enter SVC mode and disable
   * FIQs/IRQs (numeric definitions from angel arm.h source).
   * We only do this if we were in user mode on entry.
   */
  mrs r2, cpsr  @ get current mode
  tst r2, #3   @ not user?
  bne not_angel
  mov r0, #0x17  @ angel_SWIreason_EnterSVC
  swi 0x123456  @ angel_SWI_ARM
not_angel:
  mrs r2, cpsr  @ turn off interrupts to
  orr r2, r2, #0xc0  @ prevent angel from running
  msr cpsr_c, r2
#else
  teqp pc, #0x0c000003  @ turn off interrupts
#endif

這段程式主要是用來關畢所有中斷.

關於 Angel 可以參考這個網址:
http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0066d/Babdcdih.html

參考 arm architecture reference manual:

32-bit 的 cpsr 暫存器:

NameDescBits
cpsr_fflags field[31:24]
cpsr_sstatus field[23:16]
cpsr_xextension field[15:8]
cpsr_ccontrol field[7:0]



cpsr bit field
Bit313029282726-----876543210
 NZCVQDNM(RAZ)IFTM4M3M2M1M0



32-bit 模式設定
M[4:0]Mode
0b10000User
0b10001FIQ
0b10010IRQ
0b10011Supervisor (SVC)
0b10111Abort
0b11011Undefined
0b11111System



26-bit 的 pc 暫存器:

Bit 31 30 29 28 27 26 25------------2 1 0
N Z C V I F program counter M1 M0

26-bit 模式設定:
M[1:0] Mode
0b00 User
0b01 FIQ
0b10 IRQ
0b11 Supervisor

N Negative
Z Zero
C Carry
O Overflow
I IRQ
F FIQ
T Thumb

  /*
   * Note that some cache flushing and other stuff may
   * be needed here - is there an Angel SWI call for this?
   */

  /*
   * some architecture specific code can be inserted
   * by the linker here, but it should preserve r7, r8, and r9.
   */

  .text
  adr r0, LC0
  ldmia r0, {r1, r2, r3, r4, r5, r6, ip, sp}
  subs r0, r0, r1  @ calculate the delta offset

      @ if delta is zero, we are
  beq not_relocated  @ running at the address we
      @ were linked at.

  /*
   * We're running at a different address.  We need to fix
   * up various pointers:
   *   r5 - zImage base address
   *   r6 - GOT start
   *   ip - GOT end
   */
  add r5, r5, r0
  add r6, r6, r0
  add ip, ip, r0

註解提到一些與平台相關的程式會被 link 加在這裡(如 head-xscale.S), 但必需保留 r7, r8 與 r9.
這段程式碼主要是在計算 offset delta 並重新定址 zImage 的 base 跟 GOT(global offset table) address.

r0: LC0 runtime address
r1: LC0 linked address
r0 - r1: delta offset

adr 是一個假指令, 會自動幫你算出偏移量並將自動換成 add, sub, mov 或 mvn 等指令。
這邊是算出 LC0 offset 然後 load 到各暫存器裡。

LC0 如下:

  .type LC0, #object
LC0:  .word LC0   @ r1
  .word __bss_start  @ r2
  .word _end   @ r3
  .word zreladdr  @ r4
  .word _start   @ r5
  .word _got_start  @ r6
  .word _got_end  @ ip
  .word user_stack+4096  @ sp
LC1:  .word reloc_end - reloc_start
  .size LC0, . - LC0

r0: LC0 runtime address
r1: LC0 linked address
r2: BSS start
r3: BSS end
r4: memory physical address
r5: kernel start address
r6: GOT start
ip: GOT end
sp: stack pointer

stack pointer 要加 4096,這是因為 stack 是高位往低位倒著長的,所以先指到 stack 最尾端。
在檔案最後一行可以看到:
user_stack: .space 4096
其他暫存器後面會用到,如 r4 在設置 mmu 時會用到。

#ifndef CONFIG_ZBOOT_ROM
  /*
   * If we're running fully PIC === CONFIG_ZBOOT_ROM = n,
   * we need to fix up pointers into the BSS region.
   *   r2 - BSS start
   *   r3 - BSS end
   *   sp - stack pointer
   */
  add r2, r2, r0
  add r3, r3, r0
  add sp, sp, r0

  /*
   * Relocate all entries in the GOT table.
   */
1:  ldr r1, [r6, #0]  @ relocate entries in the GOT
  add r1, r1, r0  @ table.  This fixes up the
  str r1, [r6], #4  @ C references.
  cmp r6, ip
  blo 1b
#else

  /*
   * Relocate entries in the GOT table.  We only relocate
   * the entries that are outside the (relocated) BSS region.
   */
1:  ldr r1, [r6, #0]  @ relocate entries in the GOT
  cmp r1, r2   @ entry < bss_start ||
  cmphs r3, r1   @ _end < entry
  addlo r1, r1, r0  @ table.  This fixes up the
  str r1, [r6], #4  @ C references.
  cmp r6, ip
  blo 1b
不知道 ZBOOT 是什麼,不過可以知道這段是在重新定址(relocate) BSS 與 GOT。 GOT (Global Offset Table) 是用來存放 object 的指針,因為 linker 不會知道程式在執行時,object 會放在記憶體哪裡,所以必須透過 GOT 來取得 object 的位址。 GOT 參考: http://bottomupcs.sourceforge.net/csbu/x3824.htm 最後的 blo 就是 bcc,這段程式碼等於 if (r6 < ip) goto 1: (b = before 往上跳)。
not_relocated: mov r0, #0
1:  str r0, [r2], #4  @ clear bss
  str r0, [r2], #4
  str r0, [r2], #4
  str r0, [r2], #4
  cmp r2, r3
  blo 1b

這一段就很簡單了,把 BSS 節區全部設為 0,因為 C 語言規範裡,BSS (uninitialized data) 應該為 0。

  /*
   * The C runtime environment should now be setup
   * sufficiently.  Turn the cache on, set up some
   * pointers, and start decompressing.
   */
  bl cache_on

C 的 runtime environment 設置到這應該足夠了。把 cache 打開,設置一些指標,並開始解壓縮。
這裡只有一行 bl cache_on,bl 會把現在 pc 存到 lr 裡以供返回使用。

/*
 * Turn on the cache.  We need to setup some page tables so that we
 * can have both the I and D caches on.
 *
 * We place the page tables 16k down from the kernel execution address,
 * and we hope that nothing else is using it.  If we're using it, we
 * will go pop!
 *
 * On entry,
 *  r4 = kernel execution address
 *  r6 = processor ID
 *  r7 = architecture number
 *  r8 = atags pointer
 *  r9 = run-time address of "start"  (???)
 * On exit,
 *  r1, r2, r3, r9, r10, r12 corrupted
 * This routine must preserve:
 *  r4, r5, r6, r7, r8
 */
  .align 5
cache_on: mov r3, #8   @ cache_on function
  b call_cache_fn

為了後面的 table,以 .align 5 來對齊,r3 = #8 指到 cache_on 函式。

/*
 * Here follow the relocatable cache support functions for the
 * various processors.  This is a generic hook for locating an
 * entry and jumping to an instruction at the specified offset
 * from the start of the block.  Please note this is all position
 * independent code.
 *
 *  r1  = corrupted
 *  r2  = corrupted
 *  r3  = block offset
 *  r6  = corrupted
 *  r12 = corrupted
 */

call_cache_fn: adr r12, proc_types
#ifdef CONFIG_CPU_CP15
  mrc p15, 0, r6, c0, c0 @ get processor ID
#else
  ldr r6, =CONFIG_PROCESSOR_ID
#endif
1:  ldr r1, [r12, #0]  @ get value
  ldr r2, [r12, #4]  @ get mask
  eor r1, r1, r6  @ (real ^ match)
  tst r1, r2   @       & mask
  addeq pc, r12, r3  @ call cache function
  add r12, r12, #4*5
  b 1b

(1) 取得 CPU ID。
(2) 從 proc_types 開始查表比對 CPU 以提供相應的程式。
(3) 透過直接改 pc 暫存器的方式來執行相應的程式。

r12: 目前 proc_types 表格位址
r1: CPU ID
r2: CPU ID 的遮罩
r3: #8 (r12 + #8 則是 cache on)

/*
 * Table for cache operations.  This is basically:
 *   - CPU ID match
 *   - CPU ID mask
 *   - 'cache on' method instruction
 *   - 'cache off' method instruction
 *   - 'cache flush' method instruction
 *
 * We match an entry using: ((real_id ^ match) & mask) == 0
 *
 * Writethrough caches generally only need 'on' and 'off'
 * methods.  Writeback caches _must_ have the flush method
 * defined.
 */
  .type proc_types,#object
proc_types:

其它表格省略,只列出會跳躍到的,PXA3xx CPU ID 為 0x690568xx 屬於 ARMv5TE 系列。
Note: 參考 Datasheet Valume I 的 2.16.6.2 裡的 Table 9。

  .word 0x00050000  @ ARMv5TE
  .word 0x000f0000
  b __armv4_mmu_cache_on
  b __armv4_mmu_cache_off
  b __armv4_mmu_cache_flush

所以接下來會執行 __armv4_mmu_cache_on

__armv4_mmu_cache_on:
  mov r12, lr
  bl __setup_mmu
  mov r0, #0
  mcr p15, 0, r0, c7, c10, 4 @ drain write buffer
  mcr p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
  mrc p15, 0, r0, c1, c0, 0 @ read control reg
  orr r0, r0, #0x5000  @ I-cache enable, RR cache replacement
  orr r0, r0, #0x0030
  bl __common_mmu_cache_on
  mov r0, #0
  mcr p15, 0, r0, c8, c7, 0 @ flush I,D TLBs
  mov pc, r12

把 lr 保存在 r12 後用 bl 跳 __setup_mmu,所以先看 __setup_mmu:

__setup_mmu: sub r3, r4, #16384  @ Page directory size
  bic r3, r3, #0xff  @ Align the pointer
  bic r3, r3, #0x3f00
/*
 * Initialise the page tables, turning on the cacheable and bufferable
 * bits for the RAM area only.
 */
  mov r0, r3
  mov r9, r0, lsr #18
  mov r9, r9, lsl #18  @ start of RAM
  add r10, r9, #0x10000000 @ a reasonable RAM size
  mov r1, #0x12
  orr r1, r1, #3 << 10
  add r2, r3, #16384
1:  cmp r1, r9   @ if virt > start of RAM
  orrhs r1, r1, #0x0c  @ set cacheable, bufferable
  cmp r1, r10   @ if virt > end of RAM
  bichs r1, r1, #0x0c  @ clear cacheable, bufferable
  str r1, [r0], #4  @ 1:1 mapping
  add r1, r1, #1048576
  teq r0, r2
  bne 1b

這個迴圈會為整個 4G memory 配置 translation table 每個 word (4-bytes) 代表 1M memory section,
所以 translation table size 算法:

total size (4G) / section size (1M) = 4K * word(4) = 16K (Bytes)

ARM MMU 要求暫存器裡的數值要對齊 16K 邊界 (boundary align),所以才需要把 0x3fff 設成 0,
但是目前還不清楚為什麼要先清 0xff 再清 0x3f00。

記得前面的 LC0 吧,r4 一直都沒被改過,所以 r4: zreladdr,註解說是 kernel execute address,其實就是 physical memory address,
那這個 zreladdr 是在哪邊決定呢?答案是在 Makefile 裡,透過 arch/arm/boot 與 arch/arm/boot/compressed 裡的 Makefile,
最後可以在 arch/arm/mach-xxx/Makefile.boot 裡找到真正的設定。

用到的暫存器意義:

r0: translation table current (variable)
r1: table descriptor,0x12 表示 type 為 section。
r2: translation table end
r3: translation table start (constant)
r4: zreladdr (equal to r2)
r9: physical memory base address
r10: resonable memory end address (size: 256M)


以我的實例來說,physical memory 是從 0x80000000 開始,用一張圖來表示 first-level section 的記憶體配置會是這樣:

    (R9) 0x80000000 +------------------+ \  Start of RAM
                    |Reserved          |  \
    (R3) 0x80004000 +------------------+\  \
                    |Translation Table |  +0x4000 (16K)
    (R2) 0x80008000 +------------------+/    \
                    |                  |      \
                    |                  |    +0x10000000 (256M resonable memory)
                   ----------------------     /
                        Cut Short            /
                   ----------------------   /
                    |                  |   /
                    |                  |  /
   (R10) 0x90000000 +------------------+ /

其他部份請參考 ARM reference manual 的 MMU 一節有詳細說明。

/*
 * If ever we are running from Flash, then we surely want the cache
 * to be enabled also for our execution instance...  We map 4MB of it
 * so there is no map overlap problem for up to 1 MB compressed kernel.
 * If the execution is in RAM then we would only be duplicating the above.
 */
  mov r1, #0x1e
  orr r1, r1, #3 << 10
  mov r2, pc, lsr #20
  orr r1, r1, r2, lsl #20
  add r0, r3, r2, lsl #2
  str r1, [r0], #4
  add r1, r1, #1048576
  str r1, [r0], #4
  add r1, r1, #1048576
  str r1, [r0], #4
  add r1, r1, #1048576
  str r1, [r0]
  mov pc, lr
ENDPROC(__setup_mmu)
這一段程式利用 PC 來取得現在執行位置,然後再將現在執行位置開始的 4M 空間設置 cached/buffered, 這是為了 kernel 在 flash 上執行時,也可以 cache,當然現在大部份 kernel 都在 RAM 裡面跑,這樣也只是重覆上面迴圈重設四個 descriptor 而已。
__common_mmu_cache_on:
#ifndef DEBUG
  orr r0, r0, #0x000d  @ Write buffer, mmu
#endif
  mov r1, #-1
  mcr p15, 0, r3, c2, c0, 0 @ load page table pointer
  mcr p15, 0, r1, c3, c0, 0 @ load domain access control
  b 1f
  .align 5   @ cache line aligned
1:  mcr p15, 0, r0, c1, c0, 0 @ load control register
  mrc p15, 0, r0, c1, c0, 0 @ and read it back to
  sub pc, lr, r0, lsr #32 @ properly flush pipeline
這一段就只是透過 cooperator 來設置 MMU 而已,p15 細節一樣請參考 ARM reference manual MMU 機制。 *會先返回 __armv4_mmu_cache_on:
  mov r1, sp   @ malloc space above stack
  add r2, sp, #0x10000 @ 64k max

/*
 * Check to see if we will overwrite ourselves.
 *   r4 = final kernel address
 *   r5 = start of this image
 *   r2 = end of malloc space (and therefore this image)
 * We basically want:
 *   r4 >= r2 -> OK
 *   r4 + image length <= r5 -> OK
 */
  cmp r4, r2
  bhs wont_overwrite
  sub r3, sp, r5  @ > compressed kernel size
  add r0, r4, r3, lsl #2 @ allow for 4x expansion
  cmp r0, r5
  bls wont_overwrite

  mov r5, r2   @ decompress after malloc space
  mov r0, r5
  mov r3, r7
  bl decompress_kernel

  add r0, r0, #127 + 128 @ alignment + stack
  bic r0, r0, #127  @ align the kernel length
/*
 * r0     = decompressed kernel length
 * r1-r3  = unused
 * r4     = kernel execution address
 * r5     = decompressed kernel start
 * r6     = processor ID
 * r7     = architecture ID
 * r8     = atags pointer
 * r9-r14 = corrupted
 */
  add r1, r5, r0  @ end of decompressed kernel
  adr r2, reloc_start
  ldr r3, LC1
  add r3, r2, r3
1:  ldmia r2!, {r9 - r14}  @ copy relocation code
  stmia r1!, {r9 - r14}
  ldmia r2!, {r9 - r14}
  stmia r1!, {r9 - r14}
  cmp r2, r3
  blo 1b
  add sp, r1, #128  @ relocate the stack

  bl cache_clean_flush
  add pc, r5, r0  @ call relocation code

/*
 * We're not in danger of overwriting ourselves.  Do this the simple way.
 *
 * r4     = kernel execution address
 * r7     = architecture ID
 */
wont_overwrite: mov r0, r4
  mov r3, r7
  bl decompress_kernel
  b call_kernel
cache 開完後,返回 226 行,接下來就是執行 decompress_kernel 跟 call_kernel 了。
call_kernel: bl cache_clean_flush
  bl cache_off
  mov r0, #0   @ must be zero
  mov r1, r7   @ restore architecture number
  mov r2, r8   @ restore atags pointer
  mov pc, r4   @ call kernel

星期一, 7月 04, 2011

GNU C 的 Variadic Macro 筆記

參考: GNU C 4.5.x Manual, Chapter 6.19, Macros with a Variable Number of Arguments.

以下為 ISO C99 Standard Macro 裡的可變量參數(variable argument)範例:

#define debug(format, ...) fprintf (stderr, format, __VA_ARGS__)

前面的 '...' 是可變量參數, 後面的 __VA_ARGS__ 會替換掉 '...' 裡面的東西, 包含前一個逗點.

gcc 支援了 variadic macro, 可以使用不同的語法來表示可變量參數, 你可以幫可變量參數取名字如:

#define debug(format, args...) fprintf (stderr, format, args)

這方法跟 ISO C99 的範例是相同的, 但更好閱讀.

ISO C99 的 MACRO 在碰到 debug ("A message") 省略參數的用法時, 也會連逗點一起帶進來而產生錯誤.
要解決這個問題, 可以用 CPP 的方式在 __VA_ARGS__ 前面加上 '##':

#define debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__)

星期三, 6月 29, 2011

Linux kernel 的 error number 與相關 macro 筆記 (IS_ERR, PTR_ERR, ERR_PTR)


說明:

* linux kernel 裡系統函式的回傳值大都是指標, 其指標返回值會有兩種類型, 1.有效指標, 2.無效指標

* 使用 IS_ERR 來判斷是否有錯誤(無效指標), 有錯誤發生再用 PTR_ERR 把回傳指標轉為錯誤碼.

* linux kernel 利用指標能定址到的最後 4k 記憶體區塊當錯誤判斷, 因為最小的記憶體分頁為 4k.
所以最後一頁邊界等於 ptr_max_addr &= ~0xfff;

* 錯誤碼最大值將不能大於 4k(0xfff).

知道這些前提後, 看一下 include/linux/err.h 裡的宣告。(kernel version: 2.6.29/Android 2.1)


/*
 * Kernel pointers have redundant information, so we can use a
 * scheme where we can return either an error code or a dentry
 * pointer with the same return value.
 *
 * This should be a per-architecture thing, to allow different
 * error and pointer decisions.
 */
#define MAX_ERRNO 4095

#ifndef __ASSEMBLY__

#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO)

static inline void *ERR_PTR(long error)
{
 return (void *) error;
}

static inline long PTR_ERR(const void *ptr)
{
 return (long) ptr;
}

static inline long IS_ERR(const void *ptr)
{
 return IS_ERR_VALUE((unsigned long)ptr);
}

/**
 * ERR_CAST - Explicitly cast an error-valued pointer to another pointer type
 * @ptr: The pointer to cast.
 *
 * Explicitly cast an error-valued pointer to another pointer type in such a
 * way as to make it clear that's what's going on.
 */
static inline void *ERR_CAST(const void *ptr)
{
 /* cast away the const */
 return (void *) ptr;
}

可以看到 maximum error number = 4095 (== 0x0fff == 4K == size of minimum page block).
IS_ERR 先將指標先轉型為 unsigned long 然後呼叫 IS_ERR_VALUE, 將 IS_ERR_VALUE 內容展開為:

unlikely(
(x) >= -4995
)

因為 -4995 轉換為 32 bit hex 為 0xfffff001, 所以是大於或等於 0xfffff001 即被視為錯誤碼.

ERR_PTR (error to pointer) 與 PTR_ERR (pointer to error) 這兩個都只有做轉型, PTR_ERR 比較常用, 一般用 IS_ERR 確定是錯誤值而不是指標後, 就可以用 PTR_ERR 來取得轉型後的錯誤數值, 範例如下:

dev = MKDEV(MISC_MAJOR, misc->minor);

misc->class = class_device_create(misc_class, NULL, dev, misc->dev, "%s", misc->name);
if (IS_ERR(misc->class)) {
        err = PTR_ERR(misc->class);
        goto out;
}

至於錯誤編號的定義, 可以很容易猜出來在 asm/errno.h 裡面. 這部份雖然是依平台不同而定, 但是目前版本裡 (2.6.29) x86 與 arm 都是引入 include/asm-generic/errno.h. 然後再由這個檔案引入 include/asm-generic/errno-base.h, 其中 errno-base.h 包含了一些最基本的定義, 如常見的 -EIO, -EBUSY 等等共有 34 個如下:

#define EPERM   1 /* Operation not permitted */
#define ENOENT   2 /* No such file or directory */
#define ESRCH   3 /* No such process */
#define EINTR   4 /* Interrupted system call */
#define EIO   5 /* I/O error */
#define ENXIO   6 /* No such device or address */
#define E2BIG   7 /* Argument list too long */
#define ENOEXEC   8 /* Exec format error */
#define EBADF   9 /* Bad file number */
#define ECHILD  10 /* No child processes */
#define EAGAIN  11 /* Try again */
#define ENOMEM  12 /* Out of memory */
#define EACCES  13 /* Permission denied */
#define EFAULT  14 /* Bad address */
#define ENOTBLK  15 /* Block device required */
#define EBUSY  16 /* Device or resource busy */
#define EEXIST  17 /* File exists */
#define EXDEV  18 /* Cross-device link */
#define ENODEV  19 /* No such device */
#define ENOTDIR  20 /* Not a directory */
#define EISDIR  21 /* Is a directory */
#define EINVAL  22 /* Invalid argument */
#define ENFILE  23 /* File table overflow */
#define EMFILE  24 /* Too many open files */
#define ENOTTY  25 /* Not a typewriter */
#define ETXTBSY  26 /* Text file busy */
#define EFBIG  27 /* File too large */
#define ENOSPC  28 /* No space left on device */
#define ESPIPE  29 /* Illegal seek */
#define EROFS  30 /* Read-only file system */
#define EMLINK  31 /* Too many links */
#define EPIPE  32 /* Broken pipe */
#define EDOM  33 /* Math argument out of domain of func */
#define ERANGE  34 /* Math result not representable */

星期六, 5月 21, 2011

Embedded System - Linux 如何新增 Nand Flash Notes

對於新加入的 flash, 參數修改可以在其相關 architecture 的 code 裡找到, 以 pxa3xx 系列為例子, 可以在 drivers/mtd/nand/pxa3xx_nand.c 裡面找到.

/* kernel 2.6.21 */

kernel 2.6.21 的 pxa3xx_nand.c 程式碼跟 WinCE 在結構上來說是大同小異, 值得關注的就是 dfc_flash_info 這個結構, 大部份的 flash 資訊都在這裡:

struct dfc_flash_info {
    struct dfc_flash_timing timing; /* NAND Flash timing */

    int  enable_arbiter;/* Data flash bus arbiter enable (ND_ARB_EN) */
    uint32_t page_per_block;/* Pages per block (PG_PER_BLK) */
    uint32_t row_addr_start;/* Row address start position (RA_START) */
    uint32_t read_id_bytes; /* returned ID bytes(RD_ID_CNT) */
    uint32_t dfc_mode; /* NAND, CARBONDALE, PIXLEY... (ND_MODE) */
    uint32_t ncsx;  /* Chip select don't care bit (NCSX) */
    uint32_t page_size; /* Page size in bytes (PAGE_SZ) */
    uint32_t oob_size; /* OOB size */
    uint32_t flash_width; /* Width of Flash memory (DWIDTH_M) */
    uint32_t dfc_width; /* Width of flash controller(DWIDTH_C) */
    uint32_t num_blocks; /* Number of physical blocks in Flash */
    uint32_t chip_id;
    uint32_t read_prog_cycles; /* Read Program address cycles */

    /* command codes */
    uint32_t read1;  /* Read */
    uint32_t read2;  /* unused, DFC don't support yet */
    uint32_t program; /* two cycle command */
    uint32_t read_status;
    uint32_t read_id;
    uint32_t erase;  /* two cycle command */
    uint32_t reset;
    uint32_t lock;  /* lock whole flash */
    uint32_t unlock; /* two cycle command, supporting partial unlock */
    uint32_t lock_status; /* read block lock status */

    /* addr2ndcb1 - encode address cycles into register NDCB1 */
    /* ndbbr2addr - convert register NDBBR to bad block address */
    int (*addr2ndcb1)(uint16_t cmd, uint32_t addr, uint32_t *p);
    int (*ndbbr2addr)(uint16_t cmd, uint32_t ndbbr,uint32_t *p);
};

大部份的東西對應 datasheet 都可以知道, 比較特別的名詞是 OOB (Out of Band), 這東西其實就是 datasheet 上的 spare area.
除此之外, 還有兩個 pointer to function, 這兩個 function 主要是用來做讀寫時位址換算, 因為 nand flash write 時是 by page, 而 erase 時是 by block, 所以在讀寫位置要特別處理.

/* kernel 2.6.36 */

kernel 2.6.36 值得注意的是 pxa3xx_nand_flash 這個結構如下:

struct pxa3xx_nand_flash {
    const struct pxa3xx_nand_timing *timing; /* NAND Flash timing */
    const struct pxa3xx_nand_cmdset *cmdset;

    uint32_t page_per_block;/* Pages per block (PG_PER_BLK) */
    uint32_t page_size; /* Page size in bytes (PAGE_SZ) */
    uint32_t flash_width; /* Width of Flash memory (DWIDTH_M) */
    uint32_t dfc_width; /* Width of flash controller(DWIDTH_C) */
    uint32_t num_blocks; /* Number of physical blocks in Flash */
    uint32_t chip_id;
};

可以看到裡面包含了 chip_id, page_size 等相關資訊, 以及 timing 跟 command set 這兩個結構:

struct pxa3xx_nand_timing {
    unsigned int tCH;  /* Enable signal hold time */
    unsigned int tCS;  /* Enable signal setup time */
    unsigned int tWH;  /* ND_nWE high duration */
    unsigned int tWP;  /* ND_nWE pulse time */
    unsigned int tRH;  /* ND_nRE high duration */
    unsigned int tRP;  /* ND_nRE pulse width */
    unsigned int tR;   /* ND_nWE high to ND_nRE low for read */
    unsigned int tWHR; /* ND_nWE high to ND_nRE low for status read */
    unsigned int tAR;  /* ND_ALE low to ND_nRE low delay */
};

timing 的部份, 請參考 datasheet 裡面有詳細的解說, 簡單來說就是在定義讀取跟寫入資料時, 哪幾條線要 pull high 幾個 clock 之類, 而這些 timing 每片 flash 都不盡相同.

struct pxa3xx_nand_cmdset {
    uint16_t read1;
    uint16_t read2;
    uint16_t program;
    uint16_t read_status;
    uint16_t read_id;
    uint16_t erase;
    uint16_t reset;
    uint16_t lock;
    uint16_t unlock;
    uint16_t lock_status;
};

再來就是 command set, 一般來說 nor flash 接法與 DRAM 一樣, 讀寫方式也類似, 最多要注意的就是 data bus 幾條跟 block 是由大到小或由小到大而已, 而 nand flash 都是用 command 來做資料傳輸的方式, 目前看得到的 nand flash, page size 基本上只有兩種: 512 跟 2048, 以後會不會有新的還不知道, 而這兩者 command set 大部份幾乎都一樣, 主要差別在 small page 跟 large page 的 read 會有點不同, 只要依照 datasheet 把正確的數值加上去就可以了.

要參考的函式:

dfc_init->dfc_get_flash_info

要修改的 struct:
enum {
 DFC_FLASH_NULL = 0 ,
 DFC_FLASH_Samsung_512Mb_X_16 = 1,
 DFC_FLASH_Micron_1Gb_X_8 = 2,
 DFC_FLASH_Micron_1Gb_X_16 = 3,
 DFC_FLASH_STM_1Gb_X_16 = 4,
 DFC_FLASH_STM_2Gb_X_16 = 5,
 DFC_FLASH_STM_MCP_1Gb_X_16 = 6,
 DFC_FLASH_Toshiba2GbX16 = 7,
 DFC_FLASH_END,
};

static struct {
 int type;
 struct dfc_flash_info *flash_info;
} type_info[] = {
 { DFC_FLASH_Samsung_512Mb_X_16, &samsung512MbX16},
 { DFC_FLASH_Micron_1Gb_X_8, &micron1GbX8},
 { DFC_FLASH_Micron_1Gb_X_16, &micron1GbX16},
 { DFC_FLASH_STM_1Gb_X_16, &stm1GbX16},
 { DFC_FLASH_STM_2Gb_X_16, &stm2GbX16},
 { DFC_FLASH_STM_MCP_1Gb_X_16, &stm70nm1GbX16},
 { DFC_FLASH_Toshiba2GbX16, &toshiba2GbX16},
 { DFC_FLASH_NULL, NULL},
};

星期五, 5月 20, 2011

Linux MTD Nand Flash Part Trace Notes

Linux MTD Nand Flash Part Trace Notes

Nand Flash 型號判斷在檔案:

drivers/mtd/nand/nand_base.c

主要函式:

nand_get_flash_type

其中:

maf_id // 1st ID
dev_id // 2nd ID
chip->cellinfo // 3rd ID
extid // 4th ID

程式可利用這幾個 ID 來判斷 write size, erase size, oob size (spare) 跟 bus width 等等, 詳細方式請參考程式碼與 datasheet.

Note:

較舊版本 2.6.21 沒一次讀取, 在某些 flash 如 K9F2G08U0A, K9K8G08U0B 上面讀取 3rd, 4th 等 ID 會有問題,
因此如果使用較舊版本要自己強制把 id 值填入才會正確, 如:

#if 1
if (*maf_id == 0xec && dev_id == 0xda) { // K9F2G08U0A
    chip->cellinfo = 0x10;
    extid = 0x95;
}
#endif

較新版本如 2.6.36 則是改成以一個迴圈一次讀完 8 個 ID, 比較正常, 因此在新版本上也可以印出 id_data 這個 array 來 debug 如:

printk(KERN_INFO "nand flash ID - 1:0x%02x 2:0x%02x 3:0x%02x 4:0x%02x 5:0x%02x 6:0x%02x 7:0x%02x 8:0x%02x\n",
    id_data[0], id_data[1], id_data[2], id_data[3], id_data[4], id_data[5], id_data[6], id_data[7]);

其中:
id_data[0] // 1st ID
id_data[1] // 2nd ID
...
id_data[7] // 8th ID

星期三, 5月 18, 2011

Embedded System - LCD Device Driver Notes

LCD 驅動程式概念與實作

基本概念:

目前市面上見得到的 LCD, 在資料傳送上有兩種, 一種是 Sync(Synchronous) mode, 一種是 DE(Data Enable) mode, 有的 Panel 可用程式控制, 可以選擇使用其中一種.
在接腳上, DE Mode 通常只要 DE, Clock 與 RGB, 而 Sync Mode 則需要 VSync, HSync, Clock 與 RGB, 有些可程式控制的 Panel 則還會需要 SPI 來做控制, 僅管如此, 各面板廠做法都不同, 當然有例外, 像是有的面板 DE Mode 可能還要接 VSync, 此外 RGB 則可能有 16(565)bit, 18(666)bit 等等, 所有接法請依照該面板的 datasheet 為準.

DE Mode 比較簡單, 通常是 DE 腳 assert 時, 表示接受資料傳送, 這時 LCD Control 會從 RGB 資料線傳送資料給 LCD Panel, 現在大部份 Panel 大都採用此種模式.

Sync Mode 大致上來說, 則是在 VSync assert 然後 back porch n 個 clock 後, 接著 HSync assert 然後 back porch n 個 clock 後, 開始傳送這條水平線的資料, 到下一條水平線時再 front porch + HSync assert + back porch 一次, 一直到整張傳完, 準備傳下一張時, 再次 front porch + assert VSync + back porch, 當然這邊只是說個大概的工作原理, 一切基準都要看該面板 datasheet 為準.

所以由此可知, 垂直掃描頻率基本上就等於整張的掃描頻率, 也因此, 螢幕掃描頻率通常都是以垂直掃描為準就是這個道理.

Note: assert = 表示該接腳信號為有效, 一切以 datasheet 為準, 例如是 low active 時, 接腳為 low 電位就是 assert, 反之則是 high 為 assert.


LCD Panel Timing:

以 Data Image 的七吋面板 FG0700 為例, 這面板的解析度為 800x480, 但是加上無效區為 900x500, 我們知道 dot clock 的公式是:

dot clock = horizontal pixels * vertical pixels * frame rate

注意這邊所謂的 horizontal 與 vertical pixels 都包含 active pixels 與 blink pixels, 因此這張 panel 的 dot clock 為:

900 * 500 * 60 = 27 MHz

picoseconds of per pixel 的公式是 :

1e12 / dot clock

所以每個 pixel 的 ps (picoseconds, pico = e-12):

1 / 27e6 Hz = 37037e-12

水平掃描(horizontal scan-line)一條所需時間的公式是:

horizontal scan-line = horizontal pixels * picoseconds of per pixel

所以每條水平掃描的所需時間為:

900 * 37.037e-9 = 33.333e-6

所以我們知道水平掃描頻率(scan rate)為:

1 / 33.333e-6 = 30 KHz

而整個面板掃完一次需要的時間公式為:

vertical scan-line = vertical pixels * picoseconds of per pixel

500 * 33.333e-6 = 16.6665e-3

由此可以得知垂直掃描頻率為:

1 / 16.6665e-3 = 60 Hz

這意思就是這個面板一秒會更新 60 次.


實作:

參考 Datasheet 可以知道, 在 HSync 之前的叫做 HFP (Horizontal front porch), 在 HSync 之後的叫做 HBP (Horizontal back porch).
但是在 PXA3xx LCD Controller Register 裡叫做 BLW, ELW, 而在 Linux Frame Buffer Driver 裡的 struct 則叫做 left margin, right margin, 這要怎樣對應呢?
這也非常簡單, 參考 Documentatoin/fb/framebuffer.txt 與 PXA3xx Datasheet :

- left_margin: time from sync to picture
- right_margin: time from picture to sync
- upper_margin: time from sync to picture
- lower_margin: time from picture to sync

BLW + 1 = HBP
ELW + 1 = HFP
BFW = VBP
EFW = VFP

因此:

left_margin = HBP = BLW - 1
right_margin = HFP = ELW - 1
upper_margin = VBP = BFW
lower_margin = VFP = EFW

然後參考 datasheet 把正確的值填到 struct 裡, 開機 fb driver 正確啟動後, 應該可以在 LCD 看到一隻企鵝, 至於 WinCE 的改法也很簡單, 以 PXA3xx WinCE BSP 為例, 可以在 display driver 的 panel 目錄裡找到相對應硬體的結構, 改一改就好了, 比較麻煩的是一些可程式的 LCD, 這類 LCD 通常就要再送 SPI command 才會動.

星期四, 4月 14, 2011

ARM based 的 __pa() 與 __va()

Linux 的 memory 分為:

1. user space virtual address
2. physical address (hardware)
3. bus address (IO)
4. kernel logical address
5. kernel virtual address


__pa(x) 與 __va(x) 是用來轉換 kernel logical address 與 <-> physical address 用的 macro, 由 <asm/page.h> 定義。
<->
通常 logical 跟 physical 只差在一個固定量的常數,以 ARM 為例為 PAGE_OFFSET,往下追蹤這兩個 macro 可找到:
 
<-><arch/arm/include/asm/memory.h>


#define __pa(x)   __virt_to_phys((unsigned long)(x))
#define __va(x)   ((void *)__phys_to_virt((unsigned long)(x)))


可知:
  __pa(x) ==> __virt_to_phys,  __va(x) ==> __phys_to_virt.

繼續追蹤,可以在同一個檔案看到如下這些定義:


#define __virt_to_phys(x) ((x) - PAGE_OFFSET + PHYS_OFFSET)
#define __phys_to_virt(x) ((x) - PHYS_OFFSET + PAGE_OFFSET)

在較舊版本 Linux 中, PAGE_OFFSET 可能會直接指定, 但在較新版本 Linux 如 2.6.36 中可能會看到如下定義:

#define PAGE_OFFSET  UL(CONFIG_PAGE_OFFSET)

看到 "CONFIG_" 開頭就不用想了, 這表示會在 autoconf.h 裡, 以 2.6.36 版為例可在 include/generated/autoconf.h 找到如下定義:
(舊版本 autoconf.h 路徑會有所不同)

#define CONFIG_PAGE_OFFSET 0xC0000000

而 PHYS_OFFSET 則依照平台不同而有所變化, 以 PXA 系列為例可以在下面檔案找到:

arch/arm/mach-pxa/include/mach/memory.h

定義內容如下:

#define PHYS_OFFSET UL(0xa0000000)

所以我們可以由此推算 logical 轉 physical 公式為:

physical = logical - 0xc0000000 + 0xa0000000

以 linux kernel 進入點為 0xc0008000 來說, 其對應的 physical address 就是 0xa0008000

然後我們就可以對照 System.map 這檔案的列表, 把想要的東西 dump 出來看,
如 Embedded Linux Primer 裡提到的利用 __log_buf 來除錯.

實際上來說, 0xa0000000 是 PXA270 的 physical address (Partitoin 0),
在 Marvell PXA310 EVM 中定義的 physical address 是 0x80000000,
Marvell PXA3xx Datasheet 的定義:

1.6.2.2 Dynamic Memory Controller Memory Map

Chip Select Address Space
nSDCS<1> (PXA32x processor only) 0xC000_0000 – 0xFFFF_FFFF
nSDCS<1> (PXA30x and PXA31x Only) 0xC000_0000 – 0xDFFF_FFFF
nSDCS<0> (PXA32x processor only) 0x8000_0000 – 0xBFFF_FFFF
nSDCS<0> (PXA30x and PXA31x only) 0x8000_0000 – 0x9FFF_FFFF

也因此如果自己拿新版 kernel 來 build 放到 EVM 上, 基本上只會看到
Uncompressing Linux... done, booting the kernel. 然後就當了.
知道原因之後, 只要把 0xa0000000 改成 0x80000000 就好了, 如下:

修改
#define PHYS_OFFSET UL(0xa0000000)

變成
#ifdef CONFIG_PXA3xx
#define PHYS_OFFSET UL(0x80000000)
#else
#define PHYS_OFFSET UL(0xa0000000)
#endif

Essential Linux Device Driver 中文版堪誤

最近看了 Essential Linux Device Driver 中文版, 大致上來說還不錯,但各章節翻譯水準忽高忽低,有時還會漏掉一段,看得挺痛苦,不得已只好找原文的來對照看,例如中文版 Page 61:

面對更高層次的 kthread API (其目的在於超越 kernel_thread()), kernel_thread() 的地位下降了。稍後我們會分析 kthreads。

不知所云為何,只好查一下原文,原本這句是:
kernel_thread() is depreciated in favor of the higher-level kthread API, which is built over the former. We will look at kthreads later on.
在 Linux API 裡,depreciated 的意思主要是『不建議使用,因為已過時且有更好的替代方案』,而且原文中,把 kernel_thread() is depreciated 放在第一句是有強調的意味,在譯文中居然被移到後面模糊了重點!

我覺得這句應該譯成比較能表達作者原來的意思:

kernel_thread() 已經過時且不建議使用,而其類似功能已被更高級的 kthread API 所支援,該 API 就是被開發出來取代前者的,我們稍後再來看 kthreads。

還是乖乖看原文好了,唉!

在 Ubuntu 裡用 apt-get 安裝 Cross Compiler (Tool-Chains)

很多人都是用 crosstool 來安裝 cross compiler
不過現在都 21 世紀了, 我們有更方便的方式來安裝 cross compiler.
Linaro 有幫我們預先做好 ARM 系列的 cross tool-chains, 只要依照以下網址的方法安裝,
就可輕鬆用 apt-get 來安裝 cross compiler 了.

https://wiki.linaro.org/Resources/ToolchainInstall-10

sudo add-apt-repository ppa:linaro-maintainers/toolchain
sudo apt-get update
sudo apt-get install gcc-4.5-arm-linux-gnueabi
dpkg -L gcc-4.5-arm-linux-gnueabi

About BUG_ON and WARN_ON

在 linux kernel 中可以常常看到 BUG_ON 與 WARN_ON 這兩個 macro.
BUG_ON 與 WARN_ON 有點像是我們平常在用的 assert, 不過作用剛好相反.
當 BUG_ON 裡的條件式為真時, 表示 kernel 有 bug, 這時會呼叫 panic 終止 kernel 運行.

我們可以在 include/asm-generic/bug.h 這裡找到這幾個宣告.
在 BUG_ON 裡可以看到如果判斷式為真時, 就呼叫 BUG 這個 macro,
繼續追蹤下去可看到 BUG 會把檔案, 函式與行號印出之後呼叫 panic() 讓 kernel 停止,
而 WARN_ON 則是把 panic() 換成 dump stack() 而已, 至於 unlikely 跟 likely 的用法請參考 Linux Device Driver 這本書, 裡有說明.
如下:

#ifndef HAVE_ARCH_BUG
#define BUG() do { \
 printk("BUG: failure at %s:%d/%s()!\n", __FILE__, __LINE__, __FUNCTION__); \
 panic("BUG!"); \
} while (0)
#endif

#ifndef HAVE_ARCH_BUG_ON
#define BUG_ON(condition) do { if (unlikely((condition)!=0)) BUG(); } while(0)
#endif

#ifndef HAVE_ARCH_WARN_ON
#define WARN_ON(condition) ({      \
 typeof(condition) __ret_warn_on = (condition);   \
 if (unlikely(__ret_warn_on)) {     \
  printk("BUG: at %s:%d %s()\n", __FILE__,  \
   __LINE__, __FUNCTION__);   \
  dump_stack();      \
 }        \
 unlikely(__ret_warn_on);     \
})
#endif

星期一, 2月 14, 2011

Linux 的 Jiffies, Tick 與 HZ 筆記

Linux 的 Jiffies, Tick 與 HZ 筆記

HZ
Linux 核心每隔固定週期會發出 timer interrupt (IRQ 0),HZ 是用來定義每一秒有幾次 timer interrupts。
舉例來說,HZ為1000,代表每秒有1000次 timer interrupts。

Tick
Tick是HZ的倒數,意即timer interrupt每發生一次中斷的時間。如HZ為250時,tick為4毫秒(millisecond)。

Jiffies
Jiffies 變數的意思是:每秒鐘執行 HZ 次。

以下程式碼會等待(佔用 CPU) 3 秒鐘 (busy-looping):
unsigned long timeout = jiffies + 3 * HZ;
whilie (time_before(jiffies, timeout)) continue;

另一個比較好的等待方法如下:(會在等待三秒鐘時做其他排程的事)
unsigned long timeout = 3 * HZ;
schedule_timeout(timeout);

HZ可在編譯核心時設定,如下所示:
$ cd /usr/src/linux
$ make menuconfig
Processor type and features ---> Timer frequency (250 HZ) --->
在 x86 平台上,kernel 2.4 HZ 預設為 100,kernel 2.6 HZ 預設為 1000,然而在 kernel 2.6.13 之後,HZ又改為預設 250。
在 ARM 平台上,kernel 2.6 中該值一般設成 100。

觀察/proc/interrupt的timer中斷次數,並於一秒後再次觀察其值。理論上,兩者應該相差250左右。
$ cat /proc/interrupts | grep timer && sleep 1 && cat /proc/interrupts | grep timer
0:        146    XT-PIC-XT        timer
LOC:     398006   Local timer interrupts
0:        146    XT-PIC-XT        timer
LOC:     398118   Local timer interrupts
上面四個欄位分別為中斷號碼、CPU中斷次數、PIC與裝置名稱。

要檢查系統上HZ的值是什麼,可執行命令:
$ cat /boot/config-`uname -r` | grep '^CONFIG_HZ='
#CONFIG_HZ_100 is not set
CONFIG_HZ_250=y
# CONFIG_HZ_300 is not set
# CONFIG_HZ_1000 is not set
CONFIG_HZ=250

HZ 的設定在平台相關的 param.h 裡:
$ fgrep HZ include/asm-arm/param.h
# define HZ             CONFIG_HZ       /* Internal kernel timer frequency */
# define USER_HZ        100             /* User interfaces are in "ticks" */
# define CLOCKS_PER_SEC (USER_HZ)       /* like times() */
# define HZ             100

星期二, 12月 21, 2010

What is linux-gate.so.1

What is linux-gate.so.1 :
http://www.trilithium.com/johan/2005/08/linux-gate/

中譯:
http://www.builder.com.cn/2008/0327/784518.shtml

以下是中文版轉貼(簡轉繁):



Linux-gate.so.1的含義?(What is linux-gate.so.1)


當你在一個比較新的linux系統下使用ldd命令時,你會經常看到一個比較奇怪的文件名,即linux-gate.so.1:

ldd /bin/sh
linux-gate.so.1 => (0xffffe000)
libdl.so.2 => /lib/libdl.so.2 (0xb7fb2000)
libc.so.6 => /lib/libc.so.6 (0xb7e7c000)
/lib/ld-linux.so.2 (0xb7fba000)

它到底是什麼文件呢?僅僅是一個動態載入庫(dynamically loaded library),對嗎?

對於廣義的動態載入庫定義來說,有那麼幾分意思。輸出缺少文件名暗示了ldd不能找到這個文件的位置。事實上,任何試圖找到這個文件的做法——無論是手工查找還是利用自動載入和分析這種庫文件的軟件——都不會成功。

一些用戶時常因為尋找並不存在的系統文件而暈頭轉向並屢屢受挫。對於這種根本就不會有結果的尋找,你可以很自信的告訴用戶:linux-gate.so.1文件目前在文件系統中根本就不被支持;它只是一個虛擬的DSO(譯者注virtual DSO:dynamically shared object),一個在每個進程的存儲空間(process' memory)指定的地址點被內核暴露出來的共享對象:

cat /proc/self/maps
08048000-0804c000 r-xp 00000000 08:03 7971106 /bin/cat
0804c000-0804d000 rwxp 00003000 08:03 7971106 /bin/cat
0804d000-0806e000 rwxp 0804d000 00:00 0 [heap]
b7e88000-b7e89000 rwxp b7e88000 00:00 0
b7e89000-b7fb8000 r-xp 00000000 08:03 8856588 /lib/libc-2.3.5.so
b7fb8000-b7fb9000 r-xp 0012e000 08:03 8856588 /lib/libc-2.3.5.so
b7fb9000-b7fbc000 rwxp 0012f000 08:03 8856588 /lib/libc-2.3.5.so
b7fbc000-b7fbe000 rwxp b7fbc000 00:00 0
b7fc2000-b7fd9000 r-xp 00000000 08:03 8856915 /lib/ld-2.3.5.so
b7fd9000-b7fdb000 rwxp 00016000 08:03 8856915 /lib/ld-2.3.5.so
bfac3000-bfad9000 rw-p bfac3000 00:00 0 [stack]
ffffe000-fffff000 ---p 00000000 00:00 0 [vdso]

這裡,cat輸出了它自己的內存映射。標記[vdso]的行是該進程的linux-gate.so.1對象,一個映射到地址為ffffe000的單獨的內存頁面。程序可以通過查詢ELF輔助向量中的AT_SYSINFO入口點來獲取內存中共享對象的位置。和程序參數(argv)和環境變量(envp)類似,輔助向量(auxv)是一個指向新進程的指針數組。

理論上進程之間的地址可能是不同的,但是,據我所知linux內核總是把它映射到一個固定的位置。上面例子的輸出來自一個x86 box,那裡進程存在於無格式老的32位地址空間,空間頁面大小為4096字節,使得ffffe000為倒數第二個頁面。真正的最後一個頁面被保存用來捕捉通過無效指針訪問,例如,對一個消耗掉的NULL指針或者從mmap返回的MAP_FAILED指針進行解除參照。

因為所有的進程在相同的位置共享相同的對象,所以如果我們想進一步查看,很容易的就可以摘錄出它的一個副本。例如,我們可以簡單的使用dd來從它自己的空間中得到這個頁面(謹慎的選擇一個與linux-gate.so.1不同的輸出名以免創建已存在的文件):

dd if=/proc/self/mem of=linux-gate.dso bs=4096 skip=1048574 count=1
1+0 records in
1+0 records out

我們跳過1048574個頁面是因為總共有220=1048576個頁面,我們想要的是與最後一個頁面相鄰的那個頁面。和其它的共享ELF對象文件類似,結果如下:

file -b linux-gate.dso
ELF 32-bit LSB shared object, Intel 80386, version 1 (SYSV), stripped

objdump -T linux-gate.dso

linux-gate.dso: file format elf32-i386

DYNAMIC SYMBOL TABLE:
ffffe400 l d .text 00000000
ffffe460 l d .eh_frame_hdr 00000000
ffffe484 l d .eh_frame 00000000
ffffe608 l d .useless 00000000
ffffe400 g DF .text 00000014 LINUX_2.5 __kernel_vsyscall
00000000 g DO *ABS* 00000000 LINUX_2.5 LINUX_2.5
ffffe440 g DF .text 00000007 LINUX_2.5 __kernel_rt_sigreturn
ffffe420 g DF .text 00000008 LINUX_2.5 __kernel_sigreturn

這些符號是rt_sigreturn/sigreturn函數的入口點,也是為了虛擬系統調用(virtual system call)用而設置。在x86平台上,linux-gate.so.1一開始的名字是linux-vsyscall.so.1,但是在開發過程中,為了使用一個通俗的名字而被修改了,從而精確的反映了在跨平台下的目的:在用戶和內核空間之間起到網關(gateway)的作用。並不是所有的系統都需要虛擬系統調用,但是對x86它們一定同等重要,從而來保證這個精細的機制。

傳統中,x86系統調用通過中斷來實現。你可能還記得在不方便的老的MS-DOS時代,請求操作系統函數的方式是通過中斷33(21h)。Windows系統調用隱藏在用戶模式API層,但在一些地方它們同樣深入到了int ox2e。在linux和其它的*nix內核中類似的實現使用int 0x80。

然而,在很多x86處理器家族中的新成員中,通過使用中斷來調用系統調用的效率被證明是「相當」(^_^)低的。一個0x80系統調用單從命令來說,在2GHz Pentium上要比在一個850MHz Pentium Ⅲ上慢很多很多。因為這個原因而產生的在執行上的影響是明顯的,至少對於執行大量系統調用的應用程序是這樣的。

Intel在早期就注意到了這個問題,並且引進了一個更有效的以sysenter和sysexit的形式的系統調用接口。快速系統調用特色最初在Pentium Pro處理器中出現,但是由於硬件上的bug,它實際上被早期的大部分CPU所拋棄(broken)。這就是你可能看到PentiumⅡ甚至Pentium Ⅲ引入了sysenter的聲明的原因了。

硬件的問題也可以幫助解釋為什麼在操作系統開始支持快速系統調用之前經歷了很長時間。如果我們忽略早期的實驗性質的片段(patches),Linux對sysenter的支持出現在2002年12月,那時內核2.5正在開發中。距離sysenter的提出已經有10年了!Micorsoft在Windows XP中才剛剛使用sysenter。

如果你的Linux機器在系統調用上使用了sysenter指令,你可以通過反彙編__kernel_vsyscall找到:

objdump -d --start-address=0xffffe400 --stop-address=0xffffe414 linux-gate.dso

linux-gate.dso: file format elf32-i386

Disassembly of section .text:

ffffe400 <__kernel_vsyscall>:
ffffe400: 51 push %ecx
ffffe401: 52 push %edx
ffffe402: 55 push %ebp
ffffe403: 89 e5 mov %esp,%ebp
ffffe405: 0f 34 sysenter
ffffe407: 90 nop
ffffe408: 90 nop
ffffe409: 90 nop
ffffe40a: 90 nop
ffffe40b: 90 nop
ffffe40c: 90 nop
ffffe40d: 90 nop
ffffe40e: eb f3 jmp ffffe403 <__kernel_vsyscall data-blogger-escaped-x3="x3">
ffffe410: 5d pop %ebp
ffffe411: 5a pop %edx
ffffe412: 59 pop %ecx
ffffe413: c3 ret

系統調用的首選調用策略在啟動時由內核確定,很明顯這個box使用了sysenter。在老式機器上你可能會看到int 0x80被使用。如果你正努力地想搞清楚這個跳轉(jump)的含義(像我第一次看到它一樣),你可能會興趣很濃的去學習以至於認為其原因是:Linus Torvalds is a disgusting pig and proud of it(使用六個參數來處理重起系統調用是一個小竅門)。

星期三, 12月 08, 2010

Linux Asynchronous I/O and Synchronous I/O

一般常見 system call 的 read, write 都屬於 synchronous I/O, 然而這類型的 I/O 在做
Blocking 操作時, 等待時間就被浪費掉了, 而在做 Non-blocking 操作時, 因為要不斷的檢查狀態而產生過多 context switch 的浪費.
Asynchronous I/O 在做 Non-blocking 操作時, 可以等待系統狀態自動回傳再做處理, 這段時間 CPU 可以去做其他的事, 善用 AIO 可以讓程式更有效率.
以下文章介紹了 AIO 與一般 IO 在 blocking 與 non-blocking 的差異, 以及 aio function 的實例.

AIO 詳細介紹(原文) 中文版在這


另外可以參考 linux/fs.h 中的 struct file_operations。

星期三, 11月 24, 2010

printf 與 line buffer 筆記

* 使用 printf 與 fprintf 時若輸出是 stdout, 只有在 1.碰到換行字元 2.呼叫 fflush() 3.buffer full 時才會真正輸出到 stdout.
* line buffer 是 I/O 共用的, 例如 printf 沒達到輸出條件時呼叫 fgets, 則會先強制輸出 printf 的內容以清空 line buffer 供 fgets 使用.
* format: %[flags][filed width][precision][length modifier]conversion type
* printf 回傳值為 0 時表示無輸出, 例如 printf("");

set-user-id (suid), set-group-id (sgid), saved-suid 筆記

set-user-id (suid), set-group-id (sgid), saved-suid 筆記

在 stat.st_mode 的 masks:
S_ISUID    0004000   set UID bit
S_ISGID    0002000   set-group-ID bit (see below)

當 process 在執行時,實際上的 user/group id 是如此表對映的:

User IDs and group IDs associated with each process

real user ID
real group ID
who we really are

effective user ID
effective group ID
supplementary group IDs
used for file access permission checks

saved set-user-ID
saved set-group-ID
saved by exec functions

一般來說,process 的權限是以 real user/group id 為主的,但是當 st_mode 中的
set-user-id 或 set-group-id 屬性開啟時,這意思是執行 process 時會以檔案擁有
者的身份來執行,例如我現在是用 ken 登入,但是當我執行 passwd 修改密碼時,
passwd 其實是以 root 權限在執行的,因為 passwd 有 set-user-id (suid)。

所謂的 EUID 與 EGID 是 effective user/group ID。
這東西就是指 process 在跑的時候,是用什麼身份在跑。
通常 EUID/EGID 會與現在的 UID/GID 相同,
但是當執行檔有SUID/SGID(set-user-ID, set-group-ID)屬性時,
則這個 process 在執行時,EUID/EGID 會變成檔案 owner 的 ID。

舉個最常見的例子,passwd 這隻程式因為要存取 /etc/passwd 或 /etc/shadow,
所以這個檔案在執行時必須要有 root 權限才能寫入,
但是你一定會很納悶,那為什麼我用一般 user 登入,
一樣可以用 passwd 來改自己的密碼呢?
答案就在於 passwd 有 SUID,
所以 passwd 在執行時,其 UID (或稱 real user ID) 即使是一般 user,
但是 EUID 其實是 root,這樣才能對 /etc/passwd 之類的檔案有存取權。
SGID 也是一樣的道理,因為早期系統沒有 mkdir system call,
為了使用只有 root 才能呼叫的 mknod 來產生目錄,就要利用到 SGID。
但是現代一些 UNIX-Like 的 implement 對 SGID 的繼承方式其實是有微妙的差異的。
如 BSD 與 Linux 在某些系統呼叫上對這個 bit 的處理都不太一樣。

至於 saved suid 是用來保存 suid 的,主要用途如下:


* saved suid 是 2001 年的 POSIX.1 標準, 在編譯階段可檢查 _POSIX_SAVED_IDS 定義.
* 在 runtime 階段可以透過呼叫 sysconf 帶入 _SC_SAVED_IDS 參數來檢查.
* 此屬性與兩個函式相關, exec 與 setuid.
* 這個屬性無法在執行階段取得, 此屬性是用來供 setuid 切換 effective uid 時用來比對的.
* saved uid 是用來供 setuid 函式能夠在 real uid 跟 saved suid 中切換 effective uid 用.

Refer to APUE 2E chapter 4.4, 8.11 and 10.9.

Linux Memory Allocate Strategy

The difference between kmalloc() and vmalloc():

kmalloc() -> physically contiguous
vmalloc() -> virtually contiguous (not physically contiguous)

kmalloc() flags:

GFP_ATOMIC -> non-sleep (interrupt)
GFP_KERNEL -> able to sleep (process context)

If you want to allocate high memory:

alloc_pages() and kmap()

星期一, 11月 01, 2010

編譯 GCC cross-compiler 出現 WARNING: `makeinfo' is missing on your system. 解法

如果你在編譯 cross-compiler 出現下列錯誤訊息,請不用驚慌,這是 binutils-2.18 的 bug,據說已經在較新版本中修正。

WARNING: `makeinfo' is missing on your system.  You should only need it if
         you modified a `.texi' or `.texinfo' file, or any other file
         indirectly affecting the aspect of the manual.  The spurious
         call might also be the consequence of using a buggy `make' (AIX,
         DU, IRIX).  You might want to install the `Texinfo' package or
         the `GNU make' package.  Grab either from any GNU archive site.

在 binutils 目錄裡的 configure 與 configure.ac 中搜尋 missing makeinfo 可以找到如下行:

| egrep 'texinfo[^0-9]*([1-3][0-9]|4\.[4-9]|[5-9])' >/dev/null 2>&1; then

很明顯的,這個正規式(regular expression)有點問題,因為他是判斷 1x~3x 或 4.4+ 或 5+ 版本,結果我的 makeinfo 剛好是 4.13!

所以就幫他修改一下改成下面這樣變成判斷 4.x/4.xx 就可以了

| egrep 'texinfo[^0-9]*([1-3][0-9]|4\.[0-9][0-9]*|[5-9])' >/dev/null 2>&1; then

實際上我們只要改 configure 即可,因為 configure.ac 應該是供 auto-tools 來產生 configure 用的,不過為了保險起見還是兩個一起改好了。

因為是用 fgrep 去搜的,一併搜到 gcc-4.2.1 裡的 configure.in 與 configure 裡的正規式也是錯的,順便也改一下。

星期四, 1月 14, 2010

uClibc 的一些細節

1. uClibc 沒 source 指令,可以用 . 來代替。

2. uClibc 下設定時間的方式:

格式是date後跟[月日時分年]
注意年是4位年,例如:2007年10月26日 10:00:00
date -s 102610002007

普通 Linux 下設定時間的方式 (對照參考):
date -s "20080705 12:00:00 UTC+0800"
date -s 20080705
date -s 12:00:00

星期三, 7月 08, 2009

Bash Shell Script Notes

Bash Shell Script Notes

(Keywords)保留字:

! esac select }
case fi then [[
do for until ]]
done function while
elif if time
else in {


預設變數與環境變數:

    $?              上個指令執行的回傳值 (0 = 成功執行)
    $IFS            Shell Script 參數間格字元 (空白、Tab 等)
    $0              Shell Script File Name
    $1, $2 etc...   First Parameter, Second Parameter etc...
    $#              Number of Parameters
    $$              PID of the Shell Script


自定變數:

    定義: foo=123 或 foo="Hello, World"
    使用: $foo 或 ${foo}
    移除: foo= 或 unset foo
    注意: 定義時變數名稱與 = 之間不可以有空白,如 foo = 123 是錯誤的。


test 與 [ 條件判斷:

    通常條件判斷會用 test,test 可用來做很多種比對,像比較兩個整數、判斷檔案存在與否等等。
    語法:

        test EXPRESSION
        test
        [ EXPRESSION ]
        [ ]
        [ OPTION

    實際上 [ 通常是 test 的連結(Fedora7 是獨立的),可以用 ls 來觀察如:
        ls -l /usr/bin/[ /usr/bin/test

    通常我們還會用 ] 來對應前面的 [。

    test 常用參數:

    -e FILE                 檔案存在
    -f FILE                 檔案存在並且一般檔案 (較常用)
    -d FILE                 目錄存在
    -h FILE                 檔案是否為 symbol link (同 -L)
    -x FILE                 檔案是否可執行
    -s FILE                 檔案大小不是零

    -n STRING               字串非零 (not zero)
    -z STRING               字串為零 (zero string)

    INTEGER1 -eq INTEGER2   INTEGER1 等於 INTEGER2
    INTEGER1 -ge INTEGER2   INTEGER1 大於等於 INTEGER2
    INTEGER1 -gt INTEGER2   INTEGER1 大於 INTEGER2
    INTEGER1 -le INTEGER2   INTEGER1 小於等於 INTEGER2
    INTEGER1 -lt INTEGER2   INTEGER1 小於 INTEGER2
    INTEGER1 -ne INTEGER2   INTEGER1 不等於 INTEGER2

    其他的可看 man test。


if:

    然後可以搭配 if 來使用,if 語法如下:

    if ...condition...; then
        ...statements...
    else
        ...statements...
    elif
        ...statements...
    fi

    範例:
    XFILE=/bin/ls
    if [ -x ${XFILE} ]; then
        $XFILE
    else
        echo "not executable file"
    fi


switch/case:

    case ...variable... in
        ... pattern | patterns ... )
            ...statements...
        ;;
    esac


for 迴圈:

    for ...variable... in ...variables...; do
        ...statements...
    done


while 迴圈:

    while ...condition...; do
        ...statements...
    done

    until ...condition...; do
        ...statements...
    done


其他迴圈控制:

    break
    continue
    return

    這三個用法與 C 基本上都一樣。
    return 後未加數值,會回應最後一個取得的回應值。

星期一, 7月 06, 2009

What is ABI, EABI and OABI

What is ABI, EABI and OABI
--------------------------
What is ABI:
ABI = Application Binary Interface

什麼是ABI(Application Binary Interface)?

ABI 定義:
就像 API (Application Interface) 是原始碼的介面,ABI 是低階的二元碼介面。

ABI 用途:
ABI 是用來確定二元檔的相容性,以保證目地碼(object code)在使用相同的 ABI 時,
在任何的系統上都不需要重新編譯(recompilation)。

ABI 影響到的東西有:
calling coventions
byte ordering
register use
system call invocation
linking
library behavior
binary object format

calling coventions 是指函式如何被引用,引數如何傳遞,暫存器哪個該保留或
哪個該使用,與調用的函式如何接收與傳回數值,之類的函式呼叫慣例。

ABI 特性:
ABI 是與硬體特性相關的,各平台(architecture)的 ABI 都不一樣。


Reference:
O'Reilly Linux System Programming, Chapter 1.

ABIs

Whereas an API defines a source interface, an ABI defines the low-level binary interface
between two or more pieces of software on a particular architecture. It defines
how an application interacts with itself, how an application interacts with the kernel,
and how an application interacts with libraries. An ABI ensures binary compatibility,
guaranteeing that a piece of object code will function on any system with the same
ABI, without requiring recompilation.

ABIs are concerned with issues such as calling conventions, byte ordering, register
use, system call invocation, linking, library behavior, and the binary object format.

The calling convention, for example, defines how functions are invoked, how arguments
are passed to functions, which registers are preserved and which are mangled,
and how the caller retrieves the return value.

Although several attempts have been made at defining a single ABI for a given architecture
across multiple operating systems (particularly for i386 on Unix systems), the
efforts have not met with much success. Instead, operating systems—Linux
included—tend to define their own ABIs however they see fit. The ABI is intimately
tied to the architecture; the vast majority of an ABI speaks of machine-specific
concepts, such as particular registers or assembly instructions. Thus, each machine
architecture has its own ABI on Linux. In fact, we tend to call a particular ABI by its
machine name, such as alpha, or x86-64.

System programmers ought to be aware of the ABI,but usually do not need to
memorize it. The ABI is enforced by the toolchain—the compiler, the linker, and so
on—and does not typically otherwise surface. Knowledge of the ABI, however, can
lead to more optimal programming, and is required if writing assembly code or hacking
on the toolchain itself (which is, after all, system programming).
The ABI for a given architecture on Linux is available on the Internet and implemented
by that architecture’s toolchain and kernel.


What is EABI, OABI:

EABI = Embedded ABI or Extended ABI
OABI = Old ABI

[QUOTE]

http://www.oesf.org/index.php?title=Q3:_What_are_OABI_and_EABI%3F

Q3: What are OABI and EABI?

A3: They are two kinds of ABI (Application Binary Interface). The ‘O’
stands for ‘Old’, and the ‘E’ means ‘Embedded’, a new approach to
API for portable devices like the Zaurus.

According to this explanation,

GNU EABI is a new application binary interface (ABI) for Linux. It is
part of a new family of ABI's from ARMR Ltd. known in the arm-linux
community as EABI (or sometimes Embedded ABI).

According to Debian Wiki the new EABI:
Allows mixing softfloat and hardfloat code.
Uses a more efficient syscall convention.
Will be more compatible with future tools.

Furthermore, the GCC default for EABI will be to use softfloat
instructions for floating point arithmetic.

[/QUOTE]