2014/12/12

Symbols of object file

1. Symbol Table


在 ELF file 裡, 其中有些 section 它的 type 是 DYNSYM 或 SYMTAB, 裡面含有symbol table,
我們可以用readelf來dump這些symbols, 這邊以簡單的 hello.c 當範例, compile & link 之後
$ readelf --syms hello
Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__

Symbol table '.symtab' contains 65 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000400238     0 SECTION LOCAL  DEFAULT    1 
     2: 0000000000400254     0 SECTION LOCAL  DEFAULT    2 
     3: 0000000000400274     0 SECTION LOCAL  DEFAULT    3 
     4: 0000000000400298     0 SECTION LOCAL  DEFAULT    4
....

會看到有兩個section有symbol table, 分別是 dynsym 以及 symtab

同樣的事情也可以用nm來做
$ nm hello
0000000000601040 B __bss_start
0000000000601040 b completed.6972
0000000000601030 D __data_start
0000000000601030 W data_start
......

會發現, 用 readelf 讀出來的 symbole item 比較多, 但是 readelf 裡面有不少是空的item, FILE type 的 item 也不會在 nm 裡出現

如果我們將檔案 strip 過, 會發現 symtab 被清掉了
$ strip hello

$ nm hello
nm: hello: no symbols

$ readelf --syms hello

Symbol table '.dynsym' contains 4 entries:
   Num:    Value          Size Type    Bind   Vis      Ndx Name
     0: 0000000000000000     0 NOTYPE  LOCAL  DEFAULT  UND 
     1: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND puts@GLIBC_2.2.5 (2)
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __libc_start_main@GLIBC_2.2.5 (2)
     3: 0000000000000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__


2. Symbol type


用 nm dump 出來的 symbol 的 type , 如果是大寫表示global, 小寫表示local

text section

  • T : symbol 位於 text section
data section

  • D : symbol 位於已初始化的 data section
  • G : symbol 位於小物件使用的已初始化 data section. (NOTE: 有些環境用近距離symbol效率較好)
read-only data

  • R : symbol 位於read-only data section
BSS (non-initialize data)

  • B : symbol 位於未初始化 data section (BSS)
  • S : symbol 位於小物件使用的未初始化 data section
Weak  object

  • V : symbol 是 weak object
Weak symbol

  • W : symbol 是未解析 weak object 的 weak symbol
Shared

  • C : common symbol, 未初始化資料
Debug purpose

  • N : debug purpose symbol
  • - : symbol 是 a.out object file 的 stabs symbol (for debug information)
Absolute value

  • A : symbol 是absolute value, linking的時候不會改變
Undefined

  • U : undefined symbol, instance應該在其它object file或是shared library
Indirect reference

  • I : Indirect reference symbol, 是a.out 的 GNU extension feature
Unknown

  • ? : 






2014/12/08

NFC NXP 203 Tag content example

1. Plain text example


底下是用一般的 nfc tag writter 寫進ndef plain text "123456789"的tag content

page 0     0469b055 c22d3580 5a480000 e1101200
page 4     0103a010 440310d1 010c5402 656e3132
page 8     33343536 373839fe 00000000 00000000
page 12    00000000 00000000 00000000 00000000
page 16    00000000 00000000 00000000 00000000
page 20    00000000 00000000 00000000 00000000
page 24    00000000 00000000 00000000 00000000
page 28    00000000 00000000 00000000 00000000
page 32    00000000 00000000 00000000 00000000
page 36    00000000 00000000 00000000 00000000
page 40    00000000 00000000

可以看到它總共有42個page, 根據 NFX 203F 的 format, user data 存在 page 4~39
而page 0~3, 40~41 用於紀錄 ID, checksum, read only, counter

1.1 TLV Blocks


Tag 內容格式為 TLV (Tag, Length, Value)
Tag 的值為底下幾種

  • NULL TLV (0x00) : 用於對齊 memory
  • Lock Control TLV (0x01) : 對 lock bit 做進一步描述
  • Memory Control TLV (0x02) : 定義剩下的memory
  • NDEF Message TLV (0x03)
  • Proprietary TLV (0xFD)
  • Terminator TLV (0xFE) : 最後一個TLV
上面的例子裡, 可以看到3個TLV
TLV 1: (Lock Control)
01 03 a01044

TLV 2: (NDEF Message)
03 10 d1010c5402656e313233343536373839

TLV 3: (Terminator)
fe


1.2 NDEF Message


在 NDEF TLV 的 VALUE 為 NDEF message
d1010c5402656e313233343536373839

第一個byte 0xd1為 NDEF Flag + TNF

  • MB = 1
  • ME = 1
  • CF = 0
  • SR = 1
  • IL = 0
  • TNF = 1

接下來的 1 個byte是 Type Length
Type Length = 0x01

因為 SR (Short Record) 為 1, 所以接下來的Payload length 長度為1個byte
Payload Length = 0x0c

因為 IL (ID Length is present) 為0, 所以沒有 ID 相關的欄位

接著是1個 byte 的 Payload Type
Payload Type = 0x54 (WELL-KNOWN: urn:nfc:wkt:T (Text))

最後是NDEF Text content:
02 65 6e 31 32 33 34 35 36 37 38 39
   E  N  1  2  3  4  5  6  7  8  9

第一個byte 0x02為Status

  • bit 7 : 0表示UTF-8 encoded, 1表示UTF16 encoded
  • bit 6 : RFU (must be set to zero)
  • bit 5..0 : IANA language code的長度
可以看到它用UTF-8, IANA language code的長度為2
所以接著2個byte為 0x65, 0x6e 為 "EN" 是它的 IANA language code
接著是我們的message "123456789"













2014/11/25

objdump

1. Use objdump dump ELF binary


如果想 dump 每個 section 的 binary, 可以用objdump

$ objdump --full-contents /bin/ls
Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.
Contents of section .note.ABI-tag:
 400254 04000000 10000000 01000000 474e5500  ............GNU.
 400264 00000000 02000000 06000000 18000000  ................
Contents of section .note.gnu.build-id:
 400274 04000000 14000000 03000000 474e5500  ............GNU.
 400284 64d095bc 6589dd4b fbf1c6d6 2ae98538  d...e..K....*..8
 400294 5965461b                             YeF.
