SMBus を使ってメモリの SPD を読む 〜 DIMM に記録されたデータは電源を切ると消えてしまう、○か×か 〜

前回 : https://qiita.com/tanakmura/items/80988d6976fe7e7e01b1

# DRAMのパラメータ

さて、ここまでの話で、DRAMの初期化という手順が必要なのは分かったかと思う。

で、「DRAMの初期化」ってなんやねん、という話をしておく。


DRAMというのは、read する場合
1. Row address を設定して Row を開く
2. Column address を設定してデータを取りだす
3. データが出る
4. Rowを閉じる(プリチャージ)

という手順をくりかえす。それぞれのステップでアナログ的な充電、放電時間が必要になる。
たとえば、Row address を設定するのと同時に Column address を設定することはできない。Row address 設定後、しばらく経ってから Column address を設定する必要がある。

https://akizukidenshi.com/catalog/g/g104156/ のデータシートより

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12877/353b69ce-ce2b-6f15-0555-6edf839fc2c4.png)

DRAMでは、これは実時間で管理する。DRAM アクセスするには、「Row Address 設定後、XX nsec経過したら、Column Address を設定する」というような手順になる。

DRAMに正しくアクセスするには、この時間を守ってアクセスする必要があるが、この時間は、DRAMごとに違っている。

「DRAMの初期化」というのは、この時間を遵守するように、DRAMコントローラを設定することを言う。(実際にはDDR SDRAMでは高周波信号を通すための設定もやってる。私は詳しくないので省略)

「DRAMは初期化してないやん、DRAMコントローラの初期化やないか」、と思った人もいるかもしれない。私もそう思ったときは「DRAMCの初期化」って書いてる場合もある(人によってはメモコンの初期化とかも言う)。いやでもDLLの設定もしてるし…？

# SPD

で、このアクセスに必要な時間だが、DRAM毎に違うと言われても、デスクトップパソコンのDRAMはそこらへんで売ってるのをユーザーが勝手に差して使ってるわけで、どういうDRAMが差さってるかはマザーボードやファームウェアを作ってる時点では分からない。

あと容量もDRAMのモジュールごとに違うし、同じ容量のDRAMでもそのアドレスのビットが、チャネル、モジュール、ランク、バンク、Row、Column、のどれに割り当たってるかも違う。もちろんそのあたりもDRAMにあわせて適切に設定しないといけない。

これらの情報は、DRAMとセットになって配布されないといけない。つまりDRAMを買ったときにその情報も一緒に買っているはずである。

DIMMが手元にある人はよく見てみよう。(あ、今時のDIMMはヒートシンク付いてるから見れない人も多いかもね…)

デスクトップPCで使うDRAMのDIMMの場合は、DIMMの上にDRAMとは別に**EEPROM**という不揮発メモリが搭載されていて、そこにパラメータが保存されている。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12877/354b6a2b-1c3d-e7bb-4e61-b02ff0ca3d73.png)

この赤い丸で囲ってあるところのように、大きいチップ以外に、小さいチップが乗っているはずだ。(これはDDR2。今調べたら、DDR5はDIMMの上に電源が乗ってて部品が増えてるらしいので、小さいチップがたくさん乗ってるっぽい)

これがEEPROMで、ここにDRAMのパラメータが保存されている。

このパラメータは **SPD** (Serial Presence Detect)と呼ばれ、そのフォーマットはJEDECで決められている。 https://en.wikipedia.org/wiki/Serial_presence_detect

このSPDを読み取れば、DRAMコントローラに設定すべき適切な値が分かるようになっている。

なお、半田付けされているノートPCや組み込み機器では、DRAMは機器ごとに固定なので、SPD が ファームウェアと一緒に SPI Flash の中に保存されているか、DRAMコントローラの初期化コードがハードコードされてることが多いはずである。ノートPC用に自分でファームウェアを書く場合は、DRAMチップの型番を調べて、自分でパラメータを設定しよう。

# SMBUS

さて、それでは、EEPROMにアクセスしてSPDを見てみる。

この EEPROM は、I2Cでアクセス可能になっている。DIMM のピン上に、I2Cアクセス用の端子が実装されていて、その端子経由でアクセスできる。この端子が、DIMM上のEEPROMに直結されている。

https://www.gadget-manual.com/pinout-ddr/

使うのは、SDA,SCL,SA2,SA1,SA0だ。

I2Cでのデバイスのスレーブアドレスは、EEPROMを示す1010と、DIMM端子上に出ているSA2,SA1,SA0 の7bitで表現される。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12877/3e992381-96d0-bd67-3024-c7e78476e6ef.png)

