付録A MACアドレスを取得する
本書では「オレオレイーサネットフレーム」として任意のバイナリ列をNICから送信する方法を紹介しました。後は「オレオレ」ではなくちゃんとしたイーサネットフレームのバイナリ列を作ればイーサネットフレームでの通信を行うことができます。
ただ、イーサネットフレームを作るためには最低限、自身のNICのMACアドレスを知る必要があります。ここでは、NIC内に保存されているMACアドレスの取得方法を紹介します。
A.1 EEPROMへのアクセスを試す
たいていの場合、MACアドレスはNICのEEPROMから取得できます。ここでは、NICのEEPROMへのアクセスを試します。
この項のサンプルディレクトリは「A02_get_mac_01」です。
A.1.1 EERDの使い方
NICのEEPROMの値の取得には「EEPROM Read Register(EERD:オフセット0x0014)」という専用のレジスタがあります(図A.1*1)。
図A.1: EERDレジスタについて
また、EERDレジスタの各ビットフィールドの意味は以下の通りです。
- START
- 1を設定すると、NICがEEPROMのADDRフィールドのアドレスの場所からデータを読み出し、DATAフィールドへ格納する
- このビットは自動的に0に戻る
- DONE
- EEPROMのデータ読み出しを完了すると1が設定される
- NICが自動的に設定するビットで、ソフトウェアからの書き込みは無効
- ADDR
- DATA
- EEPROMから読み出したデータが格納されるフィールド
以上から、EERDを使用したEEPROMアクセスの流れは以下の通りです。
- ADDRへアクセスしたいEEPROMのアドレスと、STARTビットを設定
- DONEビットが設定されるのを待つ
- DATAフィールドに格納されたEEPROMの値を取得
そして、EEPROMのアドレスマップは図A.2*2の通りです(MACアドレスの箇所のみ抜粋)。
図A.2: EEPROMのアドレスマップ(MACアドレスの箇所のみ)
EEPROMの先頭(0x00バイト目)から2バイト(16ビット)ずつMACアドレスが並んでいます。
なお、NICによっては(PCによっては?)EEPROMは搭載されていないものなのか、EEPROMからの値取得が行えないものもあります。その際、2.のDONEビットがいつまで経っても設定されないので、どこかのタイミングでタイムアウトする必要があります。
A.1.2 EEPROMへのアクセスを実装してみる
それでは、EEPROMへのアクセスを試してみます。
与えられたEEPROMアドレスの値を取得する関数「get_eeprom_data」をnic.cへ追加します(リストA.1)。
リストA.1: A02_get_mac_01/nic.c
/* ・・・ 省略 ・・・ */
static void disable_nic_interrupt(void)
{
/* ・・・ 省略 ・・・ */
}
/* 追加(ここから) */
#define EERD_TIMEOUT 10000
/* 1万回分のEERD読み出し処理を経てもDONEビットが立たない場合
* タイムアウトとする */
static int get_eeprom_data(unsigned char eeprom_addr)
{
/* アクセスしたいEEPROMアドレスとSTARTビットをセット */
set_nic_reg(NIC_REG_EERD,
(eeprom_addr << NIC_EERD_ADDRESS_SHIFT) | NIC_EERD_START);
/* DONEビットが設定されるのをタイムアウト付きで待つ */
volatile unsigned int wait = EERD_TIMEOUT;
while (wait--) {
unsigned int eerd = get_nic_reg(NIC_REG_EERD);
if (eerd & NIC_EERD_DONE) {
/* DONEビットが設定されたら
* EERDに格納されたデータ(上位16ビット)を返す */
return eerd >> NIC_EERD_DATA_SHIFT;
}
}
/* タイムアウトの際は-1を返す */
return -1;
}
/* 追加(ここまで) */
/* ・・・ 省略 ・・・ */
やっている内容は前項で説明の通りです。タイムアウトの際は-1を返すようにしてみました。
「EERD_TIMEOUT」の定数値に特に理由は無いので、適宜変えてみてください。最初に試す際は「本当にいつまで経ってもDONEは設定されないのか」を確認する意味でも莫大な値か、あるいはタイムアウト無しで試してみると良いと思います。
そして、NIC初期化時に呼ばれてMACアドレス取得を行う関数「get_mac_addr」も用意しておきます(リストA.2)。
リストA.2: A02_get_mac_01/nic.c
/* ・・・ 省略 ・・・ */
static int get_eeprom_data(unsigned char eeprom_addr)
{
/* ・・・ 省略 ・・・ */
}
/* 追加(ここから) */
static void get_mac_addr(void)
{
unsigned char eeprom_accessible = get_eeprom_data(0x00) >= 0;
if (eeprom_accessible) {
puts("EEPROM ACCESSIBLE\r\n");
} else {
puts("EEPROM NOT ACCESSIBLE\r\n");
}
while (1);
}
/* 追加(ここまで) */
/* ・・・ 省略 ・・・ */
まずはEEPROMへのアクセスがタイムアウトするか否かから、EEPROMへのアクセスが可能か否かを出力するようにしてみました。
また、ここではMACアドレス取得実験のため、get_mac_addr()の最後で無限ループにより処理が進まないようにしています。
最後にnic_init()から呼び出すようにすれば作業完了です(リストA.3)。
リストA.3: A02_get_mac_01/nic.c
/* ・・・ 省略 ・・・ */
void nic_init(void)
{
/* NICのレジスタのベースアドレスを取得しておく */
nic_reg_base = get_nic_reg_base();
/* NICの割り込みを全て無効にする */
disable_nic_interrupt();
/* 追加(ここから) */
/* MACアドレスを取得 */
get_mac_addr();
/* 追加(ここまで) */
/* 受信の初期化処理 */
rx_init();
/* 送信の初期化処理 */
tx_init();
}
unsigned int get_nic_reg_base(void)
/* ・・・ 省略 ・・・ */
A.1.3 動作確認
筆者の環境では、実行すると図A.3のようにEEPROMへのアクセスはできませんでした。
図A.3: A02_get_mac_01の実行結果
次項で別の方法を試してみます。
A.1.4 補足: EEPROMアクセスができた場合のMACアドレス取得方法
EEPROMへのアクセスができた場合、get_eeprom_data()を使用してリストA.4の様にMACアドレスを取得できます。(「nic_mac_addr」は、グローバル変数として定義されたunsigned char型の配列とします。)
リストA.4: EEPROMからMACアドレスを取得する関数例
static void get_mac_addr_eeprom(void)
{
unsigned short mac_1_0 = (unsigned short)get_eeprom_data(0x00);
unsigned short mac_3_2 = (unsigned short)get_eeprom_data(0x01);
unsigned short mac_5_4 = (unsigned short)get_eeprom_data(0x02);
nic_mac_addr[0] = mac_1_0 & 0x00ff;
nic_mac_addr[1] = mac_1_0 >> 8;
nic_mac_addr[2] = mac_3_2 & 0x00ff;
nic_mac_addr[3] = mac_3_2 >> 8;
nic_mac_addr[4] = mac_5_4 & 0x00ff;
nic_mac_addr[5] = mac_5_4 >> 8;
}
EEPROMの先頭から2バイトずつ取り出し、1バイトずつnic_mac_addr[]へ格納しています。
A02_get_mac_01のサンプルディレクトリでは、get_mac_addr()でEEPROMにアクセス可能な場合、get_mac_addr_eeprom()を呼び出してMACアドレスを取得し、その結果を表示するようにしています。適宜参照してみてください。
A.2 受信アドレスレジスタから取得する
EERDレジスタによるEEPROMからの値取得とは別の方法として、「受信アドレスレジスタ」を使用する方法を紹介します。
この項のサンプルディレクトリは「A02_get_mac_02」です。
「受信アドレスレジスタ」は、受信したフレームをMACアドレスでフィルタリングするためのレジスタです。MACアドレスの下位32ビット(4バイト)を登録する「Receive Address Low(RAL)」と、上位16ビット(2バイト)を登録する「Receive Address High」の2つのレジスタがあり、それぞれ16個ずつあるので、フィルタリングするアドレスを16個までNICへ登録できる仕組みです。
ここで、受信アドレスレジスタの先頭の要素には自身のNICのMACアドレスを設定することが決められています。NICによるのかUEFIによるのかは不明ですが、この値が自動的に設定されている場合、ここからNICのMACアドレスを知ることができます。
受信アドレスレジスタを使用する場合のMACアドレス取得関数「get_mac_addr_rar」はリストA.5の通りです。
リストA.5: a02_get_mac_02/nic.c
/* ・・・ 省略 ・・・ */
unsigned char nic_mac_addr[6] = { 0 }; /* 追加 */
/* ・・・ 省略 ・・・ */
/* 追加(ここから) */
static void get_mac_addr_rar(void)
{
unsigned int ral_0 = get_nic_reg(NIC_REG_RAL(0));
unsigned int rah_0 = get_nic_reg(NIC_REG_RAH(0));
nic_mac_addr[0] = ral_0 & 0x000000ff;
nic_mac_addr[1] = (ral_0 >> 8) & 0x000000ff;
nic_mac_addr[2] = (ral_0 >> 16) & 0x000000ff;
nic_mac_addr[3] = (ral_0 >> 24) & 0x000000ff;
nic_mac_addr[4] = rah_0 & 0x000000ff;
nic_mac_addr[5] = (rah_0 >> 8) & 0x000000ff;
}
/* 追加(ここまで) */
static void get_mac_addr(void)
/* ・・・ 省略 ・・・ */
「NIC_REG_RAL」と「NIC_REG_RAH」は、それぞれ、N番目のレジスタのアドレスを表すマクロとしてinclude/nic.hへ定義しています。
RALとRAHそれぞれ0番目の値を取得し、RALに格納されているMACアドレス下位4バイトと、RAHに格納されているMACアドレス上位2バイトをnic_mac_addr[]へ格納しています。
最後にget_mac_addr()からget_mac_addr_rar()を呼び出し、結果を出力するようにしてみます(リストA.6)。
リストA.6: A02_get_mac_02/nic.c
static void get_mac_addr(void)
{
unsigned char eeprom_accessible = get_eeprom_data(0x00) >= 0;
if (eeprom_accessible) {
puts("EEPROM ACCESSIBLE\r\n");
get_mac_addr_eeprom();
} else {
puts("EEPROM NOT ACCESSIBLE\r\n");
get_mac_addr_rar(); /* 追加 */
}
/* 追加(ここから) */
unsigned char i;
for (i = 0; i < 6; i++) {
puth(nic_mac_addr[i], 2);
putc(' ');
}
/* 追加(ここまで) */
while (1);
}
A.2.1 動作確認
実行すると、今度はMACアドレスを取得することができました。(実行結果画像は単にMACアドレスの表示が追加されただけなので省略)