root/usr/src/cmd/fm/modules/common/fabric-xlate/fx_fire.c
/*
 * CDDL HEADER START
 *
 * The contents of this file are subject to the terms of the
 * Common Development and Distribution License (the "License").
 * You may not use this file except in compliance with the License.
 *
 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
 * or http://www.opensolaris.org/os/licensing.
 * See the License for the specific language governing permissions
 * and limitations under the License.
 *
 * When distributing Covered Code, include this CDDL HEADER in each
 * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
 * If applicable, add the following below this CDDL HEADER, with the
 * fields enclosed by brackets "[]" replaced with your own identifying
 * information: Portions Copyright [yyyy] [name of copyright owner]
 *
 * CDDL HEADER END
 */

/*
 * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
 * Use is subject to license terms.
 */
#include <strings.h>
#include <sys/fm/io/sun4_fire.h>

#include "fabric-xlate.h"

typedef struct fab_fire_tbl {
        const char      *err_class;
        uint32_t        fire_bit;       /* Fire error bit */
        uint16_t        pci_err_sts;    /* Equivalent PCI Error Status */
        uint16_t        pci_bdg_sts;    /* Equivalent PCI Bridge Status */
} fab_fire_tbl_t;

/*
 * Translation tables for converting fire error bits into "pci" ereports.
 * <Fire Bit>
 * <pci ereport Class>
 * <pci error status reg>
 * <pci bridge status reg>
 * <pci target class>
 */
#define FAB_FIRE_PEC_BIT(fb) "ereport.io." PCIEX_FIRE "." FIRE_PEC_ ## fb
#define FAB_FIRE_DMC_BIT(fb) "ereport.io." PCIEX_FIRE "." FIRE_DMC_ ## fb
#define FAB_N2_DMU_BIT(fb) "ereport.io.n2.dmu." fb
#define FAB_OB_PEC_BIT(fb) "ereport.io." PCIEX_OBERON "." FIRE_PEC_ ## fb

#define FAB_FIRE_UE(fb, bit, sts, bdg) \
        FAB_FIRE_PEC_BIT(fb), PCIE_AER_UCE_ ## bit, sts, bdg
#define FAB_OB_UE(fb, bit, sts, bdg) \
        FAB_OB_PEC_BIT(fb), PCIE_AER_UCE_ ## bit, sts, bdg
static fab_fire_tbl_t fab_fire_pec_ue_tbl[] = {
        FAB_FIRE_UE(UR,  UR,       PCI_STAT_S_SYSERR,   0),
        FAB_FIRE_UE(UC,  UC,       PCI_STAT_S_SYSERR,   0),
        FAB_OB_UE(ECRC,  ECRC,     PCI_STAT_S_SYSERR,   0),
        FAB_FIRE_UE(CTO, TO,       PCI_STAT_S_SYSERR,   0),
        FAB_FIRE_UE(ROF, RO,       PCI_STAT_S_SYSERR,   0),
        FAB_FIRE_UE(MFP, MTLP,     PCI_STAT_S_SYSERR,   0),
        FAB_FIRE_UE(PP,  PTLP,     PCI_STAT_S_PERROR,
            (PCI_STAT_S_SYSERR | PCI_STAT_PERROR)),
        FAB_FIRE_UE(FCP, FCP,      PCI_STAT_S_SYSERR,   0),
        FAB_FIRE_UE(DLP, DLP,      PCI_STAT_S_SYSERR,   0),
        FAB_FIRE_UE(TE,  TRAINING, PCI_STAT_S_SYSERR,   0),
        FAB_FIRE_UE(CA,  CA,       PCI_STAT_S_TARG_AB,
            PCI_STAT_S_TARG_AB),
        NULL, 0, 0,
};

#define FAB_FIRE_CE(fb, bit) \
        FAB_FIRE_PEC_BIT(fb), PCIE_AER_CE_ ## bit, 0, 0
static fab_fire_tbl_t fab_fire_pec_ce_tbl[] = {
        FAB_FIRE_CE(RTO,        REPLAY_TO),
        FAB_FIRE_CE(RNR,        REPLAY_ROLLOVER),
        FAB_FIRE_CE(BDP,        BAD_DLLP),
        FAB_FIRE_CE(BTP,        BAD_TLP),
        FAB_FIRE_CE(RE,         RECEIVER_ERR),
        NULL, 0, 0,
};

/*
 * WUC/RUC will need to be special cased for the target ereports, because you
 * need to decode the tlp log.
 */
