Top

付録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)。

EERDレジスタについて

図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アクセスの流れは以下の通りです。

  1. ADDRへアクセスしたいEEPROMのアドレスと、STARTビットを設定
  2. DONEビットが設定されるのを待つ
  3. DATAフィールドに格納されたEEPROMの値を取得

そして、EEPROMのアドレスマップは図A.2*2の通りです(MACアドレスの箇所のみ抜粋)。

EEPROMのアドレスマップ(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ビットがいつまで経っても設定されないので、どこかのタイミングでタイムアウトする必要があります。

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へのアクセスはできませんでした。

A02_get_mac_01の実行結果

図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アドレスの表示が追加されただけなので省略)


Top