root/usr/src/uts/i86pc/io/amd_iommu/amd_iommu_log.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 <sys/sunddi.h>
#include <sys/amd_iommu.h>
#include "amd_iommu_impl.h"
#include "amd_iommu_log.h"


static const char *
get_hw_error(uint8_t type)
{
        const char *hwerr;

        switch (type) {
        case 0:
                hwerr = "Reserved";
                break;
        case 1:
                hwerr = "Master Abort";
                break;
        case 2:
                hwerr = "Target Abort";
                break;
        case 3:
                hwerr = "Data Error";
                break;
        default:
                hwerr = "Unknown";
                break;
        }

        return (hwerr);
}

const char *
get_illegal_req(uint8_t type, uint8_t TR)
{
        const char *illreq;

        switch (type) {
        case 0:
                illreq = (TR == 1) ? "Translation I=0/V=0/V=1&&TV=0" :
                    "Read or Non-posted Write in INTR Range";
                break;
        case 1:
                illreq = (TR == 1) ? "Translation INTR/Port-IO/SysMgt; OR"
                    "Translation when SysMgt=11b/Port-IO when IOCTL=10b "
                    "while V=1 && TV=0" :
                    "Pre-translated transaction from device with I=0 or V=0";
                break;
        case 2:
                illreq = (TR == 1) ? "Reserved":
                    "Port-IO transaction for device with IoCtl = 00b";
                break;
        case 3:
                illreq = (TR == 1) ? "Reserved":
                    "Posted write to SysMgt with device SysMgt=00b "
                    "OR SysMgt=10b && message not INTx "
                    "OR Posted write to addr transaltion range with "
                    "HtAtsResv=1";
                break;
        case 4:
                illreq = (TR == 1) ? "Reserved":
                    "Read request or non-posted write in SysMgt with "
                    "device SysMgt=10b or 0xb"
                    "OR Read request or non-posted write in "
                    "addr translation range with HtAtsResv=1";
                break;
        case 5:
                illreq = (TR == 1) ? "Reserved":
                    "Posted write to Interrupt/EOI Range "
                    "for device that has IntCtl=00b";
                break;
        case 6:
                illreq = (TR == 1) ? "Reserved":
                    "Posted write to reserved Interrupt Address Range";
                break;
        case 7:
                illreq = (TR == 1) ? "Reserved":
                    "transaction to SysMgt when SysMgt=11b OR "
                    "transaction to Port-IO when IoCtl=10b while "
                    "while V=1 TV=0";
                break;
        default:
                illreq = "Unknown error";
                break;
        }
        return (illreq);
}

static void
devtab_illegal_entry(amd_iommu_t *iommu, uint32_t *event)
{
        uint16_t deviceid;
        uint8_t TR;
        uint8_t RZ;
        uint8_t RW;
        uint8_t I;
        uint32_t vaddr_lo;
        uint32_t vaddr_hi;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "devtab_illegal_entry";

        ASSERT(AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE) ==
            AMD_IOMMU_EVENT_DEVTAB_ILLEGAL_ENTRY);

        deviceid = AMD_IOMMU_REG_GET32(&event[0],
            AMD_IOMMU_EVENT_DEVTAB_ILL_DEVICEID);

        TR = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_DEVTAB_ILL_TR);

        RZ = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_DEVTAB_ILL_RZ);

        RW = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_DEVTAB_ILL_RW);

        I = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_DEVTAB_ILL_INTR);

        vaddr_lo = AMD_IOMMU_REG_GET32(&event[2],
            AMD_IOMMU_EVENT_DEVTAB_ILL_VADDR_LO);

        vaddr_hi = event[3];

        cmn_err(CE_WARN, "%s: %s%d: idx = %d. Illegal device table entry "
            "deviceid=%u, %s request, %s %s transaction, %s request, "
            "virtual address = %p",
            f, driver, instance, iommu->aiomt_idx,
            deviceid,
            TR == 1 ? "Translation" : "Transaction",
            RZ == 1 ? "Non-zero reserved bit" : "Illegal Level encoding",
            RW == 1 ? "Write" : "Read",
            I == 1 ? "Interrupt" : "Memory",
            (void *)(uintptr_t)(((uint64_t)vaddr_hi) << 32 | vaddr_lo));
}

