Oracle® Solaris 11.2 デバイスドライバの記述

印刷ビューの終了

更新: 2014 年 9 月
 
 

エラー処理

このセクションでは、ドライバの内部で入出力障害サービスの API を使用してエラーを処理する方法について説明します。このセクションでは、ドライバで障害管理機能を指定および初期化する方法、エラーレポートを生成する方法、および、ドライバのエラーハンドラルーチンを登録する方法を説明します。

FMA エラーレポートリモート測定を提供するように設計されたドライバは、エラーを検出し、それらのエラーがドライバによって提供されるサービスに及ぼす影響を判断します。ドライバが、エラーの検出後にそのサービスが影響を受けたタイミングと影響の程度を特定するようにしてください。

    入出力ドライバは、検出されたエラーにただちに応答する必要があります。適切な応答には次のものがあります。

  • 回復を試みる

  • 入出力トランザクションを再試行する

  • フェイルオーバー手法を試みる

  • 呼び出し側のアプリケーション/スタックにエラーを報告する

  • ほかのどの方法によってもエラーを抑制できない場合、パニック状態に移行する

ドライバによって検出されたエラーは ereport として障害管理デーモンに伝達されます。ereport は、FMA イベントプロトコルによって定義された構造化イベントです。イベントプロトコルは各種の共通データフィールドの仕様であり、障害疑いのリストだけでなく、すべてのエラーイベントおよびフォルトイベントはこのプロトコルを使用して記述する必要があります。ereport は収集されてエラーリモート測定のフローにまとめられ、診断エンジンにディスパッチされます。

障害管理機能の宣言

強化されたデバイスドライバでは、その障害管理機能を入出力障害管理フレームワークに宣言する必要があります。ドライバの障害管理機能を宣言するには、ddi_fm_init (9F) 関数を使用します。

void ddi_fm_init(dev_info_t *dip, int *fmcap, ddi_iblock_cookie_t *ibcp)

ddi_fm_init() 関数は、カーネルコンテキストからドライバの attach (9E) またはdetach (9E) エントリポイントで呼び出すことができます。ddi_fm_init() 関数は通常、attach() エントリポイントから呼び出されます。ddi_fm_init() 関数は、fmcap に従ってリソースを割り当てて初期化します。fmcap パラメータには、次に示す障害管理機能のビット単位の論理和を設定する必要があります。

  • DDI_FM_EREPORT_CAPABLE - ドライバはエラー条件を検出したときに FMA プロトコルのエラーイベント (ereport) を生成する必要があり、そのための能力を備えています。

  • DDI_FM_ACCCHK_CAPABLE - ドライバは 1 つ以上のアクセス入出力トランザクションが完了したときにエラーをチェックする必要があり、そのための能力を備えています。

  • DDI_FM_DMACHK_CAPABLE - ドライバは 1 つ以上の DMA 入出力トランザクションが完了したときにエラーをチェックする必要があり、そのための能力を備えています。

  • DDI_FM_ERRCB_CAPABLE - ドライバはエラーコールバック関数を備えています。

強化されたリーフドライバでは通常、これらすべての機能を設定します。ただし、そのドライバの親ネクサスが要求された機能をどれもサポートしない場合、機能に関連付けられたビットがクリアされてからドライバに返されます。ddi_fm_init(9F) から戻る前に、一連の障害管理機能プロパティー (fm-ereport-capable、fm-accchk-capable、fm-dmachk-capable、および fm-errcb-capable) を作成します。現在サポートされている障害管理機能のレベルは、prtconf (1M) コマンドを使用すると確認できます。

管理者が選択した障害管理機能をドライバでサポートするには、driver.conf (4) ファイルで障害管理機能レベルのプロパティーをエクスポートし、先に説明した値を設定します。fm-capable プロパティーの設定および読み取りは、目的の機能リストを指定して ddi_fm_init() を呼び出す前に行われる必要があります。

bge ドライバからの次の例では bge_fm_init() 関数が示されていますが、この関数から ddi_fm_init(9F) 関数が呼び出されています。bge_fm_init() 関数は bge_attach() 関数内で呼び出されます。