( https://akizukidenshi.com/goodsaffix/24LC256.pdf より)

マザーボード上には、複数のDIMMが差せる。それを区別するために、マザーボード側からスロットの位置をA2,A1,A0を信号として出力している。Slot 0 は、A2,A1,A0=000b,  Slot 1 は A2,A1,A0=001b となるように、マザーボードからDIMMへ向けて信号が出ている(はずである。D945GCLF2はスロット一個しかないので区別する必要がない)

I2C は一個の経路で、複数のデバイスと通信できるバスである。このI2Cの線が、マザーボード上の各種デバイスと通信できるように張り巡らされている。マザーボード上の温度を監視するためのセンサと通信するのもこのI2Cのバスになる。

このマザーボード上に張り巡らされたI2Cのバスを、SMBus (System Management Bus)と呼ぶ。厳密にはI2CとSMBusは区別があるが、今回の話の範囲としては、同じものと見做してよい。(I2Cと違って minimum freq が決まっている、エラー処理が必須などの違いがあるらしい(よく知らない))

この SMBus にぶら下がっているデバイスは、Linux のコマンドラインからも確認できる。
```
 $ sudo i2cdetect -l | grep -i SMBus
i2c-0   smbus           SMBus I801 adapter at efa0              SMBus adapter
```

i2cdetect コマンドで見て、SMBus と付いているものが、SMBus になる。

```
 $ sudo i2cdetect 0
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0.
I will probe address range 0x08-0x77.
Continue? [Y/n]
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         08 -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 31 -- -- 34 35 36 -- -- -- -- -- -- -- -- --
40: -- -- -- -- 44 -- -- -- -- -- -- -- -- -- -- --
50: 50 51 52 53 -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
```

`i2cdetect` コマンドで、このバスをスキャンすると、その下にぶら下がっているデバイスが分かる。さきほども書いたとおり、DRAM上のEEPROMのデバイスアドレスは、0b1010xxx になるので、上の場合、0x50-0x53 の4つのデバイスが、このPCに差さっているDIMM上に搭載されたEEPROMになる。(これはD945GCLF2ではなくて、普通に使ってる手元のPCなので4つDIMMが付いてます)

```
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f
00:                         08 -- -- -- -- -- -- --
10: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
20: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
30: 30 31 -- -- 34 35 UU UU -- -- -- -- -- -- -- --
40: -- -- -- -- 44 -- -- -- -- -- -- -- -- -- -- --
50: UU UU UU UU -- -- -- -- -- -- -- -- -- -- -- --
60: -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
70: -- -- -- -- -- -- -- --
```

もし `UU` と表示されていたら、このデバイスはカーネルのドライバによって占有されていることを意味する。 `/sys/bus/i2c/devices/i2c-0/0-0050/` などにアクセスして、どのドライバが使っているかを調べてはずしておこう。

```
$ ls -l /sys/bus/i2c/devices/i2c-0/0-0050/driver
lrwxrwxrwx 1 root root 0 10月 21 22:52 /sys/bus/i2c/devices/i2c-0/0-0050/driver -> ../../../../../bus/i2c/drivers/ee1004

$ sudo modprobe -r ee1004
```

`i2cdump` コマンドで、対象デバイスに対してread/writeできる
```
$ sudo i2cdump 0 0x50
i2cdump 0 0x50
No size specified (using byte-data access)
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0, address 0x50, mode byte
Continue? [Y/n]
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 23 10 0c 02 84 19 00 08 00 00 00 03 09 03 00 00    #?????.?...???..
10: 00 00 07 0c fc 2b 00 00 6b 6b 6b 11 00 6b 20 08    ..???+..kkk?.k ?
20: 00 05 70 03 00 b8 1b 28 28 00 00 00 00 00 00 00    .?p?.??((.......
30: 00 00 00 00 00 00 00 00 00 00 00 00 16 36 16 36    ............?6?6
40: 16 36 16 36 00 20 2b 0c 2b 0c 2b 0c 2b 0c 00 00    ?6?6. +?+?+?+?..
50: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
60: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
70: 00 00 00 00 00 ed 9d b5 ca ca ca ca 00 d6 d3 48    .....???????.??H
80: 11 11 01 01 00 00 00 00 00 00 00 00 00 00 00 00    ????............
90: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
a0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 de 27    ..............?'
```

DDR4 以降では、データが128byteより大きくなっていて、ページを切りかえてアクセスする必要がある。

I2C上の 0x36 or 0x37 に何か書くことで、読むページを切りかえられる。0x36に何か書くと、ページ0、0x37に何か書くとページ1になる
```
 $ i2cset 0 0x37 0 c # page 1にきりかえ
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will write to device file /dev/i2c-0, chip address 0x37, data address
0x00, no data.
Continue? [Y/n]

 $ i2cdump 0 0x50
No size specified (using byte-data access)
WARNING! This program can confuse your I2C bus, cause data loss and worse!
I will probe file /dev/i2c-0, address 0x50, mode byte
Continue? [Y/n]
     0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f    0123456789abcdef
00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
10: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
20: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
30: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
40: 85 9b 00 00 00 a8 0b fc e5 42 4c 53 38 47 34 44    ??...????BLS8G4D
50: 32 34 30 46 53 41 2e 4d 31 36 46 41 44 00 80 2c    240FSA.M16FAD.?,
60: 00 34 31 36 39 32 36 30 37 31 ff 00 00 00 00 00    .416926071......
70: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
80: 0c 4a 05 20 00 00 00 00 00 94 00 00 07 ff 3f 00    ?J? .....?..?.?.
90: 00 6a 6a 6a 11 00 6b 20 08 00 05 70 03 00 b8 1e    .jjj?.k ?.?p?.??
a0: 2b 00 00 00 00 00 00 00 00 cf b5 ca ca ca ca d6    +........???????
b0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
c0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
d0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
e0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
f0: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................
```

今使っているDIMMの名前は、"BLS8G4D240FSA.M16FAD" になる。これを表現する文字列が見える。

これを人間が見やすいようにフォーマットするのが、`decode-dimms` コマンドになる。みんなも試してみよう。

これとは別に、 `dmidecode` という似たような情報を表示できるコマンドもある。これは直接 SMBus を読んでいるわけではなく、ファームウェアが記録したテーブルに書かれた情報を読んでるので、動作は少し違うので注意しよう。


# ICH7 と SMBus

このマザーボード上に張り巡らされた SMBus は、D945GCLF2では、ICH7(south bridhge)に入っている SMBus Host の機能を使ってアクセスできる。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12877/6a84363d-e6eb-f4ed-4f10-29495e828f65.png)