static void
io_page_fault(amd_iommu_t *iommu, uint32_t *event)
{
        uint16_t deviceid;
        uint16_t domainid;
        uint8_t TR;
        uint8_t RZ;
        uint8_t RW;
        uint8_t PE;
        uint8_t PR;
        uint8_t I;
        uint32_t vaddr_lo;
        uint32_t vaddr_hi;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "io_page_fault";

        ASSERT(AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE) ==
            AMD_IOMMU_EVENT_IO_PAGE_FAULT);

        deviceid = AMD_IOMMU_REG_GET32(&event[0],
            AMD_IOMMU_EVENT_IO_PGFAULT_DEVICEID);

        TR = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_IO_PGFAULT_TR);

        RZ = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_IO_PGFAULT_RZ);

        PE = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_IO_PGFAULT_PE);

        RW = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_IO_PGFAULT_RW);

        PR = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_IO_PGFAULT_PR);

        I = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_IO_PGFAULT_INTR);

        domainid = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_IO_PGFAULT_DOMAINID);

        vaddr_lo = event[2];

        vaddr_hi = event[3];

        cmn_err(CE_WARN, "%s: %s%d: idx = %d. IO Page Fault. "
            "deviceid=%u, %s request, %s, %s permissions, %s transaction, "
            "%s, %s request, domainid=%u, virtual address = %p",
            f, driver, instance, iommu->aiomt_idx,
            deviceid,
            TR == 1 ? "Translation" : "Transaction",
            RZ == 1 ? "Non-zero reserved bit" : "Illegal Level encoding",
            PE == 1 ? "did not have" : "had",
            RW == 1 ? "Write" : "Read",
            PR == 1 ? "Page present or Interrupt Remapped" :
            "Page not present or Interrupt Blocked",
            I == 1 ? "Interrupt" : "Memory",
            domainid,
            (void *)(uintptr_t)(((uint64_t)vaddr_hi) << 32 | vaddr_lo));
}

static void
devtab_hw_error(amd_iommu_t *iommu, uint32_t *event)
{
        uint16_t deviceid;
        uint8_t type;
        uint8_t TR;
        uint8_t RW;
        uint8_t I;
        uint32_t physaddr_lo;
        uint32_t physaddr_hi;
        const char *hwerr;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "devtab_hw_error";

        ASSERT(AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE) ==
            AMD_IOMMU_EVENT_DEVTAB_HW_ERROR);

        deviceid = AMD_IOMMU_REG_GET32(&event[0],
            AMD_IOMMU_EVENT_DEVTAB_HWERR_DEVICEID);

        type = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_DEVTAB_HWERR_TYPE);

        hwerr = get_hw_error(type);

        TR = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_DEVTAB_HWERR_TR);

        RW = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_DEVTAB_HWERR_RW);

        I = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_DEVTAB_HWERR_INTR);

        physaddr_lo = AMD_IOMMU_REG_GET32(&event[2],
            AMD_IOMMU_EVENT_DEVTAB_HWERR_PHYSADDR_LO);

        physaddr_hi = event[3];

        cmn_err(CE_WARN, "%s: %s%d: idx = %d. Device Table HW Error. "
            "deviceid=%u, HW error type: %s, %s request, %s transaction, "
            "%s request, physical address = %p",
            f, driver, instance, iommu->aiomt_idx,
            deviceid, hwerr,
            TR == 1 ? "Translation" : "Transaction",
            RW == 1 ? "Write" : "Read",
            I == 1 ? "Interrupt" : "Memory",
            (void *)(uintptr_t)(((uint64_t)physaddr_hi) << 32 | physaddr_lo));
}


static void
pgtable_hw_error(amd_iommu_t *iommu, uint32_t *event)
{
        uint16_t deviceid;
        uint16_t domainid;
        uint8_t type;
        uint8_t TR;
        uint8_t RW;
        uint8_t I;
        uint32_t physaddr_lo;
        uint32_t physaddr_hi;
        const char *hwerr;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "pgtable_hw_error";

        ASSERT(AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE) ==
            AMD_IOMMU_EVENT_PGTABLE_HW_ERROR);

        deviceid = AMD_IOMMU_REG_GET32(&event[0],
            AMD_IOMMU_EVENT_PGTABLE_HWERR_DEVICEID);

        type = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_DEVTAB_HWERR_TYPE);

        hwerr = get_hw_error(type);

        TR = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_PGTABLE_HWERR_TR);

        RW = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_PGTABLE_HWERR_RW);

        I = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_PGTABLE_HWERR_INTR);

        domainid = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_PGTABLE_HWERR_DOMAINID);

        physaddr_lo = AMD_IOMMU_REG_GET32(&event[2],
            AMD_IOMMU_EVENT_PGTABLE_HWERR_PHYSADDR_LO);

        physaddr_hi = event[3];

        cmn_err(CE_WARN, "%s: %s%d: idx = %d. Page Table HW Error. "
            "deviceid=%u, HW error type: %s, %s request, %s transaction, "
            "%s request, domainid=%u, physical address = %p",
            f, driver, instance, iommu->aiomt_idx,
            deviceid, hwerr,
            TR == 1 ? "Translation" : "Transaction",
            RW == 1 ? "Write" : "Read",
            I == 1 ? "Interrupt" : "Memory",
            domainid,
            (void *)(uintptr_t)(((uint64_t)physaddr_hi) << 32 | physaddr_lo));
}