static void
bge_fm_init(bge_t *bgep)
{
        ddi_iblock_cookie_t iblk;

        /* Only register with IO Fault Services if we have some capability */
        if (bgep->fm_capabilities) {
                bge_reg_accattr.devacc_attr_access = DDI_FLAGERR_ACC;
                dma_attr.dma_attr_flags = DDI_DMA_FLAGERR;
                /* 
                 * Register capabilities with IO Fault Services
                 */
                ddi_fm_init(bgep->devinfo, &bgep->fm_capabilities, &iblk);
                /*
                 * Initialize pci ereport capabilities if ereport capable
                 */
                if (DDI_FM_EREPORT_CAP(bgep->fm_capabilities) ||
                    DDI_FM_ERRCB_CAP(bgep->fm_capabilities))
                        pci_ereport_setup(bgep->devinfo);
                /*
                 * Register error callback if error callback capable
                 */
                if (DDI_FM_ERRCB_CAP(bgep->fm_capabilities))
                        ddi_fm_handler_register(bgep->devinfo,
                        bge_fm_error_cb, (void*) bgep);
        } else {
                /*
                 * These fields have to be cleared of FMA if there are no
                 * FMA capabilities at runtime.
                 */
                bge_reg_accattr.devacc_attr_access = DDI_DEFAULT_ACC;
                dma_attr.dma_attr_flags = 0;
        }
}

障害管理リソースのクリーンアップ

ddi_fm_fini(9F) 関数は、dip の障害管理をサポートするために割り当てられたリソースをクリーンアップします。

void ddi_fm_fini(dev_info_t *dip)

ddi_fm_fini() 関数は、カーネルコンテキストからドライバの attach(9E) またはdetach (9E) エントリポイントで呼び出すことができます。

bge ドライバからの次の例では bge_fm_fini() 関数が示されていますが、この関数から ddi_fm_fini(9F) 関数が呼び出されています。bge_fm_fini() 関数は bge_unattach() 関数内で呼び出され、この関数は bge_attach() および bge_detach() の両関数内で呼び出されます。

static void
bge_fm_fini(bge_t *bgep)
{
        /* Only unregister FMA capabilities if we registered some */
        if (bgep->fm_capabilities) {
                /*
                 * Release any resources allocated by pci_ereport_setup()
                 */
                if (DDI_FM_EREPORT_CAP(bgep->fm_capabilities) ||
                    DDI_FM_ERRCB_CAP(bgep->fm_capabilities))
                        pci_ereport_teardown(bgep->devinfo);
                /*
                 * Un-register error callback if error callback capable
                 */
                if (DDI_FM_ERRCB_CAP(bgep->fm_capabilities))
                        ddi_fm_handler_unregister(bgep->devinfo);
                /*
                 * Unregister from IO Fault Services
                 */
                ddi_fm_fini(bgep->devinfo);
        }
}

障害管理機能ビットマスクの取得

ddi_fm_capable(9F) 関数は、dip に現在設定されている機能ビットマスクを返します。

void ddi_fm_capable(dev_info_t *dip)

エラーの報告

    このセクションでは、次のトピックについての情報を提供します。

  • Queueing an Error Eventでは、エラーイベントをキューに入れる方法について説明します。

  • Detecting and Reporting PCI-Related Errorsでは、PCI 関連のエラーを報告する方法について説明します。

  • Reporting Standard I/O Controller Errorsでは、標準入出力コントローラのエラーを報告する方法について説明します。

  • Service Impact Functionでは、デバイスによって提供されるサービスにエラーが影響を及ぼしたかどうかを報告する方法について説明します。

キューへのエラーイベントの送信

ddi_fm_ereport_post(9F) 関数は、障害管理デーモン fmd(1M) に配信する ereport イベントをキューに入れます。

void ddi_fm_ereport_post(dev_info_t *dip, 
                         const char *error_class, 
                         uint64_t ena, 
                         int sflag, ...)

sflag パラメータは、システムメモリーリソースおよびイベントチャネルリソースが利用可能になるまで呼び出し側が待機するかどうかを示します。

ENA はこのエラーレポートに対するエラー数値関連付け (Error Numeric Association) を意味します。ENA は、バスネクサスドライバのような別のエラー検出ソフトウェアモジュールから初期化および取得されている可能性があります。ENA を 0 に設定した場合、ENA は ddi_fm_ereport_post() によって初期化されます。

名前-値ペア (nvpair) 変数の引数リストの内容は、配列以外の data_type_t 型の場合は 1 つ以上の名前、型、値ポインタの nvpair 組であり、data_type_t 配列型の場合は 1 つ以上の名前、型、要素数、値ポインタの組です。nvpair 組は、診断のために必要な ereport イベントペイロードを構成します。引数リストの終了は NULL によって指定されます。

error_class には、入出力コントローラに関してReporting Standard I/O Controller Errorsで説明されている ereport クラス名およびペイロードが適宜使用されます。その他の ereport クラス名およびペイロードも定義できますが、これらは、ドライバ固有の診断エンジンソフトウェアまたは Eversholt フォルトツリー (eft) ルールとともに Oracle イベントレジストリに登録する必要があります。