#define FAB_FIRE_WUCRUC(fb) \
        FAB_FIRE_PEC_BIT(fb), 0, 0, (PCI_STAT_R_MAST_AB | PCI_STAT_S_SYSERR)
#define FAB_FIRE_OE(fb, bit) \
        FAB_FIRE_PEC_BIT(fb), PCIE_AER_UCE_ ## bit, PCI_STAT_S_SYSERR, 0
#define FAB_OB_OE(fb, bit) \
        FAB_FIRE_PEC_BIT(fb), PCIE_AER_UCE_ ## bit, PCI_STAT_S_SYSERR, 0
static fab_fire_tbl_t fab_fire_pec_oe_tbl[] = {
        FAB_FIRE_WUCRUC(WUC),
        FAB_FIRE_WUCRUC(RUC),
        FAB_FIRE_OE(ERU, DLP),
        FAB_FIRE_OE(ERO, DLP),
        FAB_FIRE_OE(EMP, DLP),
        FAB_FIRE_OE(EPE, DLP),
        NULL, 0, 0,
};

#define FAB_FIRE_DMC(fb) \
        FAB_FIRE_DMC_BIT(fb), PCIE_AER_UCE_CA, 0, PCI_STAT_S_TARG_AB
#define FAB_N2_DMU(fb) \
        FAB_N2_DMU_BIT(fb), PCIE_AER_UCE_CA, 0, PCI_STAT_S_TARG_AB
static fab_fire_tbl_t fab_fire_dmc_tbl[] = {
        FAB_FIRE_DMC(BYP_ERR),
        FAB_FIRE_DMC(BYP_OOR),
        FAB_FIRE_DMC(TRN_OOR),
        FAB_FIRE_DMC(TTE_INV),
        FAB_FIRE_DMC(TTE_PRT),
        FAB_N2_DMU("iotsbdesc_inv"),
        FAB_N2_DMU("sun4v_adj_va_uf"),
        FAB_N2_DMU("sun4v_inv_pg_sz"),
        FAB_N2_DMU("sun4v_key_err"),
        FAB_N2_DMU("sun4v_va_oor"),
        NULL, 0, 0
};

/* ARGSUSED */
static void
fab_fire_to_data(fmd_hdl_t *hdl, nvlist_t *nvl, fab_data_t *data)
{
        data->nvl = nvl;

        /* Always Root Complex */
        data->dev_type = PCIE_PCIECAP_DEV_TYPE_ROOT;

        data->pcie_ue_sev = (PCIE_AER_UCE_DLP | PCIE_AER_UCE_SD |
            PCIE_AER_UCE_FCP | PCIE_AER_UCE_RO | PCIE_AER_UCE_MTLP);
}

static int
fab_xlate_fire_ce(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt,
    const char *class)
{
        fab_fire_tbl_t  *entry;
        uint64_t        reg;

        for (entry = fab_fire_pec_ce_tbl; entry->err_class; entry++) {
                if (STRCMP(class, entry->err_class))
                        goto send;
        }

        return (0);

send:
        fmd_hdl_debug(hdl, "Translate Fire CE %s\n", class);

        /* Fill in the device status register */
        data->pcie_err_status = PCIE_DEVSTS_CE_DETECTED;

        /* Fill in the AER CE register */
        if (nvlist_lookup_uint64(erpt, "tlu-cess", &reg) == 0) {
                data->pcie_ce_status = (uint32_t)reg | (uint32_t)(reg >> 32);
        }

        return (1);
}