Contents of section .gnu.hash:
 400298 03000000 72000000 02000000 07000000  ....r...........
 4002a8 a201400c 12010c3f 28440003 a8040000  ..@....?(D......
 4002b8 72000000 75000000 7e000000 281d8c1c  r...u...~...(...
 4002c8 4245d5ec bbe3927c bc50769e 86f0967c  BE.....|.Pv....|
 4002d8 96a08997 3cad390d d871581c ce2c6372  ....<.9..qX..,cr
 4002e8 e46241f5 b88df10e 39f28b1c 32c4f712  .bA.....9...2...
 4002f8 ead3ef0e b3a2f712                    ........

但示通常會印出一堆, 所以通常會看特定的section
可以用 objdump 找 section
$ objdump --section-headers /bin/ls

/bin/ls:     file format elf64-x86-64

Sections:
Idx Name          Size      VMA               LMA               File off  Algn
  0 .interp       0000001c  0000000000400238  0000000000400238  00000238  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  1 .note.ABI-tag 00000020  0000000000400254  0000000000400254  00000254  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  2 .note.gnu.build-id 00000024  0000000000400274  0000000000400274  00000274  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  3 .gnu.hash     00000068  0000000000400298  0000000000400298  00000298  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  4 .dynsym       00000c18  0000000000400300  0000000000400300  00000300  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  5 .dynstr       00000593  0000000000400f18  0000000000400f18  00000f18  2**0
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  6 .gnu.version  00000102  00000000004014ac  00000000004014ac  000014ac  2**1
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  7 .gnu.version_r 00000090  00000000004015b0  00000000004015b0  000015b0  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  8 .rela.dyn     000000a8  0000000000401640  0000000000401640  00001640  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
  9 .rela.plt     00000a80  00000000004016e8  00000000004016e8  000016e8  2**3
                  CONTENTS, ALLOC, LOAD, READONLY, DATA
 10 .init         0000001a  0000000000402168  0000000000402168  00002168  2**2
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 11 .plt          00000710  0000000000402190  0000000000402190  00002190  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
 12 .text         0000f65a  00000000004028a0  00000000004028a0  000028a0  2**4
                  CONTENTS, ALLOC, LOAD, READONLY, CODE
......

找到想要印的section
$ objdump --full-contents --section .interp /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.

也可以指定範圍, 比如說 .interp 從 0x400238 到 0x400253
$ objdump --full-contents --start-address=0x400238 --stop-address=0x400253 /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e32             x86-64.so.2

如果指定的範圍超過 section 邊界的話, 它會將跨過的 section 名字也印出來
$ objdump --full-contents --start-address=0x400238 --stop-address=0x400280 /bin/ls

/bin/ls:     file format elf64-x86-64

Contents of section .interp:
 400238 2f6c6962 36342f6c 642d6c69 6e75782d  /lib64/ld-linux-
 400248 7838362d 36342e73 6f2e3200           x86-64.so.2.
Contents of section .note.ABI-tag:
 400254 04000000 10000000 01000000 474e5500  ............GNU.
 400264 00000000 02000000 06000000 18000000  ................
Contents of section .note.gnu.build-id:
 400274 04000000 14000000 03000000           ............

如果只想把 ELF 檔案當 binary 來印, 或是想印某些純 binary 檔
$ objdump --full-contents -b binary /bin/ls


2. Use objdump to disassemble file


假如有個 "hello.c", 內容如下:
// hello.c
#include <stdio.h>

int main() {
        printf("Hello World\n");
        return 0;
}

然後 compile : "gcc -c -g hello.c", "gcc -o hello hello.o"

可以用 objdump 反組譯
$ objdump -d hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
   e:   b8 00 00 00 00          mov    $0x0,%eax
  13:   5d                      pop    %rbp
  14:   c3                      retq

使用 -d (--disassemble) 反組譯, 通常只會包含執行碼的 section (.text之類的)
如果想反組譯所有 section, 可以使用 -D ( --disassemble-all ), 但是會把不是程式碼的部份也當成程式碼反組譯

反組譯的輸出格式是
address <symbol>
    address:    code byte sequence        disassemble result

如果不想看到 code byte sequence
$ objdump -d --no-show-raw-insn hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
   0:   push   %rbp
   1:   mov    %rsp,%rbp
   4:   mov    $0x0,%edi
   9:   callq  e <main+0xe>
   e:   mov    $0x0,%eax
  13:   pop    %rbp
  14:   retq


如果想看到逐行加上 symbol 的位置
$ objdump -d --prefix-address hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:
0000000000000000 <main> push   %rbp
0000000000000001 <main+0x1> mov    %rsp,%rbp
0000000000000004 <main+0x4> mov    $0x0,%edi
0000000000000009 <main+0x9> callq  000000000000000e <main+0xe>
000000000000000e <main+0xe> mov    $0x0,%eax
0000000000000013 <main+0x13> pop    %rbp
0000000000000014 <main+0x14> retq

此時 code byte sequence 就會被忽略, 如果想加回code byte sequence
$ objdump -d --prefix-address --show-raw-insn hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:
0000000000000000 <main> 55                      push   %rbp
0000000000000001 <main+0x1> 48 89 e5                    mov    %rsp,%rbp
0000000000000004 <main+0x4> bf 00 00 00 00              mov    $0x0,%edi
0000000000000009 <main+0x9> e8 00 00 00 00              callq  000000000000000e <main+0xe>
000000000000000e <main+0xe> b8 00 00 00 00              mov    $0x0,%eax
0000000000000013 <main+0x13> 5d                         pop    %rbp
0000000000000014 <main+0x14> c3                         retq


如果只想反組譯特定的 section
$ objdump -d --section .init hello

hello:     file format elf64-x86-64


Disassembly of section .init:

00000000004003e0 <_init>:
  4003e0:       48 83 ec 08             sub    $0x8,%rsp
  4003e4:       48 8b 05 0d 0c 20 00    mov    0x200c0d(%rip),%rax        # 600ff8 <_DYNAMIC+0x1d0>
  4003eb:       48 85 c0                test   %rax,%rax
  4003ee:       74 05                   je     4003f5 <_init+0x15>
  4003f0:       e8 3b 00 00 00          callq  400430 <__gmon_start__@plt>
  4003f5:       48 83 c4 08             add    $0x8,%rsp
  4003f9:       c3                      retq

反組譯的部份跟之前一樣, 也可以設定 --start-address 以及 --stop-address

如果 object file 包含 debug information, 加上 -l (--line-numbers) 可以把指令與原始碼行號對應資訊印出來, 但如果 object file 沒有 debug information 就沒有任何效果
$ objdump -d -l hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
main():
/home/william/temp/hello.c:3
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
/home/william/temp/hello.c:4
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
/home/william/temp/hello.c:5
   e:   b8 00 00 00 00          mov    $0x0,%eax
/home/william/temp/hello.c:6
  13:   5d                      pop    %rbp
  14:   c3                      retq


如果加上 -S (--source), 如果找的到原始檔就會顯示對應的原始碼, 但如果 object file 沒有 debug information 就沒有作用

$ objdump -d -S hello.o

hello.o:     file format elf64-x86-64


Disassembly of section .text:

0000000000000000 <main>:
#include <stdio.h>

int main() {
   0:   55                      push   %rbp
   1:   48 89 e5                mov    %rsp,%rbp
        printf("Hello World\n");
   4:   bf 00 00 00 00          mov    $0x0,%edi
   9:   e8 00 00 00 00          callq  e <main+0xe>
        return 0;
   e:   b8 00 00 00 00          mov    $0x0,%eax
}
  13:   5d                      pop    %rbp
  14:   c3                      retq

-S 與 -l 可以同時使用
其中可以注意的是, 在 printf 下面的 address 4, bf 00 00 00 00, 後面的位置都是00, 是因為檔案還沒 reallocate, 在 linking 之後才會嵌入位置
$ objdump -S hello

......
000000000040052d <main>:
  40052d:       55                      push   %rbp
  40052e:       48 89 e5                mov    %rsp,%rbp
  400531:       bf d4 05 40 00          mov    $0x4005d4,%edi
  400536:       e8 d5 fe ff ff          callq  400410 <puts@plt>
  40053b:       b8 00 00 00 00          mov    $0x0,%eax
  400540:       5d                      pop    %rbp
  400541:       c3                      retq
  400542:       66 2e 0f 1f 84 00 00    nopw   %cs:0x0(%rax,%rax,1)
  400549:       00 00 00
  40054c:       0f 1f 40 00             nopl   0x0(%rax)
......

可以看到執行檔裡已經嵌入位置 bf d4 05 40 00



















FreeRTOS Interrupt Management

1. Introduction


關於處理event, 有幾個要考慮的點

  • 要怎麼偵測event? 通常會用 interrupt, 但也可以用polling的方式
  • 如果使用 interrupt, 要在 ISR (Interrupt service routine) 裡面處裡多少事情? 相對地要在ISR外面處理多少事情? 是否 ISR 裡面的事情要愈少愈好?
  • ISR 裡的 code 要如何跟外面的 code 溝通? 尤其是 asynchronous 的code
FreeRTOS 沒有規定 event handle要怎麼實作, 但提供了一些interrupt API
在這些API裡, 只有 FromISR 結尾的 function 才可以在 ISR 裡呼叫, 以及以 FROM_ISR 結尾的 macro 可以在ISR裡使用

2. Binary Semaphores used for Synchronization


整個概念如下圖, 當 interrupt 發生時, ISR 解開 semaphore 並讓 handler task 變成 ready state, 然後 ISR return, 這時 handler task 有最高的 priority 並執行, 當它執行完, 等下一個 semaphore, 進入block state, 並讓低 priority 的 task 執行



在使用 semaphore 時, 分成 take & give, 實作上, 它就像是長度為1的queue, 它一樣要設定 block time, 相關的限制也相同

2.1 Create Semaphore


void vSemaphoreCreateBinary( xSemaphoreHandle xSemaphore );

xSemaphoreHandle 是 semaphore 的型態, 在使用它之前必需先 create

2.2 Take Semaphore


portBASE_TYPE xSemaphoreTake( xSemaphoreHandle xSemaphore, portTickType xTicksToWait );

如果把 xTicksToWait 設成 portMAX_DELAY, 表示要永遠等下去

xSemaphoreTake 不能在 ISR 裡面使用

可能的回傳值 :
  • pdPASS : 成功拿到 semaphore, 如果 block time > 0, 表示在 timeout 前拿到 semaphore
  • pdFALSE : 沒能在 block time timeout 前拿到 semaphore

2.3 Give Semaphore in ISR


portBASE_TYPE xSemaphoreGiveFromISR( xSemaphoreHandle xSemaphore,
                                     portBASE_TYPE *pxHigherPriorityTaskWoken
                                     );

如果有 high priority task 在等這個 semaphore, 然後呼叫 xSemaphoreGiveFromISR 造成目前的 task 被 scheduler switch out, 那麼會把 pxHigherPriorityTaskWoken 這個 pointer 裡面的值設成 pdTRUE, 表示要處裡目前要被 switch out 的 task 的 context switch

可能的回傳值 :

  • pdPASS : 成功
  • pdFALSE : 如果目前的 semaphore 已經被 give 了, 會回傳失敗

2.4 sample code


static void vPeriodicTask( void *pvParameters )
{
    for( ;; )
    {
        /* 等 500ms 準備送出 sofeware interrupt */
        vTaskDelay( 500 / portTICK_RATE_MS );

        /* 送出 interrupt, 前後夾 log */
        vPrintString( "Periodic task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Periodic task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

static void vHandlerTask( void *pvParameters )
{
    /* As per most tasks, this task is implemented within an infinite loop. */
    for( ;; )
    {
        /* take semaphore */
        xSemaphoreTake( xBinarySemaphore, portMAX_DELAY );
        vPrintString( "Handler task - Processing event.\r\n" );
    }
}

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;

 /* 先設成 false, 這樣如果有 task 被switch out時會設成ture, 才能分辨 */
    xHigherPriorityTaskWoken = pdFALSE;

    /* give semaphore */
    xSemaphoreGiveFromISR( xBinarySemaphore, &xHigherPriorityTaskWoken );
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        /* 做 context switch, 其中 portSWITCH_CONTEXT() 是 Open Watcom Dos 的port,
           其它平台可能有不同的 port */
        portSWITCH_CONTEXT();
    }
}

int main( void )
{
    /* 建 semaphore */
    vSemaphoreCreateBinary( xBinarySemaphore );

    /* 設定 interrupt handler */
    _dos_setvect( 0x82, vExampleInterruptHandler );

    if( xBinarySemaphore != NULL )
    {
        /* 建立要take semaphore 的 task, 它的 priority 是 3 */
        xTaskCreate( vHandlerTask, "Handler", 1000, NULL, 3, NULL );

        /* 建立定期發出 software interrupt 的 task */
        xTaskCreate( vPeriodicTask, "Periodic", 1000, NULL, 1, NULL );

        vTaskStartScheduler();
    }
    /* it should never reach here */
    for( ;; );
}

3. Counting semaphores


使用 binary semaphore 的缺點是, 在 ISR unblock binary semaphore之後, 並且讓handle task執行時, 如果這時又再有一個 interrupt, 這時候 handle task也只會做最初的那次, counting semaphore 可以解決這種情況

可以將 counting semaphore 想成是長度為n的queue, 只是我們不在乎裡面的data

通常 counting semaphore 用在兩個用途: 記錄次數, 與管理資源

底下是它的 api

xSemaphoreHandle xSemaphoreCreateCounting( unsigned portBASE_TYPE uxMaxCount,
                                           unsigned portBASE_TYPE uxInitialCount );

其中 uxMaxCount 是最大值, uxInitialCount是初始值, 成功的話回傳 semaphore handle


4. Use queue in ISR


在 ISR 裡使用 queue, 有相對應的 function:

portBASE_TYPE xQueueSendToFrontFromISR( xQueueHandle xQueue,
                                        void *pvItemToQueue
                                        portBASE_TYPE *pxHigherPriorityTaskWoken
                                        );

portBASE_TYPE xQueueSendToBackFromISR( xQueueHandle xQueue,
                                       void *pvItemToQueue
                                       portBASE_TYPE *pxHigherPriorityTaskWoken
                                       );

BaseType_t xQueueReceiveFromISR( QueueHandle_t xQueue,
                                 void *pvBuffer,
                                 BaseType_t *pxHigherPriorityTaskWoken
                                 );

這些其實就是 xQueueSendToFront(), xQueueSendToBack() 以及 xQueueReceive()

4.1 Sample code


static void vIntegerGenerator( void *pvParameters )
{
    portTickType xLastExecutionTime;
    unsigned portLONG ulValueToSend = 0;
    int i;

    /* Initialize the variable used by the call to vTaskDelayUntil(). */
    xLastExecutionTime = xTaskGetTickCount();
    for( ;; )
    {
        /* 每 200ms block */
        vTaskDelayUntil( &xLastExecutionTime, 200 / portTICK_RATE_MS );

        /* 寫入queue 5次, 這些值將從ISR讀出 */
        for( i = 0; i < 5; i++ )
        {
            xQueueSendToBack( xIntegerQueue, &ulValueToSend, 0 );
            ulValueToSend++;
        }
        /* 觸發 software interrupt 0x82 */
        vPrintString( "Generator task - About to generate an interrupt.\r\n" );
        __asm{ int 0x82 } /* This line generates the interrupt. */
        vPrintString( "Generator task - Interrupt generated.\r\n\r\n\r\n" );
    }
}

static void __interrupt __far vExampleInterruptHandler( void )
{
    static portBASE_TYPE xHigherPriorityTaskWoken;
    static unsigned long ulReceivedNumber;

    /* 宣告成 static const 確保它們不是 ISR 產生的 */
    static const char *pcStrings[] = {"St0\r\n", "St1\r\n", "St2\r\n", "St3\r\n"};
    xHigherPriorityTaskWoken = pdFALSE;

    /* Loop until the queue is empty. */
    while( xQueueReceiveFromISR( xIntegerQueue, &ulReceivedNumber, &xHigherPriorityTaskWoken ) != errQUEUE_EMPTY )
    {
        /* Mask最後兩個bit, 然後把對應的String放進queue裡 */
        ulReceivedNumber &= 0x03;
        xQueueSendToBackFromISR( xStringQueue, &pcStrings[ ulReceivedNumber ], &xHigherPriorityTaskWoken );
    }

    /* 如果寫入 queue 造成 high priority task 變成 ready, 就要做context switch */
    if( xHigherPriorityTaskWoken == pdTRUE )
    {
        /* NOTE: 這個context switch 是給 Open Watcom DOS port 使用, 其它平台有不同的port */
        portSWITCH_CONTEXT();
    }
}

static void vStringPrinter( void *pvParameters )
{
    char *pcString;
    for( ;; )
    {
        /* Block on the queue to wait for data to arrive. */
        xQueueReceive( xStringQueue, &pcString, portMAX_DELAY );
        vPrintString( pcString );
    }
}

int main( void )
{
    /* 建queue */
    xIntegerQueue = xQueueCreate( 10, sizeof( unsigned long ) );
    xStringQueue = xQueueCreate( 10, sizeof( char * ) );

    /* 設定 interrupt handler. */
    _dos_setvect( 0x82, vExampleInterruptHandler );

    /* low priority period task */
    xTaskCreate( vIntegerGenerator, "IntGen", 1000, NULL, 1, NULL );

    /* High priority interrupt handler */
    xTaskCreate( vStringPrinter, "String", 1000, NULL, 2, NULL );

    vTaskStartScheduler();
    for( ;; );
}







I2S Introduction

1. Introduction


I2S (Inter-IC Sound), 用於IC之間傳遞數位音源, 它由三條單方向線組成

  1. SCK (Serial Clock): serial transmission的clock line
  2. SD (Serial Data): serial transmission的data line
  3. WS (Word Select): 即 udio channel

在傳輸時, 有一方是Master, 取決於誰輸出SCK

Example 1: 這張圖中, Transmitter 輸出SCK, 所以 Transmitter 是 master


Example 2: 另種情況是, 雖然 receiver 從 tranmitter 的 SD 收到data, 但 receiver 輸出 SCK, 所以 receiver 是 master

Example 3: 有時候設計上會多出一個 controller, 它負責控制 SCK 以及WS, 這種情況下 controller 是 master

有時候在設計上, 為了達到master與slave角色互換, 可以利用修改pin function

在邏輯電壓準位上, 使用 TTL standard, Output Level 在 0V~0.8V為Lo, 2.4~5.0V為Hi

2 line description


2.1 Serial Data


傳輸格式為 two's complement, 並且先傳 MSB (Most Significant Bit)
使用MSB是因為如果 transmitter 與 receiver 兩邊的 word length 不同, 那麼就不需要考慮到調整word length的問題
例如, transmitter word length = 20 bits receiver word length = 24 bits, 那麼 transmitter 傳完之後, receiver 自動補上 4 個 0 就可以
另個例子是transmitter word length = 20 bits receiver word length = 16 bits, 那麼 receiver收完 16 bits 之後, 自動捨棄後面的 4 個 bits 即可


transmitter 在傳 SD 時, 它的 clock signal 在做synchronization可以是 trailing edge (high-to-low) 或者是 leading edge (low-to-high), 不管是哪一種, receiver 在 leading edge 時都得在 SD 上加 latch

2.2 Word Select


WS 輸出 0 表示 Channel 1 (Left), 輸出1表示 Channel 2 (Right)
WS在轉變0或1時, leading edge的SD是WS未轉變之前的資訊, 確定WS轉變後, 下一個leading edge 的 SD才是 WS 指定的 channel info
參考上一張圖, WS 原本值是 1 (Right Channel), 改成0之後, SD的data在1個clock period之間仍然是 Right Channel, 等到下個clock leading edge才變成 Left Channel






2014/10/20

static library and shared library

1. Static library


static library 把許多 object 檔包成同一個檔案, 用 ar 指令打包
在 link 的時候, 會在 library 裡找到對應的 object 副本, 再作 link 的動作
它以 object 檔為單位, link 的時候會放一份副本
也因此如果有多個執行檔都用到同個 static library, 每個執行檔都會有一份副本
使用的記憶體也因為每個執行檔都有一份, 所以也使用較多的記憶體
在更新 static library 時, 也得要將所有用到這份 static library 的執行檔都重新 link

2. Shared library


shared library 透過 mmap 的機制讓多個 process 共享記憶體, 並參考其中內容
一樣是將多個 object 打包, OS 會參考 memory mapping table , 等到實際取用的時候才去讀取資料

因為多個執行檔只要一份, 所以較省空間與記憶體
更新 shared library 也只需要更新一次

製作 shared library:
$ gcc -c foo.c
$ gcc -c bar.c
$ gcc -shared -W1,-soname,libfoo.so.o -o libfoo.so foo.o bar.o
-shared 表示要建立 shared object
-soname 表示 shared object soname, (別名)

使用的方式:
$ gcc -o baz baz.o -lfoo

3. Dependency of shared library


ELF 的 dynamic section 的 NEEDED 裡會儲存檔案用到哪些 shared library, 可以用objdump來看
$ objdump -p /bin/ls

/bin/ls:     file format elf32-littlearm

Program Header:
......

Dynamic Section:
  NEEDED               libselinux.so.1
  NEEDED               librt.so.1
  NEEDED               libacl.so.1
  NEEDED               libgcc_s.so.1
  NEEDED               libc.so.6
  NEEDED               ld-linux-armhf.so.3
  INIT                 0x00009bb8

或是用readelf
$ readelf -d /bin/ls

Dynamic section at offset 0x16ee8 contains 30 entries:
  Tag        Type                         Name/Value
 0x00000001 (NEEDED)                     Shared library: [libselinux.so.1]
 0x00000001 (NEEDED)                     Shared library: [librt.so.1]
 0x00000001 (NEEDED)                     Shared library: [libacl.so.1]
 0x00000001 (NEEDED)                     Shared library: [libgcc_s.so.1]
 0x00000001 (NEEDED)                     Shared library: [libc.so.6]
 0x00000001 (NEEDED)                     Shared library: [ld-linux-armhf.so.3]
 0x0000000c (INIT)                       0x9bb8

NEEDED 記錄的是 soname, 為了要找到 soname 對應的實際的檔案, 底下是可能的找法

  • 它會先從 /usr/lib 找 soname的檔案
  • 如果環境變數 LD_LIBRARY_PAT 有設定, 就會在設定的路徑下找
  • 如果 /etc/ld.so.cache 有記錄相關資訊的話, 也會使用它的內容

一個一個找其實很麻煩, 通常會使用 ldd 來檢查
$ ldd /bin/ls
        /usr/lib/arm-linux-gnueabihf/libcofi_rpi.so (0xb6f10000)
        libselinux.so.1 => /lib/arm-linux-gnueabihf/libselinux.so.1 (0xb6edf000)
        librt.so.1 => /lib/arm-linux-gnueabihf/librt.so.1 (0xb6ed0000)
        libacl.so.1 => /lib/arm-linux-gnueabihf/libacl.so.1 (0xb6ec1000)
        libgcc_s.so.1 => /lib/arm-linux-gnueabihf/libgcc_s.so.1 (0xb6e99000)
        libc.so.6 => /lib/arm-linux-gnueabihf/libc.so.6 (0xb6d69000)
        /lib/ld-linux-armhf.so.3 (0xb6f1e000)
        libdl.so.2 => /lib/arm-linux-gnueabihf/libdl.so.2 (0xb6d5e000)
        libpthread.so.0 => /lib/arm-linux-gnueabihf/libpthread.so.0 (0xb6d3f000)
        libattr.so.1 => /lib/arm-linux-gnueabihf/libattr.so.1 (0xb6d32000)

它會顯示出所有需要的 shared library, soname, 以及實際的位置











2014/10/13

ELF (Executable and Linking Format)

1. Abstract


ELF (Executable and Linking Format), 是 executable binary file 以及 object file 的檔案格式規範

ELF 的檔案開頭會有 (1) ELF header, (2) program header table, (3) section header table

ELF 相關的 define 在 "/usr/include/elf.h", 裡面有分 32-bit 及 64-bit, 以下都以32-bit當例子

2. ELF header


查看 ELF header 的內容, 可以用 readelf
$ readelf -h /bin/ls
ELF Header:
  Magic:   7f 45 4c 46 01 01 01 00 00 00 00 00 00 00 00 00
  Class:                             ELF32
  Data:                              2's complement, little endian
  Version:                           1 (current)
  OS/ABI:                            UNIX - System V
  ABI Version:                       0
  Type:                              EXEC (Executable file)
  Machine:                           ARM
  Version:                           0x1
  Entry point address:               0xc3f0
  Start of program headers:          52 (bytes into file)
  Start of section headers:          95220 (bytes into file)
  Flags:                             0x5000002, has entry point, Version5 EABI
  Size of this header:               52 (bytes)
  Size of program headers:           32 (bytes)
  Number of program headers:         9
  Size of section headers:           40 (bytes)
  Number of section headers:         28
  Section header string table index: 27

其實這只是把 /bin/ls 的 binary 的開頭部份以 Elf32_Ehdr 來 parse
Elf32_Ehdr 在 "/usr/include/elf.h" 的定義為
typedef struct
{
  unsigned char e_ident[EI_NIDENT];     /* Magic number and other info */
  Elf32_Half    e_type;                 /* Object file type */
  Elf32_Half    e_machine;              /* Architecture */
  Elf32_Word    e_version;              /* Object file version */
  Elf32_Addr    e_entry;                /* Entry point virtual address */
  Elf32_Off     e_phoff;                /* Program header table file offset */
  Elf32_Off     e_shoff;                /* Section header table file offset */
  Elf32_Word    e_flags;                /* Processor-specific flags */
  Elf32_Half    e_ehsize;               /* ELF header size in bytes */
  Elf32_Half    e_phentsize;            /* Program header table entry size */
  Elf32_Half    e_phnum;                /* Program header table entry count */
  Elf32_Half    e_shentsize;            /* Section header table entry size */
  Elf32_Half    e_shnum;                /* Section header table entry count */
  Elf32_Half    e_shstrndx;             /* Section header string table index */
} Elf32_Ehdr;

那麼把 /bin/ls 的開頭 dump 出來就可以對應到以上的欄位
$ hexdump -n 64 -C /bin/ls
00000000  7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00  |.ELF............|
00000010  02 00 28 00 01 00 00 00  f0 c3 00 00 34 00 00 00  |..(.........4...|
00000020  f4 73 01 00 02 00 00 05  34 00 20 00 09 00 28 00  |.s......4. ...(.|
00000030  1c 00 1b 00 01 00 00 70  c0 60 01 00 c0 e0 01 00  |.......p.`......|

其中 Elf32_Ehdr 實際對應的值是
e_ident:     7f 45 4c 46 01 01 01 00  00 00 00 00 00 00 00 00
e_type:      02 00       = 0x0002
e_machine:   28 00       = 0x0028
e_version:   01 00 00 00 = 0x00000001
e_entry:     f0 c3 00 00 = 0x0000c3f0
e_phoff:     34 00 00 00 = 0x00000034 = 52 (bytes)
e_shoff:     f4 73 01 00 = 0x000173f4 = 95220 (bytes)
e_flags:     02 00 00 05
e_ehsize:    34 00       = 0x0034     = 52 (bytes)
e_phentsize: 20 00       = 0x0020     = 32 (bytes)
e_phnum:     09 00       = 0x0009
e_shentsize: 28 00       = 0x0028
e_shnum:     1c 00       = 0x001c
e_shstrndx:  1b 00       = 0x001b

其中 e_ident 包含 ELF magic number 和其它資訊, ELF 檔案的前 4 個byte一定是 "0x7f 45 4c 46"

e_type 則是底下幾種:

  • ET_REL (1) : relocatable file
  • ET_EXEC (2) : executable file
  • ET_DYN (3) : shared object file
  • ET_CORE (4) : core file

3. Program Header


program header table的位置從 ELF header 的 "Start of program headers" (e_phoff) 開始
program header 的大小則是由 "Size of program headers" (e_phentsize) 以及 "Number of program headers" (e_phnum) 決定

"/bin/ls" 的例子裡, program header 從offset 52 bytes 開始, header size是 32bytes, 共有 9 個header

用 readelf 來解讀:
$ readelf --program-headers /bin/ls

Elf file type is EXEC (Executable file)
Entry point 0xc3f0
There are 9 program headers, starting at offset 52

Program Headers:
  Type           Offset   VirtAddr   PhysAddr   FileSiz MemSiz  Flg Align
  EXIDX          0x0160c0 0x0001e0c0 0x0001e0c0 0x00028 0x00028 R   0x4
  PHDR           0x000034 0x00008034 0x00008034 0x00120 0x00120 R E 0x4
  INTERP         0x000154 0x00008154 0x00008154 0x00019 0x00019 R   0x1
      [Requesting program interpreter: /lib/ld-linux-armhf.so.3]
  LOAD           0x000000 0x00008000 0x00008000 0x160ec 0x160ec R E 0x8000
  LOAD           0x016edc 0x00026edc 0x00026edc 0x003f8 0x0108c RW  0x8000
  DYNAMIC        0x016ee8 0x00026ee8 0x00026ee8 0x00118 0x00118 RW  0x4
  NOTE           0x000170 0x00008170 0x00008170 0x00044 0x00044 R   0x4
  GNU_STACK      0x000000 0x00000000 0x00000000 0x00000 0x00000 RW  0x4
  GNU_RELRO      0x016edc 0x00026edc 0x00026edc 0x00124 0x00124 R   0x1

 Section to Segment mapping:
  Segment Sections...
   00     .ARM.exidx
   01
   02     .interp
   03     .interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.exidx .eh_frame
   04     .init_array .fini_array .jcr .dynamic .got .data .bss
   05     .dynamic
   06     .note.ABI-tag .note.gnu.build-id
   07
   08     .init_array .fini_array .jcr .dynamic

其中 program header 對應到 "/usr/include/elf.h"
typedef struct
{
  Elf32_Word    p_type;                 /* Segment type */
  Elf32_Off     p_offset;               /* Segment file offset */
  Elf32_Addr    p_vaddr;                /* Segment virtual address */
  Elf32_Addr    p_paddr;                /* Segment physical address */
  Elf32_Word    p_filesz;               /* Segment size in file */
  Elf32_Word    p_memsz;                /* Segment size in memory */
  Elf32_Word    p_flags;                /* Segment flags */
  Elf32_Word    p_align;                /* Segment alignment */
} Elf32_Phdr;

一樣可以用 hexdump 來倒出對應的值, 其中參數 288 = 32 bytes * 9 header, offset 是 52 bytes
$ hexdump -n 288 -s 52 -C /bin/ls
00000034  01 00 00 70 c0 60 01 00  c0 e0 01 00 c0 e0 01 00  |...p.`..........|
00000044  28 00 00 00 28 00 00 00  04 00 00 00 04 00 00 00  |(...(...........|
00000054  06 00 00 00 34 00 00 00  34 80 00 00 34 80 00 00  |....4...4...4...|
00000064  20 01 00 00 20 01 00 00  05 00 00 00 04 00 00 00  | ... ...........|
00000074  03 00 00 00 54 01 00 00  54 81 00 00 54 81 00 00  |....T...T...T...|
00000084  19 00 00 00 19 00 00 00  04 00 00 00 01 00 00 00  |................|
00000094  01 00 00 00 00 00 00 00  00 80 00 00 00 80 00 00  |................|
000000a4  ec 60 01 00 ec 60 01 00  05 00 00 00 00 80 00 00  |.`...`..........|
000000b4  01 00 00 00 dc 6e 01 00  dc 6e 02 00 dc 6e 02 00  |.....n...n...n..|
000000c4  f8 03 00 00 8c 10 00 00  06 00 00 00 00 80 00 00  |................|
000000d4  02 00 00 00 e8 6e 01 00  e8 6e 02 00 e8 6e 02 00  |.....n...n...n..|
000000e4  18 01 00 00 18 01 00 00  06 00 00 00 04 00 00 00  |................|
000000f4  04 00 00 00 70 01 00 00  70 81 00 00 70 81 00 00  |....p...p...p...|
00000104  44 00 00 00 44 00 00 00  04 00 00 00 04 00 00 00  |D...D...........|
00000114  51 e5 74 64 00 00 00 00  00 00 00 00 00 00 00 00  |Q.td............|
00000124  00 00 00 00 00 00 00 00  06 00 00 00 04 00 00 00  |................|
00000134  52 e5 74 64 dc 6e 01 00  dc 6e 02 00 dc 6e 02 00  |R.td.n...n...n..|
00000144  24 01 00 00 24 01 00 00  04 00 00 00 01 00 00 00  |$...$...........|

以第一個 program header 來說, Elf32_Phdr 對應的值是:
p_type:   01 00 00 70 = 0x70000001
p_offset: c0 60 01 00 = 0x000160c0
p_vaddr:  c0 e0 01 00 = 0x0001e0c0
p_paddr:  c0 e0 01 00 = 0x0001e0c0
p_filesz: 28 00 00 00 = 0x00000028
p_memsz:  28 00 00 00 = 0x00000028
p_flags:  04 00 00 00 = 0x00000004
p_align:  04 00 00 00 = 0x00000004

其中 p_type 常見的值:

  • PT_LOAD (1) : 載入的 program section
  • PT_DYNAMIC (2) : 動態連結資訊
  • PT_INTERP (3) : program interpreter
  • PT_NOTE (4) : 輔助資訊
  • PT_PHDR (6) : program header本身
  • PT_TLS (7) : thread local storage

在 readelf 印出來的資料中, "Section to Segment mapping"底下的資料是每個program header對應的segment以及它包含的section list
比如說, 第1個 header 的 p_type 是 EXIDX, 它的 segment 是 index 00, 它包含的section是 ".ARM.exidx"
第4個 header的 p_type 是 LOAD, 它的 segment 是 index 03, 它包含的 section 是 ".interp .note.ABI-tag .note.gnu.build-id .hash .gnu.hash .dynsym .dynstr .gnu.version .gnu.version_r .rel.dyn .rel.plt .init .plt .text .fini .rodata .ARM.exidx .eh_frame"

4. Section header


section header table 從 "Start of section headers" (e_shoff) 的 offset 開始
它的大小為 "Size of section headers" (e_shentsize) 乘以 "Number of section headers" (e_shnum)

"/bin/ls" 的 offset 是 95220 bytes, 大小為 40 bytes * 28 items = 1120 bytes

用 readelf 解讀:
$ readelf --section-headers /bin/ls
There are 28 section headers, starting at offset 0x173f4:

Section Headers:
  [Nr] Name              Type            Addr     Off    Size   ES Flg Lk Inf Al
  [ 0]                   NULL            00000000 000000 000000 00      0   0  0
  [ 1] .interp           PROGBITS        00008154 000154 000019 00   A  0   0  1
  [ 2] .note.ABI-tag     NOTE            00008170 000170 000020 00   A  0   0  4
  [ 3] .note.gnu.build-i NOTE            00008190 000190 000024 00   A  0   0  4
  [ 4] .hash             HASH            000081b4 0001b4 000380 04   A  6   0  4
  [ 5] .gnu.hash         GNU_HASH        00008534 000534 0003f8 04   A  6   0  4
  [ 6] .dynsym           DYNSYM          0000892c 00092c 0007d0 10   A  7   1  4
  [ 7] .dynstr           STRTAB          000090fc 0010fc 000592 00   A  0   0  1
  [ 8] .gnu.version      VERSYM          0000968e 00168e 0000fa 02   A  6   0  2
  [ 9] .gnu.version_r    VERNEED         00009788 001788 0000b0 00   A  7   5  4
  [10] .rel.dyn          REL             00009838 001838 000030 08   A  6   0  4
  [11] .rel.plt          REL             00009868 001868 000350 08   A  6  13  4
  [12] .init             PROGBITS        00009bb8 001bb8 00000c 00  AX  0   0  4
  [13] .plt              PROGBITS        00009bc4 001bc4 00050c 04  AX  0   0  4
  [14] .text             PROGBITS        0000a0d0 0020d0 010bd0 00  AX  0   0  8
  [15] .fini             PROGBITS        0001aca0 012ca0 000008 00  AX  0   0  4
  [16] .rodata           PROGBITS        0001aca8 012ca8 003418 00   A  0   0  4
  [17] .ARM.exidx        ARM_EXIDX       0001e0c0 0160c0 000028 00  AL 14   0  4
  [18] .eh_frame         PROGBITS        0001e0e8 0160e8 000004 00   A  0   0  4
  [19] .init_array       INIT_ARRAY      00026edc 016edc 000004 00  WA  0   0  4
  [20] .fini_array       FINI_ARRAY      00026ee0 016ee0 000004 00  WA  0   0  4
  [21] .jcr              PROGBITS        00026ee4 016ee4 000004 00  WA  0   0  4
  [22] .dynamic          DYNAMIC         00026ee8 016ee8 000118 08  WA  7   0  4
  [23] .got              PROGBITS        00027000 017000 0001bc 04  WA  0   0  4
  [24] .data             PROGBITS        000271c0 0171c0 000114 00  WA  0   0  8
  [25] .bss              NOBITS          000272d8 0172d4 000c90 00  WA  0   0  8
  [26] .ARM.attributes   ARM_ATTRIBUTES  00000000 0172d4 00002f 00      0   0  1
  [27] .shstrtab         STRTAB          00000000 017303 0000f1 00      0   0  1
Key to Flags:
  W (write), A (alloc), X (execute), M (merge), S (strings)
  I (info), L (link order), G (group), T (TLS), E (exclude), x (unknown)
  O (extra OS processing required) o (OS specific), p (processor specific)

而 section header 的定義為:
typedef struct
{
  Elf32_Word    sh_name;                /* Section name (string tbl index) */
  Elf32_Word    sh_type;                /* Section type */
  Elf32_Word    sh_flags;               /* Section flags */
  Elf32_Addr    sh_addr;                /* Section virtual addr at execution */
  Elf32_Off     sh_offset;              /* Section file offset */
  Elf32_Word    sh_size;                /* Section size in bytes */
  Elf32_Word    sh_link;                /* Link to another section */
  Elf32_Word    sh_info;                /* Additional section information */
  Elf32_Word    sh_addralign;           /* Section alignment */
  Elf32_Word    sh_entsize;             /* Entry size if section holds table */
} Elf32_Shdr;


常見的 section type:

  • SHT_PROGBITS (1) : 程式資料
  • SHT_SYMTAB (2) : 符號表
  • SHT_STRTAB (3) : 字串表
  • SHT_RELA (4) : relocation addition item
  • SHT_HASH (5) : symbol hash table
  • SHT_DYNAMIC (6) : 動態連結資訊
  • SHT_NOTE (7)
  • SHT_NOBITS (8) : 檔案裡不是資料的部份 (.bss)
  • SHT_REL (9) : relocatable item
  • SHT_DYNSYM (11) : dynamic linker 所使用的 symbol table
  • SHT_INIT_ARRAY (14) : constructor array (.init)
  • SHT_FINI_ARRAY (15) : destructor array (.fini)

在 elf header 裡, e_shstrndx 標明字串表的 section index, 這個例子裡 "Section header string table index" (e_shstrndx) 是 27, section header 的 name (sh_name) 是從這個 section 對應而來

在index 27, 它的 offset = 0x017303, size = 0xf1 (241), 用 hexdump把它印出來
$ hexdump -n 241 -s 0x017303 -C /bin/ls
00017303  00 2e 73 68 73 74 72 74  61 62 00 2e 69 6e 74 65  |..shstrtab..inte|
00017313  72 70 00 2e 6e 6f 74 65  2e 41 42 49 2d 74 61 67  |rp..note.ABI-tag|
00017323  00 2e 6e 6f 74 65 2e 67  6e 75 2e 62 75 69 6c 64  |..note.gnu.build|
00017333  2d 69 64 00 2e 67 6e 75  2e 68 61 73 68 00 2e 64  |-id..gnu.hash..d|
00017343  79 6e 73 79 6d 00 2e 64  79 6e 73 74 72 00 2e 67  |ynsym..dynstr..g|
00017353  6e 75 2e 76 65 72 73 69  6f 6e 00 2e 67 6e 75 2e  |nu.version..gnu.|
00017363  76 65 72 73 69 6f 6e 5f  72 00 2e 72 65 6c 2e 64  |version_r..rel.d|
00017373  79 6e 00 2e 72 65 6c 2e  70 6c 74 00 2e 69 6e 69  |yn..rel.plt..ini|
00017383  74 00 2e 74 65 78 74 00  2e 66 69 6e 69 00 2e 72  |t..text..fini..r|
00017393  6f 64 61 74 61 00 2e 41  52 4d 2e 65 78 69 64 78  |odata..ARM.exidx|
000173a3  00 2e 65 68 5f 66 72 61  6d 65 00 2e 69 6e 69 74  |..eh_frame..init|
000173b3  5f 61 72 72 61 79 00 2e  66 69 6e 69 5f 61 72 72  |_array..fini_arr|
000173c3  61 79 00 2e 6a 63 72 00  2e 64 79 6e 61 6d 69 63  |ay..jcr..dynamic|
000173d3  00 2e 67 6f 74 00 2e 64  61 74 61 00 2e 62 73 73  |..got..data..bss|
000173e3  00 2e 41 52 4d 2e 61 74  74 72 69 62 75 74 65 73  |..ARM.attributes|

也可以用 readelf印出來
$ readelf -x27 /bin/ls

Hex dump of section '.shstrtab':
  0x00000000 002e7368 73747274 6162002e 696e7465 ..shstrtab..inte
  0x00000010 7270002e 6e6f7465 2e414249 2d746167 rp..note.ABI-tag
  0x00000020 002e6e6f 74652e67 6e752e62 75696c64 ..note.gnu.build
  0x00000030 2d696400 2e676e75 2e686173 68002e64 -id..gnu.hash..d
  0x00000040 796e7379 6d002e64 796e7374 72002e67 ynsym..dynstr..g
  0x00000050 6e752e76 65727369 6f6e002e 676e752e nu.version..gnu.
  0x00000060 76657273 696f6e5f 72002e72 656c2e64 version_r..rel.d
  0x00000070 796e002e 72656c2e 706c7400 2e696e69 yn..rel.plt..ini
  0x00000080 74002e74 65787400 2e66696e 69002e72 t..text..fini..r
  0x00000090 6f646174 61002e41 524d2e65 78696478 odata..ARM.exidx
  0x000000a0 002e6568 5f667261 6d65002e 696e6974 ..eh_frame..init
  0x000000b0 5f617272 6179002e 66696e69 5f617272 _array..fini_arr
  0x000000c0 6179002e 6a637200 2e64796e 616d6963 ay..jcr..dynamic
  0x000000d0 002e676f 74002e64 61746100 2e627373 ..got..data..bss
  0x000000e0 002e4152 4d2e6174 74726962 75746573 ..ARM.attributes
  0x000000f0 00                                  .

section header 的 sh_name 是字串表的 offset, 比如說 sh_name=1的話, name就是".shstrtab", 後面有字串結尾'\0', 如果 sh_name=0x0b的話, name 就是 ".interp"

5. Symbol table


如果檔案有strip過, 那麼只會看到 dynamic symbol table
"/bin/ls" 的 section header index 06 它的 type 是 DYNSYM, 所以 dynamic symbol table存在這
也可以先用 readelf來解讀
$ readelf --syms /bin/ls

Symbol table '.dynsym' contains 125 entries:
   Num:    Value  Size Type    Bind   Vis      Ndx Name
     0: 00000000     0 NOTYPE  LOCAL  DEFAULT  UND
     1: 00000000     0 FUNC    GLOBAL DEFAULT  UND __aeabi_unwind_cpp_pr0@GCC_3.5 (2)
     2: 00000000     0 NOTYPE  WEAK   DEFAULT  UND __gmon_start__
     3: 00000000     0 NOTYPE  WEAK   DEFAULT  UND _Jv_RegisterClasses
     4: 00009c08     0 FUNC    GLOBAL DEFAULT  UND getpwnam@GLIBC_2.4 (3)
......

如果要用hexdump的話, section header index 06 的 offset是0x92c, 大小是0x7d0
$ hexdump -n 32 -s 0x92c -C /bin/ls
0000092c  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
0000093c  cd 00 00 00 00 00 00 00  00 00 00 00 12 00 00 00  |................|

而symbol item 的 struct :
typedef struct
{
  Elf32_Word    st_name;                /* Symbol name (string tbl index) */
  Elf32_Addr    st_value;               /* Symbol value */
  Elf32_Word    st_size;                /* Symbol size */
  unsigned char st_info;                /* Symbol type and binding */
  unsigned char st_other;               /* Symbol visibility */
  Elf32_Section st_shndx;               /* Section index */
} Elf32_Sym;

struct大小為16 bytes, 以第2個item來說, 它對應的值是
st_name:  cd 00 00 00 = 0x000000cd
st_value: 00 00 00 00
st_size:  00 00 00 00
st_info:  12          = STB_GLOBAL | STT_FUNC
st_other: 00
st_shndx: 00 00

st_name 雖然也是參考 string table, 但它參考的是 dynamic string table, 也就是 section header index 07 的表

st_info 分成高位 4 bit 及低位 4 bit
高位4 bit為:

  • STB_LOCAL (0) : local symbol
  • STB_GLOBAL (1) : global symbol
  • STB_WEAK (2) : weak linking symbol
低位4 bit為:

  • STT_OBJECT (1) : data object
  • STT_FUNC (2) : function
  • STT_SECTION (3) : section
  • STT_FILE (4) : source file name which related to object
  • STT_COMMON (5) : share data
  • STT_TLS (6) : thread local data






2014/10/07

libllcp in nfc-tools

1. nfc-tools

nfc-tools 開發 nfc 相關的 tool, 最有名的應該是libnfc
底下是 nfc-tools 的網頁
https://code.google.com/p/nfc-tools/

但是libnfc還是太底層, 為了實作 NDEF 或 NPP, 可能還要裝 libfreefare 或 libllcp

libfreefare 是專門用在 Mifare
https://code.google.com/p/libfreefare/

libllcp 則是一般性的NDEF communication
https://code.google.com/p/libllcp/
關於怎麼install:
http://nfc-tools.org/index.php?title=Libllcp

LLCP (Logical Link Control Protocol), 有點像是 IP (Internet Protocol)
它可以經由建立連線之後, 由兩端傳data
而 LLCP 又實作了兩種 protocol
一個是 NPP (NDEF Push Protocol), 就像是 UDP 一樣, 丟封包出去就不用等
另個是 SNEP (Simple NDEF Exchange Protocol), 就像是 TCP, 丟封包出去要等ACK, (但沒有三方交握)

2. NPP sample code

在 libllcp/examples/npp-server/ 有個npp server的範例
它的用途是建立連線之後, 接收從tag來的資料

但是裡面似乎有些bug, 所以我將它重寫並測試過, sample code如下:
(錯的部份是, header 檔像 llcp.h 應該在 nfc/llcp.h底下, 然後stdout的部份會強制導到file)

#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <nfc/llcp.h>
#include <nfc/llc_service.h>
#include <nfc/llc_link.h>
#include <nfc/llc_connection.h>
#include <nfc/mac.h>

#define errx(code, ...) do { \
    fprintf (stderr, __VA_ARGS__); \
    fprintf (stderr, "\n"); \
    exit (code); \
  } while (0)

struct mac_link *mac_link;
nfc_device *device;

static void stop_mac_link(int sig) {
 if (mac_link && mac_link->device) {
  nfc_abort_command(mac_link->device);
 }
}

static void bye(VOID) {
 if (device) {
  nfc_close(device);
 }
}

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void* com_android_npp_thread(void *arg) {
 struct llc_connection *connection = (struct llc_connection *)arg;
 uint8_t buffer[1024];

 int len;
 size_t n = 0;

 len = llc_connection_recv(connection, buffer, sizeof(buffer), NULL);
 if (len < 0) return NULL;
 if (len < 10) return NULL; // NPP's header (5 bytes) + NDEF entry header (5 bytes)

 // byte 0 : protocol version
 printf("NDEF Push Protocol version: %02x\n", buffer[n]);
 if (buffer[n] != 0x01) return NULL; // version not supported
 n++;

 // byte 1~4 : number of NDEF entries
 uint32_t ndef_entries_count = be32toh(*((uint32_t *)(buffer+n)));
 printf("NDEF entries count: %u\n", ndef_entries_count);
 if (ndef_entries_count != 1) return NULL; // Version 0x01 has value 0x00 0x00 0x00 0x01
 n += 4;

 // NDEF Entry
 printf("Action code: %02x\n", buffer[n]);
 if (buffer[n] != 0x01) return NULL; // Action code not supported
 n++;

 uint32_t ndef_length = be32toh(*((uint32_t *)(buffer+n)));
 n += 4;

 if ((len-n) < ndef_length) return NULL; // less received bytes than expected?

 printf("NDEF entry received (%u bytes): ", ndef_length);
 print_hex(buffer + n, ndef_length);

 llc_connection_stop(connection);

 return NULL;
}

int main(int argc, char *argv[]) {

 int ret;

 if (llcp_init() < 0) errx(EXIT_FAILURE, "llcp_init()");

 signal(SIGINT, stop_mac_link);
 atexit(bye);

 nfc_context *context;
 nfc_init(&context);
 if (context == NULL) errx(EXIT_FAILURE, "nfc_init()");

 device = nfc_open(context, NULL);
 if (device == NULL) errx(EXIT_FAILURE, "Cannot connect to NFC device");

 struct llc_link *llc_link = llc_link_new();
 if (llc_link == NULL) errx(EXIT_FAILURE, "Cannot allocate LLC link data structures");

 struct llc_service *com_android_npp 
  = llc_service_new_with_uri(NULL, com_android_npp_thread, "com.android.npp", NULL);
 if (com_android_npp == NULL) errx(EXIT_FAILURE, "cannot create com.android.npp service");

 llc_service_set_miu(com_android_npp, 512);
 llc_service_set_rw(com_android_npp, 2);

 ret = llc_link_service_bind(llc_link, com_android_npp, -1);
 if (ret < 0) errx(EXIT_FAILURE, "Cannot bind service");

 mac_link = mac_link_new(device, llc_link);
 if (mac_link == NULL) errx(EXIT_FAILURE, "Cannot create MAC link");

 ret = mac_link_activate_as_target(mac_link);
 if (ret < 0) errx(EXIT_FAILURE, "Cannot create MAC link");

 void *err;
 mac_link_wait(mac_link, &err);

 mac_link_free(mac_link);
 llc_link_free(llc_link);

 nfc_close(device);
 device = NULL;

 llcp_fini();
 nfc_exit(context);
 exit(EXIT_SUCCESS);
}

然後將它compile, 其中 llcp 的部份官網寫 -lnfc-llcp是錯的
 $ gcc -o npp_server_example npp_server_example.c -lnfc -lllcp

有些手機仍舊可以接受NPP
我手上有台 HTC ONE, 將它的瀏覽器打開, 把手機放在 reader 上面
然後執行剛剛的程式, 手機會感應到, 手機螢幕顯示點選以傳送資料, 點它, 程式就會收到data, 最後將手機移開reader, 程式就結束

$ ./npp_server_example
NDEF Push Protocol version: 01
NDEF entries count: 1
Action code: 01
NDEF entry received (23 bytes): d1 01 13 55 04 74 77 2e 6d 6f 62 69 2e 79 61 68 6f 6f 2e 63 6f 6d 2f

其中
74 77 2e 6d 6f 62 69 2e 79 61 68 6f 6f 2e 63 6f 6d 2f = tw.mobi.yahoo.com/
(TODO: 前面header應該可以parse)

3. npp server code analysis


llcp_init() 是使用llcp要用的初始化, 裡面設定signal handler以及log function,
llcp_fini() 則是結束時釋放資源
int llcp_init(void)
int llcp_fini(void)

nfc context 以及 nfc device 的用法和 libnfc 相同

llc_link 用於紀錄 llc link 相關資訊
struct llc_link {
  uint8_t role;
  enum {
    LL_ACTIVATED,
    LL_DEACTIVATED,
  } status;
  struct llcp_version version;
  uint16_t local_miu;
  uint16_t remote_miu;
  uint16_t remote_wks;
  struct timeval local_lto;
  struct timeval remote_lto;
  uint8_t local_lsc;
  uint8_t remote_lsc;
  uint8_t opt;

  pthread_t thread;
  char *mq_up_name;
  char *mq_down_name;
  mqd_t llc_up;
  mqd_t llc_down;

  struct llc_service *available_services[MAX_LLC_LINK_SERVICE + 1];
  struct llc_connection *datagram_handlers[MAX_LOGICAL_DATA_LINK];
  struct llc_connection *transmission_handlers[MAX_LLC_LINK_SERVICE + 1];

  /* Unit tests metadata */
  void *cut_test_context;
  struct mac_link *mac_link;
};

用 llc_link_new() 來設定 llc link 的 default 值
struct llc_link* llc_link_new(void)


llc_service 用來建 LLC(logical link control) 會用到的相關設定
struct llc_service {
  char *uri;
  void *(*accept_routine)(void *);
  void *(*thread_routine)(void *);
  int8_t sap;
  uint8_t rw;
  uint16_t miu;
  void *user_data;
};

llc_service_new_with_uri() 則是設定 llc_service 成default
struct llc_service* llc_service_new_with_uri(void * (*accept_routine)(void *),
                                             void * (*thread_routine)(void *),
                                             const char *uri,
                                             void *user_data)
accept_routine 是 thread function, 在建立連線時(收到PDU_CONNECT) 呼叫
thread_routine 也是 thread function, 在收到 PDU_UI 時呼叫
uri 應該只是 llc service identify
user_data 可以送 data, 這個例子裡沒用到

有了相關設定之後, 可以bind service
int llc_link_service_bind(struct llc_link *link, struct llc_service *service, int8_t sap)


接著是 mac layer 相關設定
struct mac_link {
  enum { MAC_LINK_UNSET, MAC_LINK_INITIATOR, MAC_LINK_TARGET } mode;
  nfc_device *device;
  struct llc_link *llc_link;
  uint8_t nfcid[10];
  uint8_t buffer[BUFSIZ];
  size_t buffer_size;
  pthread_t *__restrict__ exchange_pdus_thread;
};

一樣有設定default值的function
struct mac_link* mac_link_new(nfc_device *device, struct llc_link *llc_link)

然後enable mac layer setting
int mac_link_activate_as_target(struct mac_link *mac_link)
裡面做的事是用 libnfc 選 target tag
其中 mac_link_run() 開了一條thread 用於傳送接收apdu
預設是
void* mac_link_exchange_pdus(void *arg)

最後用這個 function 等 mac_link_exchange_pdus 這條 thread 結束
int mac_link_wait(struct mac_link *link, void **value_ptr)

4. snep sample code

我一樣改寫了 libllcp 的 libllcp/examples/snep-server/

#include <err.h>
#include <signal.h>
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>

#include <nfc/llcp.h>
#include <nfc/llc_service.h>
#include <nfc/llc_link.h>
#include <nfc/llc_connection.h>
#include <nfc/mac.h>

#define LOG_DGB 1

#define errx(code, ...) do { \
    fprintf (stderr, __VA_ARGS__); \
    fprintf (stderr, "\n"); \
    exit (code); \
  } while (0)

struct mac_link *mac_link;
nfc_device *device;

static void stop_mac_link(int sig) {
 if (mac_link && mac_link->device) {
  nfc_abort_command(mac_link->device);
 }
}

static void bye(VOID) {
 if (device) {
  nfc_close(device);
 }
}

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void* com_android_snep_thread(void *arg) {
 struct llc_connection *connection = (struct llc_connection *) arg;
 uint8_t buffer[1024];
 uint8_t frame_snep_response_success[] = {
  0x10, 0x81, 0x00, 0x00, 0x00, 0x00
 };

 int len;
 len = llc_connection_recv(connection, buffer, sizeof(buffer), NULL);
 if (len < 0 || len < 7) {
  // SNEP's header (2 bytes) + NDEF entry header (5 bytes)
printf("ERR: invalid length\n"); return NULL; } size_t n = 0; printf("SNEP version: %d.%d\n", buffer[n]>>4, buffer[n] & 0x0f ); if (buffer[n] != 0x10) { printf("WRN: snep version other than version 1.0 may not support.\n"); } n++; uint8_t action_code = buffer[n]; n++; switch(action_code) { case 0x00: // Continue break; case 0x01: // GET break; case 0x02: // PUT { uint32_t ndef_length = be32toh(*((uint32_t *)(buffer + n))); if ((len - 6) < ndef_length) { printf("ERR: less received bytes than expected?\n"); return NULL; } n+=4; /** return snep success response package */ llc_connection_send(connection, frame_snep_response_success, sizeof(frame_snep_response_success)); print_hex(buffer + n, ndef_length); break; } default: printf("unknown action code\n"); break; } llc_connection_stop(connection); return NULL; } int main(int argc, char *argv[]) { int ret; if (llcp_init() < 0) errx(EXIT_FAILURE, "llcp_init()"); signal(SIGINT, stop_mac_link); atexit(bye); nfc_context *context; nfc_init(&context); if (context == NULL) errx(EXIT_FAILURE, "nfc_init()"); device = nfc_open(context, NULL); if (device == NULL) errx(EXIT_FAILURE, "Cannot connect to NFC device"); struct llc_link *llc_link = llc_link_new(); if (llc_link == NULL) errx(EXIT_FAILURE, "Cannot allocate LLC link data structures"); struct llc_service *com_android_snep = llc_service_new_with_uri(NULL, com_android_snep_thread, "urn:nfc:sn:snep", NULL); if (com_android_snep == NULL) errx(EXIT_FAILURE, "cannot create com.android.npp service"); llc_service_set_miu(com_android_snep, 512); llc_service_set_rw(com_android_snep, 2); ret = llc_link_service_bind(llc_link, com_android_snep, LLCP_SNEP_SAP); if (ret < 0) errx(EXIT_FAILURE, "Cannot bind service"); mac_link = mac_link_new(device, llc_link); if (mac_link == NULL) errx(EXIT_FAILURE, "Cannot create MAC link"); ret = mac_link_activate_as_target(mac_link); if (ret < 0) errx(EXIT_FAILURE, "Cannot create MAC link"); void *err; mac_link_wait(mac_link, &err); mac_link_free(mac_link); llc_link_free(llc_link); nfc_close(device); device = NULL; llcp_fini(); nfc_exit(context); exit(EXIT_SUCCESS); }


compile 之後, 實驗方式跟 npp server 一樣

5. code analysis


整份 code 跟 npp server 很像, 但有些小差異
在 llc_link_service_bind(), 後面帶的 sap 是 LLCP_SNEP_SAP

另個差異是 header 不一樣

然後在收到data之後, 要回傳OK packet

其它的就沒有差異了




2014/10/06

NFC ISO14443A

1. ISO14443

ISO14443 定義描寫了近距離接觸(<10cm)的卡片, 運作在13.56 MHz, 以及card & reader之間的溝通方式, 分成 ISO14443A 及 ISO14443B, 其中 ISO14443A 最為普及

2. ISO14443A

在 NFC forum 定義的 4 種NFC tag type裡, ISO14443A佔了 type1, type2, type4, 還有Mifare classic tag雖然不在NFC forum定義裡, 但也實作了 ISO14443A

在辨識 tag 的過程中, anti-collision 可以得到辨識 tag 所需的資訊, 包括 ATQA, UID, SAK, ATS

  • ATQA : Answer To Request acc. to ISO/IEC 14443-4
  • UID : Unique Identifier, Type A
  • SAK : Select Acknowledge, Type A
  • ATS : Answer To Select acc. to ISO/IEC 14443-4
相關文件可以參考identify Mifare tags
http://www.nxp.com/documents/application_note/AN10833.pdf

3. anti-collision

anti-collision 可以實作在 firmware, 也可以實作在 userspace, 但是中間涉及到傳送7 bit data, 大多數的 driver 不允許傳送非8-bit單位的data, 所以如果沒有對應的SDK就無法實作在userspace

下圖是anti-collision流程圖

(1) 第一步由 reader 往外送 REQA(0x26) 或 WUPA(0x52), 這兩個訊號都是 7-bit, 這是為了讓這兩個 command 可以與其它 command 做區隔, 也因為這兩個 command 都是 7-bit, 所以要將 CRC 的位元檢查關掉

REQA 作用是邀請在整個區域裡新的 tag 來回應這個 command, 整個區域裡也有舊的 tag, 指的是曾經回應過或已經被關掉的tag

WUPA 則是邀請並喚醒區域裡所有tag, 包括回應過或關掉的tag

之所以有新舊的差別, 是因為只要是卡片未離開 reader 的區域, 就會被持續供電

如果沒有任何 tag 回應, 那麼 reader 會持續發出 REQA/WUPA, 中間的間隔雖然有被定義, 但各家廠商作法不同, 有的等上1s, 有的則是等幾個milliseconds,

(2) 接著 tag 回應 ATQA, 此時 reader 開始 anti-collision 的流程, 區分每張卡片的方式是使用 tag的 UID, UID的長度可能是4 bytes, 7 bytes, 或 10 bytes

(3) reader 接著發出 SELECT

(4) tag 回應 UID

(5) reader 發出 SELECT 並帶 UID, 成功的話, tag 會回應 SAK

底下是 libnfc 的 sample flow
http://nfc-tools.org/index.php?title=Libnfc:nfc-anticol

R: 26                                    => Welcome (REQA) (or use WUPA = 0x52)
T: 44  03                                => Respond (ATQA)
R: 93  20                                => Select cascade 1 (SEL)
T: 88  04  34  74  cc                    => CT, UID(byte 1,2,3), BCC
R: 93  70  88  04  34  74  cc  0e  05    => Select available tag (SEL)
T: 24  d8  36                            => Select Acknowledge (SAK) with cascade bit set
R: 95  20                                => Select cascade 2 (SEL)    
T: e1  e3  1c  80  9e                    => UID(byte 4,5,6,7), BCC
R: 95  70  e1  e3  1c  80  9e  b9  e1    => Finish select (SEL)
T: 20  fc  70                            => SAK without cascade bit set
R: e0  50  bc  a5                        => Request Answer to Select (RATS)
T: 06  75  77  81  02  80                => ATS (DESFire EV1)
R: 50  00  57  cd                        => Disable (HALT)

Found tag with UID: 043474e1e31c80

CT  => [Cascade Tag] byte (88), signals that the UID is not complete yet
BCC => [Bit Count Check] byte, calculated as exclusive-or over the four previous bytes

整個流成跑完之後, 其中 ATQA, SAK, ATS 包含廠商資訊
http://nfc-tools.org/index.php?title=ISO14443A







libnfc example

1. Brief Introduction


在使用libnfc相關的code, 第一步一定要先呼叫 nfc_init(), 它會做初始設定和配置相關資源
void nfc_init(nfc_context **context)


接著呼叫 nfc_open(), 它會找第一個可用的 nfc device
nfc_device* nfc_open(nfc_context *context, const nfc_connstring connstring)


參數 context 就是在 nfc_init() 用到的那個
參數 connstring 表示你想找有特定名字的device, 像"usb"之類的, 如果沒有特定就帶NULL

它會依照下列順序找:
(1) 環境變數裡預設值
(2) libnfc.conf 設定的值
(3) /etc/nfc/devices.d 裡找到
(4) auto-detected 第一個找到的值

找到之後, 它會宣告目前它在使用這個device, 並回傳device pointer


然後記得對這個 device 做初始化
int nfc_initiator_init(nfc_device *pnd)


struct nfc_modulation 表示不同ISO規格的資料
typedef struct {
  nfc_modulation_type nmt;
  nfc_baud_rate nbr;
} nfc_modulation;
其中 nfc_modulation_type為ISO標準
typedef enum {
  NMT_ISO14443A = 1,
  NMT_JEWEL,
  NMT_ISO14443B,
  NMT_ISO14443BI, // pre-ISO14443B aka ISO/IEC 14443 B' or Type B'
  NMT_ISO14443B2SR, // ISO14443-2B ST SRx
  NMT_ISO14443B2CT, // ISO14443-2B ASK CTx
  NMT_FELICA,
  NMT_DEP,
} nfc_modulation_type;
然後 nfc_baud_rate 為 tag 的 baud rate, (每種規格有不同的baud rate)
typedef enum {
  NBR_UNDEFINED = 0,
  NBR_106,
  NBR_212,
  NBR_424,
  NBR_847,
} nfc_baud_rate;
nfc_modulation 主要用在描述現在想嘗試找什麼規格的 tag


struct nfc_target 則是用來儲存找到的 tag
typedef struct {
  nfc_target_info nti;
  nfc_modulation nm;
} nfc_target;
其中 nfc_modulation 只是紀錄它的ISO規格
union nfc_target_info 則是儲存找到的這張 tag 的內容
typedef union {
  nfc_iso14443a_info nai;
  nfc_felica_info nfi;
  nfc_iso14443b_info nbi;
  nfc_iso14443bi_info nii;
  nfc_iso14443b2sr_info nsi;
  nfc_iso14443b2ct_info nci;
  nfc_jewel_info nji;
  nfc_dep_info ndi;
} nfc_target_info;
不同的 ISO 規格, 它能提供的資訊也不同


我們有nfc device, nfc modulation, 就可以找 nfc target, 用底下這個function
int nfc_initiator_select_passive_target(nfc_device *pnd,
                                        const nfc_modulation nm,
                                        const uint8_t *pbtInitData,
                                        const size_t szInitData,
                                        nfc_target *pnt)
其中 pnd 是 nfc device
nm 是  nfc modulation
pbtInitData 可以帶 NULL, 有些 ISO 規格裡可以帶data給tag, 比如說ISO14443A裡, 可以帶 UID, 而 ISO14443B 可以帶 Application Family Identifier (AFI)
szInitData 則是前一項 pbtInitData的size
pnt 則是存找到的nfc target


最後記得要關device
void nfc_close(nfc_device *pnd)

以及要記得釋放nfc context資源
void nfc_exit(nfc_context *context)

2. Sample code


這份sample code從libnfc官方網頁上可以找到, 這是原始網頁
http://nfc-tools.org/index.php?title=Libnfc:quick_start_example

它可以找 ISO14443-A tag, (可能是最常見的tag)

#include <stdlib.h>
#include <nfc/nfc.h>

static void
print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;

 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

int
main(int argc, const char *argv[]) {
 nfc_device *pnd;
 nfc_target nt;
 nfc_context *context;

 nfc_init(&context);
 if (context == NULL) {
  printf("Unable to init libnfc (malloc)\n");
  exit(EXIT_FAILURE);
 }

 pnd = nfc_open(context, NULL);
 if (pnd == NULL) {
  printf("ERROR: %s\n", "Unable to open NFC device.");
  exit(EXIT_FAILURE);
 }

 if (nfc_initiator_init(pnd) < 0) {
  nfc_perror(pnd, "nfc_initiator_init");
  exit(EXIT_FAILURE);
 }

 printf("NFC reader: %s opened\n", nfc_device_get_name(pnd));

 const nfc_modulation nmMifare = {
  .nmt = NMT_ISO14443A,
  .nbr = NBR_106,
 };
 if (nfc_initiator_select_passive_target(pnd, nmMifare, NULL, 0, &nt) > 0) {
  printf("The following (NFC) ISO14443A tag was found:\n");
  printf("    ATQA (SENS_RES): ");
  print_hex(nt.nti.nai.abtAtqa, 2);
  printf("       UID (NFCID%c): ", (nt.nti.nai.abtUid[0] == 0x08 ? '3' : '1'));
  print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen);
  printf("      SAK (SEL_RES): ");
  print_hex(&nt.nti.nai.btSak, 1);
  if (nt.nti.nai.szAtsLen) {
   printf("          ATS (ATR): ");
   print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen);
  }
 }

 nfc_close(pnd);

 nfc_exit(context);
 exit(EXIT_SUCCESS);
}

然後 compile它
$ gcc -o quick_start_example1 quick_start_example1.c -lnfc

執行結果
$ ./quick_start_example1
NFC reader: pn532_spi:/dev/spidev0.0 opened
The following (NFC) ISO14443A tag was found:
    ATQA (SENS_RES): 00 04
       UID (NFCID1): 45 d1 a9 51
      SAK (SEL_RES): 08

3. Polling


前個例子是一執行馬上就掃當下讀的到的卡
也可以改成用 poll 的方式, 在一定的時間內讀到卡即可

int nfc_initiator_poll_target(nfc_device *pnd,
                              const nfc_modulation *pnmModulations,
                              const size_t szModulations,
                              const uint8_t uiPollNr,
                              const uint8_t uiPeriod,
                              nfc_target *pnt)
其中, pnd 是 nfc device, pnt 是 nfc target
pnmModulations 是 nfc_modulation array, 裡面放想掃的 modulation type
szModulations 是 pnmModulations 的 array 長度
uiPollNr 指要 poll 幾次, 可以 1~254 次, 如果是 0xFF 表示要永遠 poll
uiPeriod 表是每次 poll 間隔幾個單位, 1個單位內定是150ms, 2個單位就是300ms, 以此類推
回傳值是 poll 掃到的 nfc target 數目, <0 表示error, =0表示沒掃到

可以 poll 卡是不是接近之後, 也可以檢查卡是不是已經拿開
int nfc_initiator_target_is_present(nfc_device *pnd, const nfc_target *pnt)
回傳 0 表示還在, 其它的值表示 error (即離開或讀不到了)

3.1 Polling sample code


相關的 sample code 可以參考 libnfc 裡的 nfc-poll.c
底下這個例子稍微簡化 nfc-poll.c,

#include <signal.h>
#include <stdio.h>
#include <stdlib.h>

#include <nfc/nfc.h>
#include <nfc/nfc-types.h>

static nfc_device *pnd = NULL;
static nfc_context *context;

static void print_hex(const uint8_t *pbtData, const size_t szBytes) {
 size_t szPos;
 for (szPos = 0; szPos < szBytes; szPos++) {
  printf("%02x ", pbtData[szPos]);
 }
 printf("\n");
}

static void stop_polling(int sig) {
 if (pnd != NULL) {
  nfc_abort_command(pnd);
 } else {
  nfc_exit(context);
  exit(EXIT_FAILURE);
 }
}

int main(int argc, const char *argv[]) {
 signal(SIGINT, stop_polling);

 const uint8_t uiPollNr = 20;
 const uint8_t uiPeriod = 2;
 const nfc_modulation nmModulations[1] = {
  { .nmt = NMT_ISO14443A, .nbr = NBR_106 },
 };
 const size_t szModulations = 1;

 nfc_target nt;
 int res = 0;

 nfc_init(&context);
 if (context == NULL) {
  printf("Unable to init libnfc (malloc)\n");
  exit(EXIT_FAILURE);
 }

 pnd = nfc_open(context, NULL);
 if (pnd == NULL) {
  printf("Unable to open NFC device\n");
  exit(EXIT_FAILURE);
 }

 if (nfc_initiator_init(pnd) < 0) {
  nfc_perror(pnd, "nfc_initiator_init");
  nfc_close(pnd);
  nfc_exit(context);
  exit(EXIT_FAILURE);
 }

 printf("NFC reader: %s opened\n", nfc_device_get_name(pnd));
 printf("NFC device will poll %d ms\n", uiPollNr * szModulations * uiPeriod * 150);
 res = nfc_initiator_poll_target(pnd, nmModulations, szModulations, uiPollNr, uiPeriod, &nt);
 if (res < 0) {
  nfc_perror(pnd, "nfc_initiator_poll_target");
  nfc_close(pnd);
  nfc_exit(context);
  exit(EXIT_FAILURE);
 } else if (res == 0) {
  printf("No target found\n");
 } else {
  printf("The following (NFC) ISO14443A tag was found:\n");
  printf("    ATQA (SENS_RES): ");
  print_hex(nt.nti.nai.abtAtqa, 2);
  printf("       UID (NFCID%c): ", (nt.nti.nai.abtUid[0] == 0x08 ? '3' : '1'));
  print_hex(nt.nti.nai.abtUid, nt.nti.nai.szUidLen);
  printf("      SAK (SEL_RES): ");
  print_hex(&nt.nti.nai.btSak, 1);
  if (nt.nti.nai.szAtsLen) {
   printf("          ATS (ATR): ");
   print_hex(nt.nti.nai.abtAts, nt.nti.nai.szAtsLen);
  }
 }

 printf("Waiting for card removing...\n");
 while (0 == nfc_initiator_target_is_present(pnd, NULL)) {};
 nfc_perror(pnd, "nfc_initiator_target_is_present");
 printf("done\n");

 nfc_close(pnd);
 nfc_exit(context);
 exit(EXIT_SUCCESS);
}






2014/10/03

Connect PN532 NFC to Raspberry Pi

1. Enable SPI on Raspberry Pi


PN532 NFC提供3種介面, SPI, I2C, 與UART
先選SPI做測試,

在RPi上面, SPI與I2C預設是關的, 我們要先打開它,

$ sudo nano /etc/modprobe.d/raspi-blacklist.conf


會看到裡面有一些黑名單

blacklist spi-bcm2708
blacklist i2c-bcm2708
blacklist snd-soc-pcm512x
blacklist snd-soc-wm8804


我們要用到spi, 所以把它mark掉

#blacklist spi-bcm2708

存檔離開, 然後重開機讓設定生效


重開機之後, 來看看SPI driver有沒有起來

$ ls /dev/spidev*
/dev/spidev0.0  /dev/spidev0.1

會看到有兩個 SPI driver, 前面的數字是第幾個SPI peripheral,
兩個數字都是1表示 RPi 只有一個SPI port
第二個數字是 Chip Selectt 使用pin CS0 & CS1


2. Introduction to SPI


Serial Peripheral Interface (SPI), 跟 I2C很像
是同步序列資料協定(synchronous serial data link)

常用的接角定義:
  • SCLK—Serial Clock(自master輸出), 有時也叫SCK
  • MOSI/SIMO—Master Output, Slave Input(自master輸出)
  • MISO/SOMI—Master Input, Slave Output(自slave輸出)
  • SS—Slave Select(active low;自master輸出), 在RPi上叫SPI_CE0_N, SPI_CE1_N, 在PN532上叫SSEL

在使用上, 1個master可以對應多個slave
可能的接法如下
如果device扮演的是Master, 那麼它的MOSI就等於是它的Output, MISO就是它的Input
如果device扮演的是Slave, 那麼它的MOSI就等於是它的Input, MISO就是它的Output

3. Connect PN532 to RPi via SPi


底下是 RPi 的接腳配置


其中跟 SPI 相關的有:
SPI_MOSI
SPI_MISO
SPI_SCLK
SPI_CE0_N
SPI_CE1_N

我們這樣接:

  • PN532 的 3.3V 接到 RPi 的3V3 (可接1或17的位置)
  • PN532 的 GND 接到 RPi 的接地 (可接 6, 9, 14, 20, 25, 30, 34, 39, 我是接39)
  • PN532 的 SCK 接到 RPi 的 SPI_SCLK (23的位置)
  • PN532 的 MISO 接到 RPi 的 SPI_MISO (21的位置)
  • PN532 的 MOSI 接到 RPi 的 SPI_MOSI (19的位置)
  • PN532 的 SSEL 接到 RPi 的SPI_CE0_N


4. Install libnfc in RPi


接好之後, 有很多種方式可以與PN532溝通, 用libnfc是其中一種方便的方式

首先先更安裝有dependency的library
sudo apt-get update
$ sudo apt-get install libusb-dev libpcsclite-dev

然後抓libnfc source code, 可以上它們的網站來找
https://code.google.com/p/libnfc/

其中stable version在寫這篇文章的時候在這裡
https://bintray.com/nfc-tools/sources/libnfc

抓好之後, 將它解壓縮

$ tar -xf libnfc-1.7.1.tar.bz2

然後compile & install
$ cd libnfc-1.7.1
$ ./configure --prefix=/usr --sysconfdir=/etc
$ make
$ sudo make install

修改它的config
$ cd /etc
$ sudo mkdir nfc
$ sudo nano /etc/nfc/libnfc.conf

把底下copy paste
# Allow device auto-detection (default: true)
# Note: if this auto-detection is disabled, user has to set manually a device
# configuration using file or environment variable
allow_autoscan = true

# Allow intrusive auto-detection (default: false)
# Warning: intrusive auto-detection can seriously disturb other devices
# This option is not recommended, user should prefer to add manually his device.
allow_intrusive_scan = false

# Set log level (default: error)
# Valid log levels are (in order of verbosity): 0 (none), 1 (error), 2 (info), 3 (debug)
# Note: if you compiled with --enable-debug option, the default log level is "debug"
log_level = 1

# Manually set default device (no default)
# To set a default device, you must set both name and connstring for your device
# Note: if autoscan is enabled, default device will be the first device available in device list.
device.name = "Itead_PN532_SPI"
device.connstring = "pn532_spi:/dev/spidev0.0:500000"

設定完成, 來試試看有沒有問題, 拿一張悠遊卡放在PN532旁邊
$ nfc-list
$ nfc-list
nfc-list uses libnfc 1.7.1
NFC device: pn532_spi:/dev/spidev0.0 opened
1 ISO14443A passive target(s) found:
ISO/IEC 14443A (106 kbps) target:
    ATQA (SENS_RES): 00  04
       UID (NFCID1): 2b  6a  20  87
      SAK (SEL_RES): 08

會動之後, 就可以玩其它功能了





2014/10/01

Setting raspberry pi uart

1. How to connect


首先需要一條 USB-TTL 線
(試過RS232轉TTL, 似乎不成功)

底下是 Raspberry Pi (RPi) B+的 40-pin 配置圖


其中
USB-TTL 接地線(GND) 接到 RPi GND (JB6)
USB-TTL RX 接到 RPi TX (JB8)
USB-TTL TX 接到 RPi RX (JB10)

2. Serail setting


用putty或其它serial terminal (Ex. tera term)

類型選 serial
Serail Line 填 USB-TTL 的 COM port
Speed 填 115200
Data Bits 填 8
Stop Bits 填 1
Parity 填 None
Flow Control 填 None


3. screen size


登入之後會發現, 它的 row = 24, column = 80, 使用起來不方便
用 vim 輸入幾個tab就超出螢幕了
可以用 stty 來修改

這個可以看當前的設定
$ stty size

修改行數為40

$ stty rows 40

修改每行字元數為128

$ stty columns 128




2014/09/29

Setting raspberry pi network

1. Use wpa_cli command connect to ssid


打這個 command 進入 wpa command line interface, (提示符號會變成 ">")

$ wpa_cli

掃瞄目前的wifi訊號, 並且列出結果
> scan
OK
> scan_result
bssid / frequency / signal level / flags / ssid
e4:71:85:04:98:f4       2442    100     [WPA-PSK-TKIP+CCMP][WPA2-PSK-TKIP+CCMP][ESS]    Almond-0326_nomap_5F-7
<3>CTRL-EVENT-SCAN-RESULTS

加入網路ssid, psk是密碼
> add_network
0
> set_network 0 ssid "MYSSID"
> set_network 0 psk "passphrase"
> enable_network 0
<2>CTRL-EVENT-CONNECTED - Connection to 00:00:00:00:00:00 completed (reauth) [id=0 id_str=]

如果是open network
> set_network 0 key_mgmt NONE

把config存起來
> save_config
OK

這些動作會存在
/etc/wpa_supplicant/wpa_supplicant.conf

其它常用的command

status (看看目前的狀態)
help (查看參數與指令)
terminate (關閉wpa_supplicant)
interface (查看有那些無線網卡介面)
list_networks (查看wpa_supplicant.conf檔裡的設定)
select_network (選擇不同的AP,id為AP代號0,1,2,3...)
enable_network 
disable_network 
remove_network (刪除AP資訊)
reconfigure (重新讀取wpa_supplicant.conf設定內容)
save_config (儲存寫入到wpa_supplicant.conf中,否責變更無效)
disconnect (斷線)
reconnect (重新連線)
scan (掃描附近的AP)
scan_results (印出附近AP的相關資訊)


2. search raspberry pi in subnet


用nmap就可以掃, 在windows底下要安裝

假如電腦的ip是 192.168.1.100/24
那麼就下這個command
nmap -sn 192.168.1.0/24

掃出來的結果裡, 可能有
Starting Nmap 6.40 ( http://nmap.org ) at 2014-03-10 12:46 GMT
Nmap scan report for hpprinter (192.168.1.2)
Host is up (0.00044s latency).
Nmap scan report for Gordons-MBP (192.168.1.4)
Host is up (0.0010s latency).
Nmap scan report for ubuntu (192.168.1.5)
Host is up (0.0010s latency).
Nmap scan report for raspberrypi (192.168.1.8)
Host is up (0.0030s latency).
Nmap done: 256 IP addresses (4 hosts up) scanned in 2.41 seconds

可以看到裡面有raspberrypi

NFC NDEF message format

1. NDEF message format


下圖是 NDEF mssage format 的示意圖, 1份 NDEF message 包含 >= 1 NDEF Record
1份 NDEF Record 包含 Header & Payload
Header包含好幾個欄位, 包括 TNF (Type Name Format)



1.1 Record Layout


每個 Record 前5個 bit 定義了record 在message中的位置

  • MB (Message Begin) : True when this is the first record in a message.
  • ME (Message End) : True when this is the last record in a message.
  • CF (Chunk Flag) : True when this record is chunked. (只用在NDEF message大到無法一次傳完)
  • SR (Short Record) : True if the short record format is used for payload length.
  • IL (ID Length is present) : True if the ID Length field is present.

如果 message 有多個 record, 那麼第一個 record 的 MB=1, ME = 0, 最後一個 record 的 MB = 0, ME = 1
如果 message 只有1個 record, 那個這個 record 的 MB=1, ME = 1

1.2 Type Name Format


TNF (Type Name Format) 佔 3 bits, 值為 0~7

  • 0 Empty : Empty record that has no type or payload
  • 1 Well-Known : NFC Forum RTD (Record Type Definition) specification 定義好的其中1種
  • 2 MIME media-type : Internet media type (defined in RFC 2046 )
  • 3 Absolute URI : URI (defined in RFC 3986 )
  • 4 External : User-defined value, based on rules in NFC Forum RTD specification
  • 5 Unknown : Type is unknown. Type length must be 0.
  • 6 Unchanged : Only for middle and terminating records of chunked payloads. Type length must be 0.
  • 7 Reserved : Reserved by the NFC Forum for future use.

常見的應用程式裡, TNF 01 (Well-Known) 以及 TNF 02 (MIME media-type) 最為常用
另外, TNF 04 (External) 也很常用, 包括了 Android App

1.3 Record Type Length


在 TNF & Layout Flag 之後是 1 byte 的 Payload Type Length
它定義了 Record Type 的長度
Record Type的用途是在TNF之外提供更細的分類, 而這個欄位的長度是可變動的
而 Record Type Length 定義這個欄位的長度

1.4 Payload Length


接著是 Payload Length, 它可能佔用1個 byte 或佔用 4個 byte
如果前面的 SR (Short Record) = 1, 那麼它佔用1個 byte
如果 SR = 0, 那麼它佔用4個byte

1.5 Record Type


Record Type,  RTD (Record Type Definition)進一步描述 Payload的內容, 這個

比如說, 當 TNF = 01 (Well-Known), 那麼 Record Type = "T" 表示 text, "U" 表示 URI, "Sp" 表示 Smart poster,
當 TNF = 02 (MIME media-type), 那麼 Record Type 可以描述 "text/html," "text/json," 以及 "image/gif."
當 TNF = 04 (External), Record Type 可以描述它是 Android APP

1.6 ID length &  ID (optional)


如果前面的 flag IL(ID Length is present)=1, 那麼就會有 ID length 以及 ID 欄位

Payload ID 是 optional field, 通常是 URL, 但也可以隨便放, 它的用途是讓應用程式提供相關資訊, 或是讓不同的 NDEF record reference 其它 record
因為是 optional, 所以也可以不放

1.7 Payload


Payload 沒有規定它的 format, 通常 NDEF library 在包NDEF record時, 是直接塞data進去而不做檢查, 所以可以加密, 也可以帶二進位資料












NFC Introduction

1. Introduction


RFID (Radio Frequency IDentification)

  • 通常用於 identification, 而不是 communication
  • RFID tags 可以帶少量的 data, 通常小於1000 bytes


NFC (Near Field Communication)

  • 交換更複雜的資料
  • NFC reader 可以讀 passive RFID tags, 也可以寫入部份的memory, 或是寫入標準格式的 RFID tags
  • 有效距離 <= 10cm, 低功耗, 其它裝置的干擾較小
  • 傳輸速率比wifi/bt低, 不適合需要高速傳輸的用途



2. RFID operating


Acting rules in RFID exchange:

  • initiator: 可能是 tag reader, 或是 tag reader/writer device
  • target: 也就是 tag, 當 initiator 嘗試連線時回應 UID (Unique IDentifier number)

Communication modes:

  • Passive RFID exchange: 用於 reader/writer 和 tag, 板上沒有power, 利用 radio power 回應 data
  • Active RFID exchanges: 用於接電的 reader/writer, 有效距離較長


RFID tag 在板上有少量的 memory <= 1KB,
initiator device 可以讀上面的 data
如果是 reader/writer device, 除了讀 data, 也可以寫 data 到 memory 上
(通常用於紀錄剩下多少錢之類的, 但現在都把database建在網路上, 然後用UID在網路找這筆資料)

ISO 制定了 很多 RFID 標準
不同的標準裡定義了 radio frequency, data transfer rate, data formats,...


3. NFC operating


NFC 和 RFID 一樣有 initiator & target
通常 target 是 programmable device, 可以做複雜的操作之後再回傳 data

Communication modes in NFC

  • passive communication mode: initiator 持續供電, 使 target 持續從 initiator 那邊吃電
  • active communication mode: initiator & target 都有電源

Operating modes in NFC

  • reader/writer, 可對 targer 讀寫
  • card emulator, 當它位於其它 NFC/RFID device field 裡
  • peer-to-peer mode, 可彼此交換 data


4. NFC data format


NFC devices 交換 data 的格式是 NDEF (NFC Data Exchange Format)
一份 NDEF message 包含 >=1 NDEF record
每個 NDEF record 包含 record type, a unique ID, length, payload
有幾個知名的 record type 是每個 NFC device 應該都會實作的:

  • Simple text records: 送text, 包括metadata像是language, encoding scheme, 通常不包含要求 target 要做的事
  • URIs: 送 network address, 通常預期 target 會送到對應的app, 像是browser
  • Smart Posters: 可以帶更多的 data, 像是 URIs, text, 通常 target 收到 Smart Poster record 會開啟 browser, SMS, email ap之類的
  • Signatures: 提供 trustworthy information

NDEF 是 NFC 與 RFID 最大的不同, 雖然 NFC 和許多 RFID protocol 都操作在 13.56 MHz
但 RFID protocol 沒有 common data format


5. NFC architecture




6. NFC Tag Types


NFC forum 定義了4種 NFC Tag Types:

Type 1

  • Based on ISO-14443A specification.
  • Can be read-only, or read/write capable.
  • 96 B ~ 2 KB memory.
  • Communication speed 106Kb.
  • No data collision protection.
  • Examples: Innovision Topaz, Broadcom BCM20203.


Type 2

  • Based on NXP/Philips Mifare Ultralight tag (ISO-14443A) specification.
  • Can be read-only, or read/write capable.
  • 96 B ~ 2 KB memory.
  • Communication speed 106Kb.
  • Anti-collision support.
  • Example: NXP Mifare Ultralight.


Type 3

  • Based on the Sony FeliCa tags (ISO-18092 and JIS-X-6319-4), without the encryption and authentication support that FeliCa affords.
  • Configured by factory to be read-only, or read/write capable.
  • Variable memory, up to 1MB per exchange.
  • Two communication speeds, 212 or 424Kbps.
  • Anti-collision support.
  • Example: Sony FeliCa.


Type 4

  • Based on NXP DESFire tag (ISO-14443A) specification.
  • Configured by factory to be read-only, or read/write capable.
  • 2, 4, or 8 KB of memory.
  • Variable memory, up to 32KB per exchange.
  • Three communication speeds: 106, 212, or 424Kbps.
  • Anti-collision support.
  • Example: NXP DESFire, SmartMX-JCOP.

另外一種雖然不在 NFC 定義裡, 但已經是目前最常見的 NFC tag type

Mifare Classic tag (ISO–14443A)

  • Memory options: 192, 768, or 3,584 bytes.
  • Communication speed 106 Kbps
  • Anti-collision support.
  • Examples: NXP Mifare Classic 1K, Mifare Classic 4K, Mifare Classic Mini.




2014/09/24

FreeRTOS queue management

1. Introduction


queue 用於 task 之間的溝通, 每個queue能放有限的item
item的大小稱為size, item的數量稱為length
當 queue 被建立時, length & size都會設定好

通常 queue 使用 FIFO, 資料從tail寫入, 從head讀出, (但也有可能從head寫入)
寫入的時候, 會copy data到queue裡面
讀出的時候, 會copy data出來, 並且queue的那一份會刪掉

queue 本身就像個物件, 會自行管理queue的相關功能,
所以多個task可以同時寫入同一個queue, 也可以多個task讀出同一個queue
queue通常有多個writer, 但不常有多個reader
( 這裡的writer指的是嘗試寫入queue的接口, reader指的是嘗試從queue讀出的接口 )

每當 task 嘗試從 queue裡讀出data, 需要設定 block time, 這是因為如果 queue 是空的, task就會進入block state, 直到 bloke time timeout 而回到 ready state, 或者是有其它 task / interrupt 寫入data到queue裡, 這樣也會讓 task 回到 ready state

如果 queue 有多個 reader, 並且有多個 task 都在等空的 queue, 那麼當有新的 data 進來, 只會有一個 task變成 ready state, queue會選出priority最高的task, 如果有多個task有一樣的priority, 那麼會選等最久的

當 task 嘗試寫入 data 到 queue裡, 也需要設定 block time, 這是因為如果 queue 滿了, task就會進入 block state, 直到 block time timeout 而回到 ready state, 或者這期間 queue 騰出空間而讓task回到 ready state

如果 queue 有多個 writer, 並且有多個 task 嘗試寫入滿的 queue, 那麼當空間騰出來時, priority最高的task會變成ready state, 如果有多個 task 有一樣的 priority, 那麼會選等最久的

2. using a queue


2.1 create a queue


xQueueHandle xQueueCreate( unsigned portBASE_TYPE uxQueueLength,
                           unsigned portBASE_TYPE uxItemSize
                           );

其中

  • uxQueueLength : max queue length
  • uxItemSize : item size, 單位是 byte

如果 RAM size 不夠,就會回傳 NULL,
如果成功, 回傳 queue handle

2.2 send data to queue


portBASE_TYPE xQueueSendToBack( xQueueHandle xQueue,
                                const void * pvItemToQueue,
                                portTickType xTicksToWait
                                );

xQueueSendToBack() 會把 item 放到 queue 的後面 (tail), xQueueSend() 跟 xQueueSendToBack() 是同樣的function

portBASE_TYPE xQueueSendToFront( xQueueHandle xQueue,
                                 const void * pvItemToQueue,
                                 portTickType xTicksToWait
                                 );

xQueueSendToFromt 把 data 送到 queue 的前端 (head)

其中它的參數 :

  • xTicksToWait : 如果想讓 send 一直等下去, 可以把 xTicksToWait 設成 portMAX_DELAY, 如果不想等, 就把 xTicksToWait 設成 0

可能的回傳值 :

  • pdPASS : 表示成功寫入, 即使task是在block time timeout之前寫入也算是成功寫入
  • errQUEUE_FULL : 失敗, 如果 xTicksToWait > 0, 那麼表示在 block time timeout之前都沒辦法寫入

2.3 receive data from queue


portBASE_TYPE xQueueReceive( xQueueHandle xQueue,
                             const void * pvBuffer,
                             portTickType xTicksToWait
                             );
xQueueReceive 從 queue head 讀一個 item, 並把 queue 裡的 item 刪掉

portBASE_TYPE xQueuePeek( xQueueHandle xQueue,
                          const void * pvBuffer,
                          portTickType xTicksToWait
                          );

xQueuePeek 從 queue head 讀一個item, 但不會刪掉 item

可能的回傳值 :

  • pdPASS : 表示成功讀出, 即使 task 在 block time timeout 之前讀出也算成功讀出
  • errQUEUE_EMPTY : 失敗, 如果 xTicksToWait > 0, 表示在 block time timeout 之前沒辦法讀出

2.4 query queue


unsigned portBASE_TYPE uxQueueMessagesWaiting( xQueueHandle xQueue );

uxQueueMessagesWaiting 可以拿到目前 queue 裡的 items 數量

要注意的是, 這個不能在interrupt裡面使用,
interrupt應該使用 uxQueueMessagesWaitingFromISR()

3. sample code


/* 宣告一個queue */
xQueueHandle xQueue;

static void vSenderTask( void *pvParameters )
{
    long lValueToSend;
    portBASE_TYPE xStatus;

    /* 我們把要寫入 queue 的資料從 task 的 parameter 帶入, 型態為 long */
    lValueToSend = ( long ) pvParameters;

    for( ;; )
    {
        /* 把data寫入queue, 第1個參數是要寫入的queue,
           第2個參數是要寫入的item pointer, 第3個參數是block time */
        xStatus = xQueueSendToBack( xQueue, &lValueToSend, 0 );
        if( xStatus != pdPASS )
        {
            vPrintString( "Could not send to the queue.\r\n" );
        }

        /* taskYIELD() 通知 scheduler switch 到別的task */
        taskYIELD();
    }
}

static void vReceiverTask( void *pvParameters )
{
    long lReceivedValue;
    portBASE_TYPE xStatus;
    const portTickType xTicksToWait = 100 / portTICK_RATE_MS;

    for( ;; )
    {
        /* 檢查 queue 裡面的 item 數量 */
        if( uxQueueMessagesWaiting( xQueue ) != 0 )
        {
            vPrintString( "Queue should have been empty!\r\n" );
        }

        /* 從 queue 裡讀出data, 第1個參數是要讀出data的queue,
           第2個參數是放 data 的 pointer, 第3個參數是 block time */
        xStatus = xQueueReceive( xQueue, &lReceivedValue, xTicksToWait );
        if( xStatus == pdPASS )
        {
            /* 成功讀出 data, 把它印出來 */
            vPrintStringAndNumber( "Received = ", lReceivedValue );
        }
        else
        {
            /* 100ms內沒有讀出data */
            vPrintString( "Could not receive from the queue.\r\n" );
        }
    }
}

int main( void )
{
    /* 建 queue, 設定成長度為5個item, item大小為 sizeof(long) */
    xQueue = xQueueCreate( 5, sizeof( long ) );
    if( xQueue != NULL )
    {
        /* 建2個 task, 分別對 queue 寫入 data, 
           data 來自於 task 的 parameter, 也就是100和200 */
        xTaskCreate( vSenderTask, "Sender1", 1000, ( void * ) 100, 1, NULL );
        xTaskCreate( vSenderTask, "Sender2", 1000, ( void * ) 200, 1, NULL );

        /* 建1個 task 從 queue 讀出 data, 它的priority比較高, 
           但是在 xQueueReceive(), 如果 queue 裡沒 data, 它就會進 block state */
        xTaskCreate( vReceiverTask, "Receiver", 1000, NULL, 2, NULL );

        vTaskStartScheduler();
    }
    else
    {
        /* 建 queue 失敗 */
    }

    /* we should never reach here */
    for( ;; );
}

4. store compound data


有時候對 receiver task 來說, 它需要知道 sender 是誰, 我們可以把 data type 設計成 struct, 並且裡面加上 sender information

另一種情況是, 如果我們要在 queue 裡放大的 data, 改成用指向該 data 的 pointer 會比較好, 這樣可以避免在寫入和讀出時 copy 大量的 data, 但是要注意 memory 的 allocation & release,