amd_iommu_cmd.c revision 7d87efa8fdfb9453670f2832df666fdae8291a84
/*
* 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 2008 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"
extern int servicing_interrupt(void);
static void
amd_iommu_wait_for_completion(amd_iommu_t *iommu)
{
ASSERT(MUTEX_HELD(&iommu->aiomt_cmdlock));
while (AMD_IOMMU_REG_GET64(REGADDR64(
iommu->aiomt_reg_status_va), AMD_IOMMU_COMWAIT_INT) != 1) {
AMD_IOMMU_REG_SET64(REGADDR64(iommu->aiomt_reg_ctrl_va),
AMD_IOMMU_CMDBUF_ENABLE, 1);
WAIT_SEC(1);
}
}
static int
create_compl_wait_cmd(amd_iommu_t *iommu, amd_iommu_cmdargs_t *cmdargsp,
amd_iommu_cmd_flags_t flags, uint32_t *cmdptr)
{
const char *driver = ddi_driver_name(iommu->aiomt_dip);
int instance = ddi_get_instance(iommu->aiomt_dip);
const char *f = "create_compl_wait_cmd";
ASSERT(cmdargsp == NULL);
if (flags & AMD_IOMMU_CMD_FLAGS_COMPL_WAIT_S) {
cmn_err(CE_WARN, "%s: %s%d: idx=%d: 'store' completion "
"not supported for completion wait command",
f, driver, instance, iommu->aiomt_idx);
return (DDI_FAILURE);
}
AMD_IOMMU_REG_SET32(&cmdptr[0], AMD_IOMMU_CMD_COMPL_WAIT_S, 0);
AMD_IOMMU_REG_SET32(&cmdptr[0], AMD_IOMMU_CMD_COMPL_WAIT_I, 1);
AMD_IOMMU_REG_SET32(&cmdptr[0], AMD_IOMMU_CMD_COMPL_WAIT_F,
(flags & AMD_IOMMU_CMD_FLAGS_COMPL_WAIT_F) != 0);
AMD_IOMMU_REG_SET32(&cmdptr[0], AMD_IOMMU_CMD_COMPL_WAIT_STORE_ADDR_LO,
0);
AMD_IOMMU_REG_SET32(&cmdptr[1], AMD_IOMMU_CMD_OPCODE, 0x01);
AMD_IOMMU_REG_SET32(&cmdptr[1], AMD_IOMMU_CMD_COMPL_WAIT_STORE_ADDR_HI,
0);
cmdptr[2] = 0;
cmdptr[3] = 0;
return (DDI_SUCCESS);
}
static int
create_inval_devtab_entry_cmd(amd_iommu_t *iommu, amd_iommu_cmdargs_t *cmdargsp,
amd_iommu_cmd_flags_t flags, uint32_t *cmdptr)
{
const char *driver = ddi_driver_name(iommu->aiomt_dip);
int instance = ddi_get_instance(iommu->aiomt_dip);
const char *f = "create_inval_devtab_entry_cmd";
uint16_t deviceid;
ASSERT(cmdargsp);
if (flags != AMD_IOMMU_CMD_FLAGS_NONE) {
cmn_err(CE_WARN, "%s: %s%d: idx=%d: invalidate devtab entry "
"no flags supported", f, driver, instance,
iommu->aiomt_idx);
return (DDI_FAILURE);
}
deviceid = cmdargsp->ca_deviceid;
AMD_IOMMU_REG_SET32(&cmdptr[0], AMD_IOMMU_CMD_INVAL_DEVTAB_DEVICEID,
deviceid);
AMD_IOMMU_REG_SET32(&cmdptr[1], AMD_IOMMU_CMD_OPCODE, 0x02);
cmdptr[2] = 0;
cmdptr[3] = 0;
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
create_inval_iommu_pages_cmd(amd_iommu_t *iommu, amd_iommu_cmdargs_t *cmdargsp,
amd_iommu_cmd_flags_t flags, uint32_t *cmdptr)
{
uint32_t addr_lo;
uint32_t addr_hi;
ASSERT(cmdargsp);
addr_lo = AMD_IOMMU_REG_GET64(REGADDR64(&cmdargsp->ca_addr),
AMD_IOMMU_CMD_INVAL_PAGES_ADDR_LO);
addr_hi = AMD_IOMMU_REG_GET64(REGADDR64(&cmdargsp->ca_addr),
AMD_IOMMU_CMD_INVAL_PAGES_ADDR_HI);
cmdptr[0] = 0;
AMD_IOMMU_REG_SET32(&cmdptr[1], AMD_IOMMU_CMD_INVAL_PAGES_DOMAINID,
cmdargsp->ca_domainid);
AMD_IOMMU_REG_SET32(&cmdptr[1], AMD_IOMMU_CMD_OPCODE, 0x03);
AMD_IOMMU_REG_SET32(&cmdptr[2], AMD_IOMMU_CMD_INVAL_PAGES_PDE,
(flags & AMD_IOMMU_CMD_FLAGS_PAGE_PDE_INVAL) != 0);
AMD_IOMMU_REG_SET32(&cmdptr[2], AMD_IOMMU_CMD_INVAL_PAGES_S,
(flags & AMD_IOMMU_CMD_FLAGS_PAGE_INVAL_S) != 0);
AMD_IOMMU_REG_SET32(&cmdptr[2], AMD_IOMMU_CMD_INVAL_PAGES_ADDR_LO,
addr_lo);
cmdptr[3] = addr_hi;
return (DDI_SUCCESS);
}
/*ARGSUSED*/
static int
create_inval_iotlb_pages_cmd(amd_iommu_t *iommu, amd_iommu_cmdargs_t *cmdargsp,
amd_iommu_cmd_flags_t flags, uint32_t *cmdptr)
{
uint32_t addr_lo;
uint32_t addr_hi;
ASSERT(cmdargsp);
addr_lo = AMD_IOMMU_REG_GET64(REGADDR64(&cmdargsp->ca_addr),
AMD_IOMMU_CMD_INVAL_IOTLB_ADDR_LO);
addr_hi = AMD_IOMMU_REG_GET64(REGADDR64(&cmdargsp->ca_addr),
AMD_IOMMU_CMD_INVAL_IOTLB_ADDR_HI);
AMD_IOMMU_REG_SET32(&cmdptr[0], AMD_IOMMU_CMD_INVAL_IOTLB_DEVICEID,
cmdargsp->ca_deviceid);
AMD_IOMMU_REG_SET32(&cmdptr[0], AMD_IOMMU_CMD_INVAL_IOTLB_MAXPEND,
AMD_IOMMU_DEFAULT_MAXPEND);
AMD_IOMMU_REG_SET32(&cmdptr[1], AMD_IOMMU_CMD_OPCODE, 0x04);
AMD_IOMMU_REG_SET32(&cmdptr[1], AMD_IOMMU_CMD_INVAL_IOTLB_QUEUEID,
cmdargsp->ca_deviceid);
AMD_IOMMU_REG_SET32(&cmdptr[2], AMD_IOMMU_CMD_INVAL_IOTLB_ADDR_LO,
addr_lo);
AMD_IOMMU_REG_SET32(&cmdptr[2], AMD_IOMMU_CMD_INVAL_IOTLB_S,
(flags & AMD_IOMMU_CMD_FLAGS_IOTLB_INVAL_S) != 0);
cmdptr[3] = addr_hi;
return (DDI_SUCCESS);
}
static int
create_inval_intr_table_cmd(amd_iommu_t *iommu, amd_iommu_cmdargs_t *cmdargsp,
amd_iommu_cmd_flags_t flags, uint32_t *cmdptr)
{
const char *driver = ddi_driver_name(iommu->aiomt_dip);
int instance = ddi_get_instance(iommu->aiomt_dip);
const char *f = "create_inval_intr_table_cmd";
ASSERT(cmdargsp);
if (flags != AMD_IOMMU_CMD_FLAGS_NONE) {
cmn_err(CE_WARN, "%s: %s%d: idx=%d: flags not supported "
"for invalidate interrupt table command",
f, driver, instance, iommu->aiomt_idx);
return (DDI_FAILURE);
}
AMD_IOMMU_REG_SET32(&cmdptr[0], AMD_IOMMU_CMD_INVAL_INTR_DEVICEID,
cmdargsp->ca_deviceid);
AMD_IOMMU_REG_SET32(&cmdptr[1], AMD_IOMMU_CMD_OPCODE, 0x05);
cmdptr[2] = 0;
cmdptr[3] = 0;
return (DDI_SUCCESS);
}
int
amd_iommu_cmd(amd_iommu_t *iommu, amd_iommu_cmd_t cmd,
amd_iommu_cmdargs_t *cmdargs, amd_iommu_cmd_flags_t flags, int lock_held)
{
int error;
int i;
uint32_t cmdptr[4] = {0};
const char *driver = ddi_driver_name(iommu->aiomt_dip);
int instance = ddi_get_instance(iommu->aiomt_dip);
uint64_t cmdhead_off;
uint64_t cmdtail_off;
const char *f = "amd_iommu_cmd";
ASSERT(lock_held == 0 || lock_held == 1);
ASSERT(lock_held == 0 || MUTEX_HELD(&iommu->aiomt_cmdlock));
if (!lock_held)
mutex_enter(&iommu->aiomt_cmdlock);
/*
* Prepare the command
*/
switch (cmd) {
case AMD_IOMMU_CMD_COMPL_WAIT:
if (flags & AMD_IOMMU_CMD_FLAGS_COMPL_WAIT) {
cmn_err(CE_WARN, "%s: %s%d: idx=%d: No completion wait "
" after completion wait command",
f, driver, instance, iommu->aiomt_idx);
error = DDI_FAILURE;
goto out;
}
error = create_compl_wait_cmd(iommu, cmdargs, flags, cmdptr);
break;
case AMD_IOMMU_CMD_INVAL_DEVTAB_ENTRY:
error = create_inval_devtab_entry_cmd(iommu, cmdargs,
flags & ~AMD_IOMMU_CMD_FLAGS_COMPL_WAIT, cmdptr);
break;
case AMD_IOMMU_CMD_INVAL_IOMMU_PAGES:
error = create_inval_iommu_pages_cmd(iommu, cmdargs,
flags & ~AMD_IOMMU_CMD_FLAGS_COMPL_WAIT, cmdptr);
break;
case AMD_IOMMU_CMD_INVAL_IOTLB_PAGES:
error = create_inval_iotlb_pages_cmd(iommu, cmdargs,
flags & ~AMD_IOMMU_CMD_FLAGS_COMPL_WAIT, cmdptr);
break;
case AMD_IOMMU_CMD_INVAL_INTR_TABLE:
error = create_inval_intr_table_cmd(iommu, cmdargs,
flags & ~AMD_IOMMU_CMD_FLAGS_COMPL_WAIT, cmdptr);
break;
default:
cmn_err(CE_WARN, "%s: %s%d: idx=%d: Unsupported cmd: %d",
f, driver, instance, iommu->aiomt_idx, cmd);
error = DDI_FAILURE;
goto out;
}
if (error != DDI_SUCCESS) {
error = DDI_FAILURE;
goto out;
}
AMD_IOMMU_REG_SET64(REGADDR64(iommu->aiomt_reg_ctrl_va),
AMD_IOMMU_CMDBUF_ENABLE, 1);
ASSERT(iommu->aiomt_cmd_tail != NULL);
for (i = 0; i < 4; i++) {
iommu->aiomt_cmd_tail[i] = cmdptr[i];
}
wait_for_drain:
cmdhead_off = AMD_IOMMU_REG_GET64(
REGADDR64(iommu->aiomt_reg_cmdbuf_head_va),
AMD_IOMMU_CMDHEADPTR);
cmdhead_off = CMD2OFF(cmdhead_off);
ASSERT(cmdhead_off < iommu->aiomt_cmdbuf_sz);
/* check for overflow */
if ((caddr_t)iommu->aiomt_cmd_tail <
(cmdhead_off + iommu->aiomt_cmdbuf)) {
if ((caddr_t)iommu->aiomt_cmd_tail + 16 >=
(cmdhead_off + iommu->aiomt_cmdbuf))
#ifdef DEBUG
cmn_err(CE_WARN, "cmdbuffer overflow: waiting for "
"drain");
#endif
goto wait_for_drain;
}
SYNC_FORDEV(iommu->aiomt_dmahdl);
/*
* Update the tail pointer in soft state
* and the tail pointer register
*/
iommu->aiomt_cmd_tail += 4;
if ((caddr_t)iommu->aiomt_cmd_tail >= (iommu->aiomt_cmdbuf
+ iommu->aiomt_cmdbuf_sz)) {
/* wraparound */
/*LINTED*/
iommu->aiomt_cmd_tail = (uint32_t *)iommu->aiomt_cmdbuf;
cmdtail_off = 0;
} else {
cmdtail_off = (caddr_t)iommu->aiomt_cmd_tail
/*LINTED*/
- iommu->aiomt_cmdbuf;
}
ASSERT(cmdtail_off < iommu->aiomt_cmdbuf_sz);
AMD_IOMMU_REG_SET64(REGADDR64(iommu->aiomt_reg_cmdbuf_tail_va),
AMD_IOMMU_CMDTAILPTR, OFF2CMD(cmdtail_off));
if (cmd == AMD_IOMMU_CMD_COMPL_WAIT) {
amd_iommu_wait_for_completion(iommu);
} else if (flags & AMD_IOMMU_CMD_FLAGS_COMPL_WAIT) {
error = amd_iommu_cmd(iommu, AMD_IOMMU_CMD_COMPL_WAIT,
NULL, 0, 1);
}
out:
if (!lock_held)
mutex_exit(&iommu->aiomt_cmdlock);
return (error);
}