static void
cmdbuf_illegal_cmd(amd_iommu_t *iommu, uint32_t *event)
{
        uint32_t physaddr_lo;
        uint32_t physaddr_hi;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "cmdbuf_illegal_cmd";

        ASSERT(AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE) ==
            AMD_IOMMU_EVENT_CMDBUF_ILLEGAL_CMD);

        physaddr_lo = AMD_IOMMU_REG_GET32(&event[2],
            AMD_IOMMU_EVENT_CMDBUF_ILLEGAL_CMD_PHYS_LO);

        physaddr_hi = event[3];

        cmn_err(CE_WARN, "%s: %s%d: idx = %d. Illegal IOMMU command. "
            "command physical address = %p",
            f, driver, instance, iommu->aiomt_idx,
            (void *)(uintptr_t)(((uint64_t)physaddr_hi) << 32 | physaddr_lo));
}

static void
cmdbuf_hw_error(amd_iommu_t *iommu, uint32_t *event)
{
        uint32_t physaddr_lo;
        uint32_t physaddr_hi;
        uint8_t type;
        const char *hwerr;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "cmdbuf_hw_error";

        ASSERT(AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE) ==
            AMD_IOMMU_EVENT_CMDBUF_HW_ERROR);

        type = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_CMDBUF_HWERR_TYPE);

        hwerr = get_hw_error(type);

        physaddr_lo = AMD_IOMMU_REG_GET32(&event[2],
            AMD_IOMMU_EVENT_CMDBUF_HWERR_PHYS_LO);

        physaddr_hi = event[3];

        cmn_err(CE_WARN, "%s: %s%d: idx = %d. Command Buffer HW error. "
            "HW error type = %s, command buffer physical address = %p",
            f, driver, instance, iommu->aiomt_idx,
            hwerr,
            (void *)(uintptr_t)(((uint64_t)physaddr_hi) << 32 | physaddr_lo));
}

static void
iotlb_inval_to(amd_iommu_t *iommu, uint32_t *event)
{
        uint16_t deviceid;
        uint32_t physaddr_lo;
        uint32_t physaddr_hi;
        uint8_t type;
        const char *hwerr;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "iotlb_inval_to";

        ASSERT(AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE) ==
            AMD_IOMMU_EVENT_IOTLB_INVAL_TO);

        deviceid = AMD_IOMMU_REG_GET32(&event[0],
            AMD_IOMMU_EVENT_IOTLB_INVAL_TO_DEVICEID);

        /*
         * XXX bug in spec. Is the type field available +04 26:25 or is
         * it reserved
         */
        type = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_IOTLB_INVAL_TO_TYPE);
        hwerr = get_hw_error(type);

        physaddr_lo = AMD_IOMMU_REG_GET32(&event[2],
            AMD_IOMMU_EVENT_IOTLB_INVAL_TO_PHYS_LO);

        physaddr_hi = event[3];

        cmn_err(CE_WARN, "%s: %s%d: idx = %d. deviceid = %u "
            "IOTLB invalidation Timeout. "
            "HW error type = %s, invalidation command physical address = %p",
            f, driver, instance, iommu->aiomt_idx, deviceid,
            hwerr,
            (void *)(uintptr_t)(((uint64_t)physaddr_hi) << 32 | physaddr_lo));
}

static void
device_illegal_req(amd_iommu_t *iommu, uint32_t *event)
{
        uint16_t deviceid;
        uint8_t TR;
        uint32_t addr_lo;
        uint32_t addr_hi;
        uint8_t type;
        const char *reqerr;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "device_illegal_req";

        ASSERT(AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE) ==
            AMD_IOMMU_EVENT_DEVICE_ILLEGAL_REQ);

        deviceid = AMD_IOMMU_REG_GET32(&event[0],
            AMD_IOMMU_EVENT_DEVICE_ILLEGAL_REQ_DEVICEID);

        TR = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_DEVICE_ILLEGAL_REQ_TR);

        type = AMD_IOMMU_REG_GET32(&event[1],
            AMD_IOMMU_EVENT_DEVICE_ILLEGAL_REQ_TYPE);

        reqerr = get_illegal_req(type, TR);


        addr_lo = event[2];
        addr_hi = event[3];

        cmn_err(CE_WARN, "%s: %s%d: idx = %d. deviceid = %d "
            "Illegal Device Request. "
            "Illegal Request type = %s, %s request, address accessed = %p",
            f, driver, instance, iommu->aiomt_idx, deviceid,
            reqerr,
            TR == 1 ? "Translation" : "Transaction",
            (void *)(uintptr_t)(((uint64_t)addr_hi) << 32 | addr_lo));
}