static int
fab_xlate_fire_ue(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt,
    const char *class)
{
        fab_fire_tbl_t  *entry;
        uint64_t        reg;
        uint32_t        temp;
        pcie_tlp_hdr_t  *hdr;

        for (entry = fab_fire_pec_ue_tbl; entry->err_class; entry++) {
                if (STRCMP(class, entry->err_class))
                        goto send;
        }

        return (0);

send:
        fmd_hdl_debug(hdl, "Translate Fire UE %s\n", class);

        /* Fill in PCI Status Register */
        data->pci_err_status = entry->pci_err_sts;
        data->pci_bdg_sec_stat = entry->pci_bdg_sts;

        /* Fill in the device status register */
        if (entry->fire_bit & data->pcie_ue_sev)
                data->pcie_err_status = PCIE_DEVSTS_FE_DETECTED;
        else
                data->pcie_err_status = PCIE_DEVSTS_NFE_DETECTED;

        if (entry->fire_bit == PCIE_AER_UCE_UR)
                data->pcie_err_status |= PCIE_DEVSTS_UR_DETECTED;

        /* Fill in the AER UE register */
        if (nvlist_lookup_uint64(erpt, "tlu-uess", &reg) == 0) {
                data->pcie_ue_status = (uint32_t)reg | (uint32_t)(reg >> 32);
        }

        /* Fill in the AER Control register */
        if ((reg & (uint64_t)entry->fire_bit) &&
            nvlist_lookup_boolean(erpt, "primary")) {
                temp = entry->fire_bit;
                for (data->pcie_adv_ctl = (uint32_t)-1; temp;
                    data->pcie_adv_ctl++)
                        temp = temp >> 1;
        }

        /* If CTO create target information */
        if (entry->fire_bit == PCIE_AER_UCE_TO &&
            nvlist_lookup_boolean(erpt, "primary")) {
                if (nvlist_lookup_uint64(erpt, "tlu-tueh1l", &reg) == 0) {
                        data->pcie_ue_hdr[0] = (uint32_t)(reg >> 32);
                        data->pcie_ue_hdr[1] = (uint32_t)(reg);
                }
                if (nvlist_lookup_uint64(erpt, "tlu-tueh2l", &reg) == 0) {
                        data->pcie_ue_hdr[2] = (uint32_t)(reg >> 32);
                        data->pcie_ue_hdr[3] = (uint32_t)(reg);
                }

                hdr = (pcie_tlp_hdr_t *)(&data->pcie_ue_hdr[0]);
                switch (hdr->type) {
                case PCIE_TLP_TYPE_IO:
                case PCIE_TLP_TYPE_MEM:
                case PCIE_TLP_TYPE_MEMLK:
                        data->pcie_ue_tgt_trans = PF_ADDR_PIO;
                        if (hdr->fmt & 0x1) {
                                data->pcie_ue_tgt_addr = reg;
                        } else {
                                data->pcie_ue_tgt_addr = data->pcie_ue_hdr[2];
                        }
                        break;
                case PCIE_TLP_TYPE_CFG0:
                case PCIE_TLP_TYPE_CFG1:
                        data->pcie_ue_tgt_trans = PF_ADDR_CFG;
                        data->pcie_ue_tgt_bdf = data->pcie_ue_hdr[2] >> 16;
                        break;
                }
        }

        /* Fill in the AER Header registers */
        if (nvlist_lookup_uint64(erpt, "tlu-rueh1l", &reg) == 0) {
                data->pcie_ue_hdr[0] = (uint32_t)(reg >> 32);
                data->pcie_ue_hdr[1] = (uint32_t)(reg);
        }
        if (nvlist_lookup_uint64(erpt, "tlu-rueh2l", &reg) == 0) {
                data->pcie_ue_hdr[2] = (uint32_t)(reg >> 32);
                data->pcie_ue_hdr[3] = (uint32_t)(reg);
        }

        return (1);
}

static int
fab_xlate_fire_oe(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt,
    const char *class)
{
        fab_fire_tbl_t  *entry;
        uint64_t        reg;

        for (entry = fab_fire_pec_oe_tbl; entry->err_class; entry++) {
                if (STRCMP(class, entry->err_class))
                        goto send;
        }

        return (0);

send:
        fmd_hdl_debug(hdl, "Translate Fire OE %s\n", class);

        /* Fill in PCI Status Register */
        if (entry->fire_bit) {
                data->pci_err_status = entry->pci_err_sts;
                data->pci_bdg_sec_stat = entry->pci_bdg_sts;
        } else {
                if (nvlist_lookup_uint64(erpt, "tlu-roeeh1l", &reg) == 0) {
                        data->pcie_ue_hdr[0] = (uint32_t)(reg >> 32);
                        data->pcie_ue_hdr[1] = (uint32_t)(reg);
                }
                if (nvlist_lookup_uint64(erpt, "tlu-roeeh2l", &reg) == 0) {
                        data->pcie_ue_hdr[2] = (uint32_t)(reg >> 32);
                        data->pcie_ue_hdr[3] = (uint32_t)(reg);
                }

                if (((pcie_tlp_hdr_t *)(&data->pcie_ue_hdr[0]))->type ==
                    PCIE_TLP_TYPE_CPL) {
                        pcie_cpl_t *cpl = (pcie_cpl_t *)&data->pcie_ue_hdr[1];
                        switch (cpl->status) {
                        case PCIE_CPL_STS_UR:
                                data->pci_err_status = 0;
                                data->pci_bdg_sec_stat = PCI_STAT_R_MAST_AB |
                                    PCI_STAT_S_SYSERR;
                                break;
                        case PCIE_CPL_STS_CA:
                                data->pci_err_status = 0;
                                data->pci_bdg_sec_stat = PCI_STAT_R_TARG_AB |
                                    PCI_STAT_S_SYSERR;
                                break;
                        }
                }
        }

        /* Fill in the device status register */
        if (entry->fire_bit & data->pcie_ue_sev)
                data->pcie_err_status = PCIE_DEVSTS_FE_DETECTED;
        else
                data->pcie_err_status = PCIE_DEVSTS_NFE_DETECTED;

        /* Fill in the AER UE register */
        data->pcie_ue_status = entry->fire_bit;

        return (1);
}