void
bge_fm_ereport(bge_t *bgep, char *detail)
{
        uint64_t ena;
        char buf[FM_MAX_CLASS];
        (void) snprintf(buf, FM_MAX_CLASS, "%s.%s", DDI_FM_DEVICE, detail);
        ena = fm_ena_generate(0, FM_ENA_FMT1);
        if (DDI_FM_EREPORT_CAP(bgep->fm_capabilities)) {
                ddi_fm_ereport_post(bgep->devinfo, buf, ena, DDI_NOSLEEP,
                    FM_VERSION, DATA_TYPE_UINT8, FM_EREPORT_VERS0, NULL);
        }
}
PCI 関連エラーの検出と報告

pci_ereport_post (9F) を使用する場合、PCI、PCI-X、PCI-E を含む PCI 関連のエラーが自動的に検出および報告されます。

void pci_ereport_post(dev_info_t *dip, ddi_fm_error_t *derr, uint16_t *xx_status)

PCI ローカルバス構成ステータスレジスタで発生するエラーについては、ドライバ固有の ereport をドライバで生成する必要はありません。pci_ereport_post() 関数は、データパリティーエラー、マスターアボート、ターゲットアボート、シグナル付きシステムエラーなどを報告できます。

ドライバ内で pci_ereport_post() を使用する場合は、ドライバの attach(9E) ルーチンで pci_ereport_setup(9F) が事前に呼び出されており、なおかつ、使用後にドライバの detach(9E) ルーチンで pci_ereport_teardown(9F) が呼び出される必要があります。

次に示す bge ドライバのコード例では、ドライバのエラーハンドラから pci_ereport_post() 関数を呼び出しています。

/*
 * The I/O fault service error handling callback function
 */
/*ARGSUSED*/
static int
bge_fm_error_cb(dev_info_t *dip, ddi_fm_error_t *err, const void *impl_data)
{
     /*
      * as the driver can always deal with an error 
      * in any dma or access handle, we can just return 
      * the fme_status value.
      */
     pci_ereport_post(dip, err, NULL);
     return (err->fme_status);
}
標準入出力コントローラのエラーの報告

入出力コントローラでよく発生するエラーに対応した、デバイス ereport の標準セットが定義されています。これらの ereport は、このセクションで説明するいずれかのエラー症状が検出されるたびに生成されます。

このセクションで説明する ereport は、標準ルールの共通セットを使用して ereport を診断する eft 診断エンジンにディスパッチされます。その他のエラーをデバイスドライバで検出する場合、そのエラーは ereport イベントの形式で、デバイス固有の診断ソフトウェアまたは eft ルールとともに Sun イベントレジストリで定義されている必要があります。

DDI_FM_DEVICE_INVAL_STATE

ドライバはデバイスが無効な状態であることを検出しました。

ドライバでは、送信または受信するデータが無効と思われることを検出した時点でエラーを送信するようにしてください。たとえば、bge のコード内の bge_chip_reset() および bge_receive_ring() ルーチンは、無効なデータを検出した時点で ereport.io.device.inval_state エラーを生成します。

/*
 * The SEND INDEX registers should be reset to zero by the
 * global chip reset; if they're not, there'll be trouble
 * later on.
 */
sx0 = bge_reg_get32(bgep, NIC_DIAG_SEND_INDEX_REG(0));
if (sx0 != 0) {
    BGE_REPORT((bgep, "SEND INDEX - device didn't RESET"));
    bge_fm_ereport(bgep, DDI_FM_DEVICE_INVAL_STATE);
    return (DDI_FAILURE);
}
/* ... */
/*
 * Sync (all) the receive ring descriptors
 * before accepting the packets they describe
 */
DMA_SYNC(rrp->desc, DDI_DMA_SYNC_FORKERNEL);
if (*rrp->prod_index_p >= rrp->desc.nslots) {
    bgep->bge_chip_state = BGE_CHIP_ERROR;
    bge_fm_ereport(bgep, DDI_FM_DEVICE_INVAL_STATE);
    return (NULL);
}
DDI_FM_DEVICE_INTERN_CORR

デバイスは自己訂正内部エラーを報告しました。たとえば、デバイスの内部バッファーで、訂正可能な ECC エラーがハードウェアによって検出されました。bge ドライバではこのエラーフラグは使用されていません。

DDI_FM_DEVICE_INTERN_UNCORR

デバイスは訂正不能な内部エラーを報告しました。たとえば、デバイスの内部バッファーで、訂正不能な ECC エラーがハードウェアによって検出されました。

bge ドライバではこのエラーフラグは使用されていません。

DDI_FM_DEVICE_STALL

ドライバは、データ転送が予期せずストールしたことを検出しました。

bge_factotum_stall_check() ルーチンは、ストール検出の例を示しています。