(このSMBusはSuperIOにも繋がっている場合がある。SuperIO上には、マザーボード上のセンサのA/D変換も付いていて、そこへアクセスできるようだ(試してない))

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12877/ff108313-97c7-194f-b7de-9e3aa2a1953c.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/12877/b434fe0e-e7c1-eb05-a288-e766d5764222.png)

https://www.intel.com/content/dam/doc/datasheet/i-o-controller-hub-7-datasheet.pdf より

これを読むとSMBusへのアクセス方法がわかる。

1. PCI dev=0x1f, fn=0x3 のコンフィグ空間の0x04に1を書いてIOを有効にする
2. PCI dev=0x1f, fn=0x3 のコンフィグ空間の0x20にIOのベースアドレスを書く(なんでもいい。SMBASEとする)
3. PCI dev=0x1f, fn=0x3 のコンフィグ空間の0x40に1を書いて SMBus Host を有効にする

これで初期化完了、あとは読んでいく。

1. SMBASE+0x00 の HST_STS に 0xff を書いて前回読んだ結果を消去
2. SMBASE+0x04 の XMIT_SLVA に I2C EEPROM のスレーブアドレス(0x50)を書く。i2cのアドレスは、7bitにプラスrwの方向の1bitを付けるので、0x50 `0b101_0000` から読むときは、0xa1 `0b1010_0001` を書く
3. SMBASE+0x03 にある HST_CMD にメモリのアドレスを書く
4. SMBASE+0x02 にある HST_CNT に read command (2<<2) と start (1<<6) を書く

これでreadが開始される。完了すると HST_STS に INTR ビットが立つ。割り込みを使わない場合は、これをポーリングで読む。


これを実装してSPDを読んでhexdump形式で出すプログラムが以下になる。

https://github.com/tanakamura/my_firmware/blob/8956d7596011031d12498b2d40bd65e2cfeac40d/d945gclf_smbus/r/src/lib.rs#L155


