2010年07月22日
Linux2.6.33を載せたKZM-CA9-01ボードでSDメモリカードがマウントできない(その2)
DMA転送の手順
MMCホストドライバのレベルでのDMA転送は以下のような手順です。
dma_map_sg *** DMA転送 *** dma_unmap_sg
dma_unmap_sgはデータ転送完了の割り込みの中で呼ばれます。
dma_map_sg, dma_unmap_sgは arch/arm/mm/dma-mapping.c にありますが、この実装は2.6.32と2.6.33で違いがあります。
キャッシュの操作の振る舞いを調べる
具体的なキャッシュの制御の実装は arch/arm/mm/cache-v7.S です。アセンブラで書かれています。
この中でDMAに関係のありそうな
- v7_dma_inv_range
- v7_dma_clean_range
- v7_dma_flush_range
にPARTNER-Jetでブレークポイントをかけて2.6.32と2.6.33で動きを比較してみます。
2.6.32 (SDメモリのmount OK)
>BP v7_dma_inv_range
>BP v7_dma_clean_range
>BP v7_dma_flush_range
>g
>
R0/R8 R1/R9 R2/R10 R3/R11 R4/R12 R5/R13 R6/R14 R7
R0-7 :C71334E8 C71334F0 00000008 C002C564 000004E8 C04B9660 C00298E0 00000008
R8-14:C034DAB0 C034DAB0 00000002 CB8EBEE0 00005D65 CB8EBDD8 C0029AF4
PC :C002C564 CPSR :-ZC--------A---_svc SPSR :-ZC--------A---_svc
PID :30(0x1E)
v7_dma_inv_range()
Real Time Count = 0,010s180m800u
>k
sched.c :2892 : C023EC64 schedule+7C0()
dma-mapping.c: 608 : C0029AF4 dma_cache_maint_page+88(page=*00000000,offset=FFFFFFFF,size=6E6F632F,dir=???)
process.c : 189 : C00242DC process.c@kernel_thread_exit()
kthread.c : 81 : C00537B8 kthread+7C(_create=*CB82BF14)
workqueue.c : 295 : C004FBB4 worker_thread+164(__cwq=*???)
core.c~2 :1128 : C01ABF18 mmc_rescan+224(work=*CBBD7150)
sd.c~1 : 773 : C01AE754 mmc_attach_sd+F4(host=*CBBD7000,ocr=C01AE754)
sd.c~1 : 438 : C01AE30C mmc_sd_init_card+38C(host=*CBBD7000,ocr=???,oldcard=*00000000)
sd_ops.c : 291 : C01AECB0 mmc_app_send_scr+EC(card=*C7133400,scr=*C71334E8)
core.c~2 : 205 : C01AC2F8 mmc_wait_for_req+1E4(host=*CBBD7000,mrq=*CB8EBECC)
a9tc_mmc.c : 400 : C01B2ED8 a9tc_mmc_request+10C(mmc=*???,req=*CB8EBECC)
dma-mapping.h: 333 : C0029BC4 dma_map_sg+50(dev=*???,sg=*000F4400,nents=1,dir= (512))
dma-mapping.c: 608 : C0029AF4 dma_cache_maint_page+88(page=*00000000,offset=CB8EBEE0,size=C04B9660,dir=???)
cache-v7.S : 274 : C002C564 v7_dma_inv_range()
>
kコマンドでスタックトレースが表示されます。
v7_dma_inv_rangeが呼ばれたのはdma_map_sgの中だけです。
dma_unmap_sgからは呼ばれていません。
2.6.33 (SDメモリのmount NG)
>BP v7_dma_inv_range
>BP v7_dma_clean_range
>BP v7_dma_flush_range
>g
>
R0/R8 R1/R9 R2/R10 R3/R11 R4/R12 R5/R13 R6/R14 R7
R0-7 :CB0F5EE8 CB0F5EF0 00000002 00000002 00000008 00000EE8 C0535EA0 00000002
R8-14:C0535EA0 C034B1D8 00000EE8 CB8CDEE0 C03D4000 CB8CDDC8 C0029BAC
PC :C002C9C4 CPSR :-ZC------------_svc SPSR :-ZC------------_svc
v7_dma_inv_range()
Real Time Count = 0,011s512m800u
>k
sched.c :2931 : C0240F7C schedule+754()
dma-mapping.c: 480 : C0029BAC dma_cache_maint_page+2C(page=*???,offset=???,size=???,dir=DMA_FROM_DEVICE,op=*C002CA68)
process.c : 193 : C0023D24 process.c@kernel_thread_exit()
kthread.c : 81 : C00537D4 kthread+7C(_create=*CB82BF14)
workqueue.c : 407 : C004FE3C worker_thread+164(__cwq=*???)
core.c~2 :1144 : C01AD11C mmc_rescan+224(work=*CBBD8150)
sd.c~1 : 754 : C01AF974 mmc_attach_sd+10C(host=*CBBD8000,ocr=C01AF974)
sd.c~1 : 438 : C01AF514 mmc_sd_init_card+38C(host=*CBBD8000,ocr=???,oldcard=*00000000)
sd_ops.c : 291 : C01AFED0 mmc_app_send_scr+EC(card=*CB0F5E00,scr=*CB0F5EE8)
core.c~2 : 221 : C01AD500 mmc_wait_for_req+1E8(host=*CBBD8000,mrq=*CB8CDECC)
a9tc_mmc.c : 400 : C01B4150 a9tc_mmc_request+10C(mmc=*???,req=*CB8CDECC)
dma-mapping.h: 373 : C0029F28 dma_map_sg+50(dev=*???,sg=*000F4400,nents=1,dir=DMA_FROM_DEVICE)
dma-mapping.c: 489 : C0029BD8 ___dma_page_cpu_to_dev+24(page=*00000001,off=0,size=8,dir=DMA_FROM_DEVICE)
dma-mapping.c: 480 : C0029BAC dma_cache_maint_page+2C(page=*???,offset=???,size=???,dir=DMA_BIDIRECTIONAL,op=*C002CA68)
cache-v7.S : 275 : C002C9C4 v7_dma_inv_range()
>g
R0/R8 R1/R9 R2/R10 R3/R11 R4/R12 R5/R13 R6/R14 R7
R0-7 :CB0F5EE8 CB0F5EF0 00000002 00000002 00000002 00000EE8 C0535EA0 00000008
R8-14:00000002 00000100 C01B47A8 C03486D0 C03D4000 C030DE20 C0029BAC
PC :C002C9C4 CPSR :--C--------AI--_svc SPSR :-ZC------------_svc
v7_dma_inv_range()
Real Time Count = 0,000s010m200u
>k
process.c : 156 : C002400C cpu_idle+28()
dma-mapping.c: 480 : C0029BAC dma_cache_maint_page+2C(page=*???,offset=???,size=???,dir= (-876771836),op=*C002CA78)
process.c : 362 : C0243FF0 cpu_online_mask()
dma-mapping.c: 480 : C0029BAC dma_cache_maint_page+2C(page=*???,offset=???,size=???,dir= (-876771836),op=*C002CA78)
entry-armv.S: 234 : C0027538 traps.c@__irq_svc+38()
irq_regs.h : 33 : C00223DC do_local_timer+50(regs=*???)
softirq.c : 308 : C0044C70 irq_exit+44()
softirq.c : 221 : C0044B94 __do_softirq+90()
timer.c :1029 : C0048DC0 run_timer_softirq+160(h=*???)
a9tc_mmc.c : 369 : C01B4834 a9tc_timeout_timer+8C(data=???)
a9tc_mmc.c : 153 : C01B451C a9tc_mmc_data_done+28(host=*CBBD81A0,cmd=*CB8CDEA0)
dma-mapping.c: 567 : C0029D24 dma_unmap_sg+40(dev=*???,sg=*???,nents=1,dir= (-1070538624))
dma-mapping.c: 511 : C0029C8C ___dma_page_dev_to_cpu+6C(page=*C034B1D8,off=1,size=1,dir=DMA_FROM_DEVICE)
dma-mapping.c: 480 : C0029BAC dma_cache_maint_page+2C(page=*???,offset=???,size=???,dir=DMA_FROM_DEVICE,op=*C002CA78)
cache-v7.S : 275 : C002C9C4 v7_dma_inv_range()
>
v7_dma_inv_rangeはdma_map_sgとdma_unmap_sgの両方から呼ばれていました。
整理すると、2.6.32では
- キャッシュをinvalidate
- DMA転送
- DMA転送終了後はキャッシュ操作なし
2.6.33では
- キャッシュをinvalidate
- DMA転送
- キャッシュをinvalidate
v7_dma_inv_rangeのコードは2.6.32と2.6.33では同一でした。
2.6.33では念入りにキャッシュのinvalidateをしていますが、この手順は特に間違っていないと思います。
試しに2.6.33でDMA転送後にキャッシュのinvalidateをしないようにソースをコメントアウトして動かしてみました。
すると ... SDメモリカードのmountに成功しました。
うーむ、単純にdma_unmap_sgの中からv7_dma_inv_rangeが呼ばれないようにすればいいようです。
v7_dma_inv_rangeではキャッシュの内容を捨て去るときに、一部メモリに書き出してしまうと考えるとこの状況の説明がつきます。
v7_dma_inv_range
これが実際のv7_dma_inv_range のコードです。
/* * v7_dma_inv_range(start,end) * * Invalidate the data cache within the specified region; we will * be performing a DMA operation in this region and we want to * purge old data in the cache. * * - start - virtual start address of region * - end - virtual end address of region */ ENTRY(v7_dma_inv_range) dcache_line_size r2, r3 sub r3, r2, #1 tst r0, r3 bic r0, r0, r3 mcrne p15, 0, r0, c7, c14, 1 @ clean & invalidate D / U line tst r1, r3 bic r1, r1, r3 mcrne p15, 0, r1, c7, c14, 1 @ clean & invalidate D / U line 1: mcr p15, 0, r0, c7, c6, 1 @ invalidate D / U line add r0, r0, r2 cmp r0, r1 blo 1b dsb mov pc, lr ENDPROC(v7_dma_inv_range)
おや? コメントの中にclean & invalidate と書いてある行があります。この文脈でcleanというとキャッシュの内容をメモリに書き出すことです。
/* * v7_dma_flush_range(start,end) * - start - virtual start address of region * - end - virtual end address of region */ ENTRY(v7_dma_flush_range) dcache_line_size r2, r3 sub r3, r2, #1 bic r0, r0, r3 1: mcr p15, 0, r0, c7, c14, 1 @ clean & invalidate D / U line add r0, r0, r2 cmp r0, r1 blo 1b dsb mov pc, lr ENDPROC(v7_dma_flush_range)
こっちと同じ命令が使われています。
CPUのドキュメントと突き合せながらこれを読み解くと
- 指定された領域の最初と最後がキャッシュのラインのアライメントに合っていないときには clean & invalidate を行う。
- 指定された領域を全部 invalidate する。
Cortex-A9の場合はキャッシュのラインサイズは32バイトです。キャッシュの操作はライン単位でしか行うことができません。キャッシュをinvalidateするときに、最初と最後の境界の部分がキャッシュのラインサイズにそろっていない場合には、そこはメモリに書き出さざるをえません。そうでないと隣接する領域への書き込みも一緒に無効にされてしまいます。(テキストエデュタで編集したのにファイルに保存しないで終了してしまったのと同じ。)
先ほどのログからv7_dma_inv_rangeの呼び出しでstart=0xCB0F5EE8 , end=0xCB0F5EF0となっていました。(R0が第一引数、R1が第二引数)
32バイト境界にないのでメモリへの書き戻しが発生し、その結果DMAで転送されたデータが上書きされてしまいます。
DMA転送にこんな中途半端なアドレスを使うのもなにか変ですね。
ソース修正
さて、問題の因果関係はわかったのですが、どう修正するのがよいでしょう。
(a) DMA転送に使うバッファをきちんとキャッシュのラインサイズの境界に合わせて確保する。
(b) キャッシュのinvalidateはDMA転送の前だけで行い、DMA転送後には行わない。
v7以外ではどうなっているかなとcache-v6.Sを見てみると、なんとv6_dma_unmap_areaでは何もせずにリターンするようになっています。つまりarmv6では2.6.33でもDMA終了後にキャッシュのinvalidateを行わないのです。
v7_dma_unmap_areaもこれに合わせることにしました。つまり(b)案です。
ソースのパッチは以下通りです。
diff --git a/arch/arm/mm/cache-v7.S b/arch/arm/mm/cache-v7.S index aa171bf..f92d0d5 100644 --- a/arch/arm/mm/cache-v7.S +++ b/arch/arm/mm/cache-v7.S @@ -346,9 +348,6 @@ ENDPROC(v7_dma_map_area) * - dir - DMA direction */ ENTRY(v7_dma_unmap_area) - add r1, r1, r0 - teq r2, #DMA_TO_DEVICE - bne v7_dma_inv_range mov pc, lr ENDPROC(v7_dma_unmap_area)
これと同じ修正をすることで、2.6.35-rc3でもSDメモリカードがマウンドできるようになりました。しばらくこれで様子を見てみます。
このあたりの事情について何か知っている方はぜひコメントください。
おまけ
いろいろと実験をする過程で何回かSDメモリカードのVFATのファイルシステムが壊れました。
これをWindowsで修復するコマンドは以下の通り。(SDメモリがg:ドライブの場合)
> chkdsk /f g:
関連するページ
Linux2.6.33を載せたKZM- CA9-01ボードでSDメモリカードがマウントできない(その1)