dogval = bge_atomic_shl32(&bgep->watchdog, 1);
if (dogval < bge_watchdog_count)
    return (B_FALSE);

BGE_REPORT((bgep, "Tx stall detected, 
watchdog code 0x%x", dogval));
bge_fm_ereport(bgep, DDI_FM_DEVICE_STALL);
return (B_TRUE);
DDI_FM_DEVICE_NO_RESPONSE

デバイスはドライバのコマンドに応答していません。

bge_chip_poll_engine(bge_t *bgep, bge_regno_t regno,
        uint32_t mask, uint32_t val)
{
        uint32_t regval;
        uint32_t n;

        for (n = 200; n; --n) {
                regval = bge_reg_get32(bgep, regno);
                if ((regval & mask) == val)
                        return (B_TRUE);
                drv_usecwait(100);
        }
        bge_fm_ereport(bgep, DDI_FM_DEVICE_NO_RESPONSE);
        return (B_FALSE);
}
DDI_FM_DEVICE_BADINT_LIMIT

デバイスが連続で発生させた無効な割り込みの数が多すぎます。

bge ドライバの bge_intr() ルーチンは、溜まっている割り込みを検出する例を示しています。bge_fm_ereport() 関数は ddi_fm_ereport_post(9F) 関数のラッパーです。Queueing an Error Eventbge_fm_ereport() の例を参照してください。

if (bgep->missed_dmas >= bge_dma_miss_limit) {
    /*
     * If this happens multiple times in a row,
     * it means DMA is just not working.  Maybe
     * the chip has failed, or maybe there's a
     * problem on the PCI bus or in the host-PCI
     * bridge (Tomatillo).
     *
     * At all events, we want to stop further
     * interrupts and let the recovery code take
     * over to see whether anything can be done
     * about it ...
     */
    bge_fm_ereport(bgep,
        DDI_FM_DEVICE_BADINT_LIMIT);
    goto chip_stop;
}
サービス影響関数

障害管理機能を備えたドライバは、デバイスによって提供されるサービスにエラーが影響を及ぼしたかどうかを示す必要があります。エラーの検出 (および、必要に応じたサービスのシャットダウン) 後に、ドライバが ddi_fm_service_impact(9F) ルーチンを呼び出して、デバイスインスタンスの現在のサービス状態を反映するようにしてください。診断および回復ソフトウェアでは、サービス状態を利用することにより、問題の識別または問題への対応が容易になります。

ドライバ自体がエラーを検出したときも、フレームワークがエラーを検出してアクセスハンドルまたは DMA ハンドルに障害のマークを付けたときも、ddi_fm_service_impact() ルーチンが呼び出されるようにしてください。

void ddi_fm_service_impact(dev_info_t *dip, int svc_impact)

ddi_fm_service_impact() に渡すことのできるサービス影響値 (svc_impact) は次のとおりです。

DDI_SERVICE _LOST

デバイスによって提供されるサービスが、デバイスの障害またはソフトウェアの不具合のために利用できません。

DDI_SERVICE _DEGRADED

ドライバは通常のサービスを提供できませんが、部分的または縮退レベルのサービスを提供できます。たとえば、ドライバは目的の操作が成功するまで操作を繰り返し試行する必要があるか、または構成された速度よりも低速に動作しています。

DDI_SERVICE _UNAFFECTED

ドライバはエラーを検出しましたが、デバイスインスタンスによって提供されるサービスは影響を受けません。

DDI_SERVICE _RESTORED

デバイスのすべてのサービスは復元されました。

ddi_fm_service_impact() を呼び出すと、サービス影響ルーチンに渡されたサービス影響引数に基づいて、ドライバの代わりに次の ereport が生成されます。

  • ereport.io.service.lost

  • ereport.io.service.degraded

  • ereport.io.service.unaffected

  • ereport.io.service.restored

次に示す bge のコードでは、ドライバはエラーの結果としてパケットの送受信を正常に再開できないと判断します。デバイスのサービス状態は DDI_SERVICE_LOST に遷移します。

/*
 * All OK, reinitialize hardware and kick off GLD scheduling
 */
mutex_enter(bgep->genlock);
if (bge_restart(bgep, B_TRUE) != DDI_SUCCESS) {
    (void) bge_check_acc_handle(bgep, bgep->cfg_handle);
    (void) bge_check_acc_handle(bgep, bgep->io_handle);
    ddi_fm_service_impact(bgep->devinfo, DDI_SERVICE_LOST);
    mutex_exit(bgep->genlock);
    return (DDI_FAILURE);
}

注 - 登録済みのコールバックルーチンから ddi_fm_service_impact() 関数を呼び出さないでください。