```
0000000 0880 0e08 010a 0040 2505 0040 0882 0000
0000010 080c 0170 0002 3003 3d45 3c50 3c1e 012d
0000020 2517 1205 1e3c 001e 3c06 807f 1e14 0000
0000030 0000 0000 0000 0000 0000 0000 0000 7d12
0000040 4f7f 0000 0000 0000 4a54 344d 4447 5244
0000050 2d32 4b38 2020 2020 2020 0020 1000 0007
0000060 5f08 00af 0000 0000 0000 0000 0000 0000
0000070 0000 0000 0000 0000 0000 0000 0000 0000
0000080
```

`dmidecode` は `hexdump` 形式のダンプファイルから読めるので、これで読んでみよう

```
 $ decode-dimms -X spd.hex
# Using little-endian 16-bit hex dump
# decode-dimms version 4.1

Memory Serial Presence Detect Decoder
By Philip Edelbrock, Christian Zuckschwerdt, Burkart Lingner,
Jean Delvare, Trent Piepho and others


Decoding EEPROM: spd.hex

---=== SPD EEPROM Information ===---
EEPROM Checksum of bytes 0-62                    OK (0x7D)
# of bytes written to SDRAM EEPROM               128
Total number of bytes in EEPROM                  256
Fundamental Memory type                          DDR2 SDRAM
SPD Revision                                     1.2

---=== Memory Characteristics ===---
Maximum module speed                             800 MT/s (PC2-6400)
Size                                             2048 MB
Banks x Rows x Columns x Bits                    8 x 14 x 10 x 64
Ranks                                            2
SDRAM Device Width                               8 bits
Module Height                                    < 25.4 mm
Module Type                                      UDIMM (133.25 mm)
DRAM Package                                     Planar
Voltage Interface Level                          SSTL 1.8V
Module Configuration Type                        No Parity
Refresh Rate                                     Reduced (7.8 us) - Self Refresh
Supported Burst Lengths                          4, 8
Supported CAS Latencies (tCL)                    6T, 5T, 4T
tCL-tRCD-tRP-tRAS                                6-6-6-18 as DDR2-800
                                                 5-5-5-15 as DDR2-666
                                                 4-4-4-12 as DDR2-533
Minimum Cycle Time                               2.50 ns at CAS 6 (tCK min)
                                                 3.00 ns at CAS 5
                                                 3.75 ns at CAS 4
Maximum Access Time                              0.40 ns at CAS 6 (tAC)
                                                 0.45 ns at CAS 5
                                                 0.50 ns at CAS 4
Maximum Cycle Time (tCK max)                     8.00 ns (DDR2-250)

---=== Timings at Standard Speeds ===---
tCL-tRCD-tRP-tRAS as DDR2-800                    6-6-6-18
tCL-tRCD-tRP-tRAS as DDR2-666                    5-5-5-15
tCL-tRCD-tRP-tRAS as DDR2-533                    4-4-4-12
tCL-tRCD-tRP-tRAS as DDR2-400                    4-3-3-9

---=== Timing Parameters ===---
Address/Command Setup Time Before Clock (tIS)    0.17 ns
Address/Command Hold Time After Clock (tIH)      0.25 ns
Data Input Setup Time Before Strobe (tDS)        0.05 ns
Data Input Hold Time After Strobe (tDH)          0.12 ns
Minimum Row Precharge Delay (tRP)                15.00 ns
Minimum Row Active to Row Active Delay (tRRD)    7.50 ns
Minimum RAS# to CAS# Delay (tRCD)                15.00 ns
Minimum RAS# Pulse Width (tRAS)                  45.00 ns
Write Recovery Time (tWR)                        15.00 ns
Minimum Write to Read CMD Delay (tWTR)           7.50 ns
Minimum Read to Pre-charge CMD Delay (tRTP)      7.50 ns
Minimum Active to Auto-refresh Delay (tRC)       60.00 ns
Minimum Recovery Delay (tRFC)                    127.50 ns
Maximum DQS to DQ Skew (tDQSQ)                   0.20 ns
Maximum Read Data Hold Skew (tQHS)               0.30 ns

---=== Manufacturing Information ===---
Manufacturer                                     Transcend Information
Manufacturing Location Code                      T
Part Number                                      JM4GDDR2-8K
Manufacturing Date                               2010-W07
Assembly Serial Number                           0x00085FAF
```

読めたね！

次回は、いよいよ DRAM 初期化をしていこう。といっても、私が結局理解できなかったのでインチキして初期化する。

続き: https://qiita.com/tanakmura/items/57f1e39f090299354ccf

# まとめ

Q. DRAM を搭載した DIMM に記録されたデータは電源を切ると消えてしまう、○か×か
A. ×、DIMM には不揮発メモリも搭載されており、消えないデータもある (論理破壊)