/* * 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 #include #include #include #include #include #include #include #include #include #include #include #include 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); }