/*
* 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/errno.h>
#include <sys/types.h>
#include <sys/conf.h>
#include <sys/kmem.h>
#include <sys/ddi.h>
#include <sys/stat.h>
#include <sys/sunddi.h>
#include <sys/file.h>
#include <sys/open.h>
#include <sys/modctl.h>
#include <sys/ddi_impldefs.h>
#include <sys/sysmacros.h>
#include <sys/ioat.h>
static int ioat_open(dev_t *devp, int flag, int otyp, cred_t *cred);
static int ioat_close(dev_t devp, int flag, int otyp, cred_t *cred);
static int ioat_attach(dev_info_t *devi, ddi_attach_cmd_t cmd);
static int ioat_detach(dev_info_t *devi, ddi_detach_cmd_t cmd);
static int ioat_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg,
void **result);
static int ioat_quiesce(dev_info_t *dip);
static struct cb_ops ioat_cb_ops = {
ioat_open, /* cb_open */
ioat_close, /* cb_close */
nodev, /* cb_strategy */
nodev, /* cb_print */
nodev, /* cb_dump */
nodev, /* cb_read */
nodev, /* cb_write */
ioat_ioctl, /* cb_ioctl */
nodev, /* cb_devmap */
nodev, /* cb_mmap */
nodev, /* cb_segmap */
nochpoll, /* cb_chpoll */
ddi_prop_op, /* cb_prop_op */
NULL, /* cb_stream */
D_NEW | D_MP | D_64BIT | D_DEVMAP, /* cb_flag */
CB_REV
};
static struct dev_ops ioat_dev_ops = {
DEVO_REV, /* devo_rev */
0, /* devo_refcnt */
ioat_getinfo, /* devo_getinfo */
nulldev, /* devo_identify */
nulldev, /* devo_probe */
ioat_attach, /* devo_attach */
ioat_detach, /* devo_detach */
nodev, /* devo_reset */
&ioat_cb_ops, /* devo_cb_ops */
NULL, /* devo_bus_ops */
NULL, /* devo_power */
ioat_quiesce, /* devo_quiesce */
};
static struct modldrv ioat_modldrv = {
&mod_driverops, /* Type of module. This one is a driver */
"ioat driver", /* Name of the module. */
&ioat_dev_ops, /* driver ops */
};
static struct modlinkage ioat_modlinkage = {
MODREV_1,
(void *) &ioat_modldrv,
NULL
};
void *ioat_statep;
static int ioat_chip_init(ioat_state_t *state);
static void ioat_chip_fini(ioat_state_t *state);
static int ioat_drv_init(ioat_state_t *state);
static void ioat_drv_fini(ioat_state_t *state);
static uint_t ioat_isr(caddr_t parm);
static void ioat_intr_enable(ioat_state_t *state);
static void ioat_intr_disable(ioat_state_t *state);
void ioat_detach_finish(ioat_state_t *state);
ddi_device_acc_attr_t ioat_acc_attr = {
DDI_DEVICE_ATTR_V0, /* devacc_attr_version */
DDI_NEVERSWAP_ACC, /* devacc_attr_endian_flags */
DDI_STORECACHING_OK_ACC, /* devacc_attr_dataorder */
DDI_DEFAULT_ACC /* devacc_attr_access */
};
/* dcopy callback interface */
dcopy_device_cb_t ioat_cb = {
DCOPY_DEVICECB_V0,
0, /* reserved */
ioat_channel_alloc,
ioat_channel_free,
ioat_cmd_alloc,
ioat_cmd_free,
ioat_cmd_post,
ioat_cmd_poll,
ioat_unregister_complete
};
/*
* _init()
*/
int
_init(void)
{
int e;
e = ddi_soft_state_init(&ioat_statep, sizeof (ioat_state_t), 1);
if (e != 0) {
return (e);
}
e = mod_install(&ioat_modlinkage);
if (e != 0) {
ddi_soft_state_fini(&ioat_statep);
return (e);
}
return (0);
}
/*
* _info()
*/
int
_info(struct modinfo *modinfop)
{
return (mod_info(&ioat_modlinkage, modinfop));
}
/*
* _fini()
*/
int
_fini(void)
{
int e;
e = mod_remove(&ioat_modlinkage);
if (e != 0) {
return (e);
}
ddi_soft_state_fini(&ioat_statep);
return (0);
}
/*
* ioat_attach()
*/
static int
ioat_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
ioat_state_t *state;
int instance;
int e;
switch (cmd) {
case DDI_ATTACH:
break;
case DDI_RESUME:
instance = ddi_get_instance(dip);
state = ddi_get_soft_state(ioat_statep, instance);
if (state == NULL) {
return (DDI_FAILURE);
}
e = ioat_channel_resume(state);
if (e != DDI_SUCCESS) {
return (DDI_FAILURE);
}
ioat_intr_enable(state);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
instance = ddi_get_instance(dip);
e = ddi_soft_state_zalloc(ioat_statep, instance);
if (e != DDI_SUCCESS) {
return (DDI_FAILURE);
}
state = ddi_get_soft_state(ioat_statep, instance);
if (state == NULL) {
goto attachfail_get_soft_state;
}
state->is_dip = dip;
state->is_instance = instance;
/* setup the registers, save away some device info */
e = ioat_chip_init(state);
if (e != DDI_SUCCESS) {
goto attachfail_chip_init;
}
/* initialize driver state, must be after chip init */
e = ioat_drv_init(state);
if (e != DDI_SUCCESS) {
goto attachfail_drv_init;
}
/* create the minor node (for the ioctl) */
e = ddi_create_minor_node(dip, "ioat", S_IFCHR, instance, DDI_PSEUDO,
0);
if (e != DDI_SUCCESS) {
goto attachfail_minor_node;
}
/* Enable device interrupts */
ioat_intr_enable(state);
/* Report that driver was loaded */
ddi_report_dev(dip);
/* register with dcopy */
e = dcopy_device_register(state, &state->is_deviceinfo,
&state->is_device_handle);
if (e != DCOPY_SUCCESS) {
goto attachfail_register;
}
return (DDI_SUCCESS);
attachfail_register:
ioat_intr_disable(state);
ddi_remove_minor_node(dip, NULL);
attachfail_minor_node:
ioat_drv_fini(state);
attachfail_drv_init:
ioat_chip_fini(state);
attachfail_chip_init:
attachfail_get_soft_state:
(void) ddi_soft_state_free(ioat_statep, instance);
return (DDI_FAILURE);
}
/*
* ioat_detach()
*/
static int
ioat_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
ioat_state_t *state;
int instance;
int e;
instance = ddi_get_instance(dip);
state = ddi_get_soft_state(ioat_statep, instance);
if (state == NULL) {
return (DDI_FAILURE);
}
switch (cmd) {
case DDI_DETACH:
break;
case DDI_SUSPEND:
ioat_channel_suspend(state);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
/*
* try to unregister from dcopy. Since this driver doesn't follow the
* traditional parent/child model, we may still be in use so we can't
* detach yet.
*/
e = dcopy_device_unregister(&state->is_device_handle);
if (e != DCOPY_SUCCESS) {
if (e == DCOPY_PENDING) {
cmn_err(CE_NOTE, "device busy, performing asynchronous"
" detach\n");
}
return (DDI_FAILURE);
}
ioat_detach_finish(state);
return (DDI_SUCCESS);
}
/*
* ioat_getinfo()
*/
/*ARGSUSED*/
static int
ioat_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **result)
{
ioat_state_t *state;
int instance;
dev_t dev;
int e;
dev = (dev_t)arg;
instance = getminor(dev);
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
state = ddi_get_soft_state(ioat_statep, instance);
if (state == NULL) {
return (DDI_FAILURE);
}
*result = (void *)state->is_dip;
e = DDI_SUCCESS;
break;
case DDI_INFO_DEVT2INSTANCE:
*result = (void *)(uintptr_t)instance;
e = DDI_SUCCESS;
break;
default:
e = DDI_FAILURE;
break;
}
return (e);
}
/*
* ioat_open()
*/
/*ARGSUSED*/
static int
ioat_open(dev_t *devp, int flag, int otyp, cred_t *cred)
{
ioat_state_t *state;
int instance;
instance = getminor(*devp);
state = ddi_get_soft_state(ioat_statep, instance);
if (state == NULL) {
return (ENXIO);
}
return (0);
}
/*
* ioat_close()
*/
/*ARGSUSED*/
static int
ioat_close(dev_t devp, int flag, int otyp, cred_t *cred)
{
return (0);
}
/*
* ioat_chip_init()
*/
static int
ioat_chip_init(ioat_state_t *state)
{
ddi_device_acc_attr_t attr;
int e;
attr.devacc_attr_version = DDI_DEVICE_ATTR_V0;
attr.devacc_attr_endian_flags = DDI_NEVERSWAP_ACC;
attr.devacc_attr_dataorder = DDI_STRICTORDER_ACC;
e = ddi_regs_map_setup(state->is_dip, 1, (caddr_t *)&state->is_genregs,
0, 0, &attr, &state->is_reg_handle);
if (e != DDI_SUCCESS) {
goto chipinitfail_regsmap;
}
/* save away ioat chip info */
state->is_num_channels = (uint_t)ddi_get8(state->is_reg_handle,
&state->is_genregs[IOAT_CHANCNT]);
/*
* If we get a bogus value, something is wrong with the H/W, fail to
* attach.
*/
if (state->is_num_channels == 0) {
goto chipinitfail_numchan;
}
state->is_maxxfer = (uint_t)ddi_get8(state->is_reg_handle,
&state->is_genregs[IOAT_XFERCAP]);
state->is_chanoff = (uintptr_t)ddi_get16(state->is_reg_handle,
(uint16_t *)&state->is_genregs[IOAT_PERPORT_OFF]);
state->is_cbver = (uint_t)ddi_get8(state->is_reg_handle,
&state->is_genregs[IOAT_CBVER]);
state->is_intrdelay = (uint_t)ddi_get16(state->is_reg_handle,
(uint16_t *)&state->is_genregs[IOAT_INTRDELAY]);
state->is_status = (uint_t)ddi_get16(state->is_reg_handle,
(uint16_t *)&state->is_genregs[IOAT_CSSTATUS]);
state->is_capabilities = (uint_t)ddi_get32(state->is_reg_handle,
(uint32_t *)&state->is_genregs[IOAT_DMACAPABILITY]);
if (state->is_cbver & 0x10) {
state->is_ver = IOAT_CBv1;
} else if (state->is_cbver & 0x20) {
state->is_ver = IOAT_CBv2;
} else {
goto chipinitfail_version;
}
return (DDI_SUCCESS);
chipinitfail_version:
chipinitfail_numchan:
ddi_regs_map_free(&state->is_reg_handle);
chipinitfail_regsmap:
return (DDI_FAILURE);
}
/*
* ioat_chip_fini()
*/
static void
ioat_chip_fini(ioat_state_t *state)
{
ddi_regs_map_free(&state->is_reg_handle);
}
/*
* ioat_drv_init()
*/
static int
ioat_drv_init(ioat_state_t *state)
{
ddi_acc_handle_t handle;
int e;
mutex_init(&state->is_mutex, NULL, MUTEX_DRIVER, NULL);
state->is_deviceinfo.di_dip = state->is_dip;
state->is_deviceinfo.di_num_dma = state->is_num_channels;
state->is_deviceinfo.di_maxxfer = state->is_maxxfer;
state->is_deviceinfo.di_capabilities = state->is_capabilities;
state->is_deviceinfo.di_cb = &ioat_cb;
e = pci_config_setup(state->is_dip, &handle);
if (e != DDI_SUCCESS) {
goto drvinitfail_config_setup;
}
/* read in Vendor ID */
state->is_deviceinfo.di_id = (uint64_t)pci_config_get16(handle, 0);
state->is_deviceinfo.di_id = state->is_deviceinfo.di_id << 16;
/* read in Device ID */
state->is_deviceinfo.di_id |= (uint64_t)pci_config_get16(handle, 2);
state->is_deviceinfo.di_id = state->is_deviceinfo.di_id << 32;
/* Add in chipset version */
state->is_deviceinfo.di_id |= (uint64_t)state->is_cbver;
pci_config_teardown(&handle);
e = ddi_intr_hilevel(state->is_dip, 0);
if (e != 0) {
cmn_err(CE_WARN, "hilevel interrupt not supported\n");
goto drvinitfail_hilevel;
}
/* we don't support MSIs for v2 yet */
e = ddi_add_intr(state->is_dip, 0, NULL, NULL, ioat_isr,
(caddr_t)state);
if (e != DDI_SUCCESS) {
goto drvinitfail_add_intr;
}
e = ddi_get_iblock_cookie(state->is_dip, 0, &state->is_iblock_cookie);
if (e != DDI_SUCCESS) {
goto drvinitfail_iblock_cookie;
}
e = ioat_channel_init(state);
if (e != DDI_SUCCESS) {
goto drvinitfail_channel_init;
}
return (DDI_SUCCESS);
drvinitfail_channel_init:
drvinitfail_iblock_cookie:
ddi_remove_intr(state->is_dip, 0, state->is_iblock_cookie);
drvinitfail_add_intr:
drvinitfail_hilevel:
drvinitfail_config_setup:
mutex_destroy(&state->is_mutex);
return (DDI_FAILURE);
}
/*
* ioat_drv_fini()
*/
static void
ioat_drv_fini(ioat_state_t *state)
{
ioat_channel_fini(state);
ddi_remove_intr(state->is_dip, 0, state->is_iblock_cookie);
mutex_destroy(&state->is_mutex);
}
/*
* ioat_unregister_complete()
*/
void
ioat_unregister_complete(void *device_private, int status)
{
ioat_state_t *state;
state = device_private;
if (status != DCOPY_SUCCESS) {
cmn_err(CE_WARN, "asynchronous detach aborted\n");
return;
}
cmn_err(CE_CONT, "detach completing\n");
ioat_detach_finish(state);
}
/*
* ioat_detach_finish()
*/
void
ioat_detach_finish(ioat_state_t *state)
{
ioat_intr_disable(state);
ddi_remove_minor_node(state->is_dip, NULL);
ioat_drv_fini(state);
ioat_chip_fini(state);
(void) ddi_soft_state_free(ioat_statep, state->is_instance);
}
/*
* ioat_intr_enable()
*/
static void
ioat_intr_enable(ioat_state_t *state)
{
uint32_t intr_status;
/* Clear any pending interrupts */
intr_status = ddi_get32(state->is_reg_handle,
(uint32_t *)&state->is_genregs[IOAT_ATTNSTATUS]);
if (intr_status != 0) {
ddi_put32(state->is_reg_handle,
(uint32_t *)&state->is_genregs[IOAT_ATTNSTATUS],
intr_status);
}
/* Enable interrupts on the device */
ddi_put8(state->is_reg_handle, &state->is_genregs[IOAT_INTRCTL],
IOAT_INTRCTL_MASTER_EN);
}
/*
* ioat_intr_disable()
*/
static void
ioat_intr_disable(ioat_state_t *state)
{
/*
* disable interrupts on the device. A read of the interrupt control
* register clears the enable bit.
*/
(void) ddi_get8(state->is_reg_handle,
&state->is_genregs[IOAT_INTRCTL]);
}
/*
* ioat_isr()
*/
static uint_t
ioat_isr(caddr_t parm)
{
uint32_t intr_status;
ioat_state_t *state;
uint8_t intrctrl;
uint32_t chan;
uint_t r;
int i;
state = (ioat_state_t *)parm;
intrctrl = ddi_get8(state->is_reg_handle,
&state->is_genregs[IOAT_INTRCTL]);
/* master interrupt enable should always be set */
ASSERT(intrctrl & IOAT_INTRCTL_MASTER_EN);
/* If the interrupt status bit isn't set, it's not ours */
if (!(intrctrl & IOAT_INTRCTL_INTR_STAT)) {
/* re-set master interrupt enable (since it clears on read) */
ddi_put8(state->is_reg_handle,
&state->is_genregs[IOAT_INTRCTL], intrctrl);
return (DDI_INTR_UNCLAIMED);
}
/* see which channels generated the interrupt */
intr_status = ddi_get32(state->is_reg_handle,
(uint32_t *)&state->is_genregs[IOAT_ATTNSTATUS]);
/* call the intr handler for the channels */
r = DDI_INTR_UNCLAIMED;
chan = 1;
for (i = 0; i < state->is_num_channels; i++) {
if (intr_status & chan) {
ioat_channel_intr(&state->is_channel[i]);
r = DDI_INTR_CLAIMED;
}
chan = chan << 1;
}
/*
* if interrupt status bit was set, there should have been an
* attention status bit set too.
*/
ASSERT(r == DDI_INTR_CLAIMED);
/* re-set master interrupt enable (since it clears on read) */
ddi_put8(state->is_reg_handle, &state->is_genregs[IOAT_INTRCTL],
intrctrl);
return (r);
}
static int
ioat_quiesce(dev_info_t *dip)
{
ioat_state_t *state;
int instance;
instance = ddi_get_instance(dip);
state = ddi_get_soft_state(ioat_statep, instance);
if (state == NULL) {
return (DDI_FAILURE);
}
ioat_intr_disable(state);
ioat_channel_quiesce(state);
return (DDI_SUCCESS);
}