static int
fab_xlate_fire_dmc(fmd_hdl_t *hdl, fab_data_t *data, nvlist_t *erpt,
    const char *class)
{
        fab_fire_tbl_t  *entry;
        uint64_t        reg;
        uint32_t        temp;

        for (entry = fab_fire_dmc_tbl; entry->err_class; entry++) {
                fmd_hdl_debug(hdl, "Matching %s\n", entry->err_class);
                if (STRCMP(class, entry->err_class) &&
                    nvlist_lookup_boolean(erpt, "primary"))
                        goto send;
        }

        return (0);

send:
        fmd_hdl_debug(hdl, "Translate Fire DMC %s\n", class);

        /* Fill in PCI Status Register */
        data->pci_err_status = entry->pci_err_sts;
        data->pci_bdg_sec_stat = entry->pci_bdg_sts;

        /* Fill in the device status register */
        data->pcie_err_status = PCIE_DEVSTS_NFE_DETECTED;

        /* Fill in the AER UE register */
        data->pcie_ue_status = entry->fire_bit;

        /* Fill in the AER Control register */
        temp = entry->fire_bit;
        for (data->pcie_adv_ctl = (uint32_t)-1; temp; data->pcie_adv_ctl++)
                temp = temp >> 1;

        /* Fill in the AER Header registers */
        if (nvlist_lookup_uint64(erpt, "mmu-tfsr", &reg) == 0) {
                fmd_hdl_debug(hdl, "tfsr 0x%llx\n", reg);
                /* Get the trans type */
                temp = (reg & 0x3F0000) >> 16;
                data->pcie_ue_hdr[0] = (uint32_t)(temp << 24);
                data->pcie_ue_tgt_trans = PF_ADDR_DMA;
                /* Get the req id */
                temp = (reg & 0xFFFF);
                data->pcie_ue_hdr[1] = (uint32_t)(temp << 16);
                data->pcie_ue_tgt_bdf = temp;
        }

        if (nvlist_lookup_uint64(erpt, "mmu-tfar", &reg) == 0) {
                fmd_hdl_debug(hdl, "tfar 0x%llx\n", reg);
                /* Get the address */
                data->pcie_ue_hdr[2] = reg;
                data->pcie_ue_hdr[3] = 0;
                data->pcie_ue_tgt_addr = reg;
        }

        fmd_hdl_debug(hdl, "HEADER 0 0x%x\n", data->pcie_ue_hdr[0]);
        fmd_hdl_debug(hdl, "HEADER 1 0x%x\n", data->pcie_ue_hdr[1]);
        fmd_hdl_debug(hdl, "HEADER 2 0x%x\n", data->pcie_ue_hdr[2]);
        fmd_hdl_debug(hdl, "HEADER 3 0x%x\n", data->pcie_ue_hdr[3]);

        return (1);
}

void
fab_xlate_fire_erpts(fmd_hdl_t *hdl, nvlist_t *nvl, const char *class)
{
        fab_data_t data = {0};

        fmd_hdl_debug(hdl, "Fire RC ereport received: %s\n", class);

        fab_fire_to_data(hdl, nvl, &data);

        if (fmd_nvl_class_match(hdl, nvl, "ereport.io.fire.pec.*")) {
                if (! fab_xlate_fire_ce(hdl, &data, nvl, class) &&
                    ! fab_xlate_fire_ue(hdl, &data, nvl, class))
                        (void) fab_xlate_fire_oe(hdl, &data, nvl, class);
        } else if (fmd_nvl_class_match(hdl, nvl, "ereport.io.fire.dmc.*") ||
            fmd_nvl_class_match(hdl, nvl, "ereport.io.n2.dmu.*"))
                (void) fab_xlate_fire_dmc(hdl, &data, nvl, class);

        fab_xlate_pcie_erpts(hdl, &data);
}