2010年07月22日

Linux2.6.33を載せたKZM-CA9-01ボードでSDメモリカードがマウントできない(その2)

前回の続き。

対症療法ですが問題は解決しました。同じ修正で2.6.35-rc3でも動くようになりました。



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では

  1. キャッシュをinvalidate
  2. DMA転送
  3. DMA転送終了後はキャッシュ操作なし

2.6.33では

  1. キャッシュをinvalidate
  2. DMA転送
  3. キャッシュを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のドキュメントと突き合せながらこれを読み解くと

  1. 指定された領域の最初と最後がキャッシュのラインのアライメントに合っていないときには clean & invalidate を行う。
  2. 指定された領域を全部 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:


トラックバックURL

トラックバック一覧

1. KZM-CA9-01ボードのリンク集  [ KMC Staff Blog ]   2010年09月16日 10:44
4コアのCortex-A9のKZM-CA9-01ボードを使って実験したことを書いたページをまとめてみました。

コメントする

名前
URL
 
  絵文字
 
 
記事検索
最新コメント
アクセスカウンター
  • 今日:
  • 昨日:
  • 累計:

QRコード
QRコード