static void
amd_iommu_process_one_event(amd_iommu_t *iommu)
{
        uint32_t event[4];
        amd_iommu_event_t event_type;
        int i;
        const char *driver = ddi_driver_name(iommu->aiomt_dip);
        int instance = ddi_get_instance(iommu->aiomt_dip);
        const char *f = "amd_iommu_process_one_event";

        ASSERT(MUTEX_HELD(&iommu->aiomt_eventlock));

        SYNC_FORKERN(iommu->aiomt_dmahdl);
        for (i = 0; i < 4; i++) {
                event[i] =  iommu->aiomt_event_head[i];
        }

        event_type = AMD_IOMMU_REG_GET32(&event[1], AMD_IOMMU_EVENT_TYPE);

        switch (event_type) {
        case AMD_IOMMU_EVENT_DEVTAB_ILLEGAL_ENTRY:
                devtab_illegal_entry(iommu, event);
                break;
        case AMD_IOMMU_EVENT_IO_PAGE_FAULT:
                io_page_fault(iommu, event);
                break;
        case AMD_IOMMU_EVENT_DEVTAB_HW_ERROR:
                devtab_hw_error(iommu, event);
                break;
        case AMD_IOMMU_EVENT_PGTABLE_HW_ERROR:
                pgtable_hw_error(iommu, event);
                break;
        case AMD_IOMMU_EVENT_CMDBUF_HW_ERROR:
                cmdbuf_hw_error(iommu, event);
                break;
        case AMD_IOMMU_EVENT_CMDBUF_ILLEGAL_CMD:
                cmdbuf_illegal_cmd(iommu, event);
                break;
        case AMD_IOMMU_EVENT_IOTLB_INVAL_TO:
                iotlb_inval_to(iommu, event);
                break;
        case AMD_IOMMU_EVENT_DEVICE_ILLEGAL_REQ:
                device_illegal_req(iommu, event);
                break;
        default:
                cmn_err(CE_WARN, "%s: %s%d: idx = %d. Unknown event: %u",
                    f, driver, instance, iommu->aiomt_idx, event_type);
                break;
        }
}

int
amd_iommu_read_log(amd_iommu_t *iommu, amd_iommu_log_op_t op)
{
        caddr_t evtail;
        uint64_t evtail_off;
        uint64_t evhead_off;

        ASSERT(op != AMD_IOMMU_LOG_INVALID_OP);

        mutex_enter(&iommu->aiomt_eventlock);

        ASSERT(iommu->aiomt_event_head != NULL);

        /* XXX verify */
        evtail_off = AMD_IOMMU_REG_GET64(
            REGADDR64(iommu->aiomt_reg_eventlog_tail_va),
            AMD_IOMMU_EVENTTAILPTR);

        evtail_off = EV2OFF(evtail_off);

        ASSERT(evtail_off <  iommu->aiomt_eventlog_sz);

        evtail = iommu->aiomt_eventlog + evtail_off;

        if (op == AMD_IOMMU_LOG_DISCARD) {
                /*LINTED*/
                iommu->aiomt_event_head = (uint32_t *)evtail;
                AMD_IOMMU_REG_SET64(REGADDR64(
                    iommu->aiomt_reg_eventlog_head_va),
                    AMD_IOMMU_EVENTHEADPTR, OFF2EV(evtail_off));
                cmn_err(CE_NOTE, "Discarded IOMMU event log");
                mutex_exit(&iommu->aiomt_eventlock);
                return (DDI_SUCCESS);
        }

        /*LINTED*/
        while (1) {
                if ((caddr_t)iommu->aiomt_event_head == evtail)
                        break;

                cmn_err(CE_WARN, "evtail_off = %p, head = %p, tail = %p",
                    (void *)(uintptr_t)evtail_off,
                    (void *)iommu->aiomt_event_head,
                    (void *)evtail);

                amd_iommu_process_one_event(iommu);

                /*
                 * Update the head pointer in soft state
                 * and the head pointer register
                 */
                iommu->aiomt_event_head += 4;
                if ((caddr_t)iommu->aiomt_event_head >=
                    iommu->aiomt_eventlog + iommu->aiomt_eventlog_sz) {
                        /* wraparound */
                        iommu->aiomt_event_head =
                        /*LINTED*/
                            (uint32_t *)iommu->aiomt_eventlog;
                        evhead_off = 0;
                } else {
                        evhead_off =  (caddr_t)iommu->aiomt_event_head
                        /*LINTED*/
                            - iommu->aiomt_eventlog;
                }

                ASSERT(evhead_off < iommu->aiomt_eventlog_sz);

                AMD_IOMMU_REG_SET64(REGADDR64(
                    iommu->aiomt_reg_eventlog_head_va),
                    AMD_IOMMU_EVENTHEADPTR, OFF2EV(evhead_off));
        }
        mutex_exit(&iommu->aiomt_eventlock);

        return (DDI_SUCCESS);
}