本書では「オレオレイーサネットフレーム」として任意のバイナリ列をNICから送信する方法を紹介しました。後は「オレオレ」ではなくちゃんとしたイーサネットフレームのバイナリ列を作ればイーサネットフレームでの通信を行うことができます。
ただ、イーサネットフレームを作るためには最低限、自身のNICのMACアドレスを知る必要があります。ここでは、NIC内に保存されているMACアドレスの取得方法を紹介します。
たいていの場合、MACアドレスはNICのEEPROMから取得できます。ここでは、NICのEEPROMへのアクセスを試します。
この項のサンプルディレクトリは「A02_get_mac_01」です。
NICのEEPROMの値の取得には「EEPROM Read Register(EERD:オフセット0x0014)」という専用のレジスタがあります(図A.1*1)。
図A.1: EERDレジスタについて
[*1] 13.4.4 EEPROM Read Register - PCI/PCI-X Family of Gigabit Ethernet Controllers Software Developer's Manual
また、EERDレジスタの各ビットフィールドの意味は以下の通りです。
以上から、EERDを使用したEEPROMアクセスの流れは以下の通りです。
そして、EEPROMのアドレスマップは図A.2*2の通りです(MACアドレスの箇所のみ抜粋)。
図A.2: EEPROMのアドレスマップ(MACアドレスの箇所のみ)
[*2] 5.6 EEPROM Address Map - PCI/PCI-X Family of Gigabit Ethernet Controllers Software Developer's Manual
EEPROMの先頭(0x00バイト目)から2バイト(16ビット)ずつMACアドレスが並んでいます。
なお、NICによっては(PCによっては?)EEPROMは搭載されていないものなのか、EEPROMからの値取得が行えないものもあります。その際、2.のDONEビットがいつまで経っても設定されないので、どこかのタイミングでタイムアウトする必要があります。
それでは、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.3のようにEEPROMへのアクセスはできませんでした。
図A.3: A02_get_mac_01の実行結果
次項で別の方法を試してみます。
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アドレスを取得し、その結果を表示するようにしています。適宜参照してみてください。
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); }
実行すると、今度はMACアドレスを取得することができました。(実行結果画像は単にMACアドレスの表示が追加されただけなので省略)