/* * 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #pragma ident "%Z%%M% %I% %E% SMI" /* * PCI-IDE bus nexus driver */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include int pciide_attach(dev_info_t *dip, ddi_attach_cmd_t cmd); int pciide_detach(dev_info_t *dip, ddi_detach_cmd_t cmd); #define PCIIDE_NATIVE_MODE(dip) \ (!ddi_prop_exists(DDI_DEV_T_ANY, (dip), DDI_PROP_DONTPASS, \ "compatibility-mode")) #define PCIIDE_PRE26(dip) \ ddi_prop_exists(DDI_DEV_T_ANY, (dip), 0, "ignore-hardware-nodes") #define PCI_IDE_IF_BM_CAP_MASK 0x80 #define PCIIDE_PDSIZE (sizeof (struct ddi_parent_private_data) + \ sizeof (struct intrspec)) #ifdef DEBUG static int pci_ide_debug = 0; #define PDBG(fmt) \ if (pci_ide_debug) { \ prom_printf fmt; \ } #else #define PDBG(fmt) #endif #ifndef TRUE #define TRUE 1 #endif #ifndef FALSE #define FALSE 0 #endif /* * bus_ops functions */ static int pciide_bus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t offset, off_t len, caddr_t *vaddrp); static int pciide_ddi_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg, void *result); static int pciide_get_pri(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp, int *pri); static int pciide_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result); static struct intrspec *pciide_get_ispec(dev_info_t *dip, dev_info_t *rdip, int inum); /* * Local Functions */ static int pciide_initchild(dev_info_t *mydip, dev_info_t *cdip); static void pciide_compat_setup(dev_info_t *mydip, dev_info_t *cdip, int dev); static int pciide_pre26_rnumber_map(dev_info_t *mydip, int rnumber); static int pciide_map_rnumber(int canonical_rnumber, int pri_native, int sec_native); /* * Config information */ struct bus_ops pciide_bus_ops = { BUSO_REV, pciide_bus_map, 0, 0, 0, i_ddi_map_fault, ddi_dma_map, ddi_dma_allochdl, ddi_dma_freehdl, ddi_dma_bindhdl, ddi_dma_unbindhdl, ddi_dma_flush, ddi_dma_win, ddi_dma_mctl, pciide_ddi_ctlops, ddi_bus_prop_op, 0, /* (*bus_get_eventcookie)(); */ 0, /* (*bus_add_eventcall)(); */ 0, /* (*bus_remove_eventcall)(); */ 0, /* (*bus_post_event)(); */ 0, 0, 0, 0, 0, 0, 0, 0, pciide_intr_ops }; struct dev_ops pciide_ops = { DEVO_REV, /* devo_rev, */ 0, /* refcnt */ ddi_no_info, /* info */ nulldev, /* identify */ nulldev, /* probe */ pciide_attach, /* attach */ pciide_detach, /* detach */ nodev, /* reset */ (struct cb_ops *)0, /* driver operations */ &pciide_bus_ops /* bus operations */ }; /* * Module linkage information for the kernel. */ static struct modldrv modldrv = { &mod_driverops, /* Type of module. This is PCI-IDE bus driver */ "pciide nexus driver for 'PCI-IDE' %I%", &pciide_ops, /* driver ops */ }; static struct modlinkage modlinkage = { MODREV_1, &modldrv, NULL }; int _init(void) { return (mod_install(&modlinkage)); } int _fini(void) { return (mod_remove(&modlinkage)); } int _info(struct modinfo *modinfop) { return (mod_info(&modlinkage, modinfop)); } int pciide_attach(dev_info_t *dip, ddi_attach_cmd_t cmd) { uint16_t cmdreg; ddi_acc_handle_t conf_hdl = NULL; int rc; switch (cmd) { case DDI_ATTACH: /* * Make sure bus-mastering is enabled, even if * BIOS didn't. */ rc = pci_config_setup(dip, &conf_hdl); /* * In case of error, return SUCCESS. This is because * bus-mastering could be already enabled by BIOS. */ if (rc != DDI_SUCCESS) return (DDI_SUCCESS); cmdreg = pci_config_get16(conf_hdl, PCI_CONF_COMM); if ((cmdreg & PCI_COMM_ME) == 0) { pci_config_put16(conf_hdl, PCI_CONF_COMM, cmdreg | PCI_COMM_ME); } pci_config_teardown(&conf_hdl); return (DDI_SUCCESS); case DDI_RESUME: /* Restore our PCI configuration header */ if (pci_restore_config_regs(dip) != DDI_SUCCESS) { /* * XXXX * This is a pretty bad thing. However, for some * reason it always happens. To further complicate * things, it appears if we just ignore this, we * properly resume. For now, all I want to do is * to generate this message so that it doesn't get * forgotten. */ cmn_err(CE_WARN, "Couldn't restore PCI config regs for %s(%p)", ddi_node_name(dip), (void *) dip); } #ifdef DEBUG /* Bus mastering should still be enabled */ if (pci_config_setup(dip, &conf_hdl) != DDI_SUCCESS) return (DDI_FAILURE); cmdreg = pci_config_get16(conf_hdl, PCI_CONF_COMM); ASSERT((cmdreg & PCI_COMM_ME) != 0); pci_config_teardown(&conf_hdl); #endif return (DDI_SUCCESS); } return (DDI_FAILURE); } /*ARGSUSED*/ int pciide_detach(dev_info_t *dip, ddi_detach_cmd_t cmd) { switch (cmd) { case DDI_DETACH: return (DDI_SUCCESS); case DDI_SUSPEND: /* Save our PCI configuration header */ if (pci_save_config_regs(dip) != DDI_SUCCESS) { /* Don't suspend if we cannot save config regs */ return (DDI_FAILURE); } return (DDI_SUCCESS); } return (DDI_FAILURE); } /*ARGSUSED*/ static int pciide_ddi_ctlops(dev_info_t *dip, dev_info_t *rdip, ddi_ctl_enum_t ctlop, void *arg, void *result) { dev_info_t *cdip; int controller; void *pdptr; int rnumber; off_t tmp; int rc; PDBG(("pciide_bus_ctl\n")); switch (ctlop) { case DDI_CTLOPS_INITCHILD: cdip = (dev_info_t *)arg; return (pciide_initchild(dip, cdip)); case DDI_CTLOPS_UNINITCHILD: cdip = (dev_info_t *)arg; pdptr = ddi_get_parent_data(cdip); ddi_set_parent_data(cdip, NULL); ddi_set_name_addr(cdip, NULL); kmem_free(pdptr, PCIIDE_PDSIZE); return (DDI_SUCCESS); case DDI_CTLOPS_NREGS: *(int *)result = 3; return (DDI_SUCCESS); case DDI_CTLOPS_REGSIZE: /* * Adjust the rnumbers based on which controller instance * is requested; adjust for the 2 tuples per controller. */ if (strcmp("0", ddi_get_name_addr(rdip)) == 0) controller = 0; else controller = 1; switch (rnumber = *(int *)arg) { case 0: case 1: rnumber += (2 * controller); break; case 2: rnumber = 4; break; default: PDBG(("pciide_ctlops invalid rnumber\n")); return (DDI_FAILURE); } if (PCIIDE_PRE26(dip)) { int old_rnumber; int new_rnumber; old_rnumber = rnumber; new_rnumber = pciide_pre26_rnumber_map(dip, old_rnumber); PDBG(("pciide rnumber old %d new %d\n", old_rnumber, new_rnumber)); rnumber = new_rnumber; } /* * Add 1 to skip over the PCI config space tuple */ rnumber++; /* * If it's not tuple #2 pass the adjusted request to my parent */ if (*(int *)arg != 2) { return (ddi_ctlops(dip, dip, ctlop, &rnumber, result)); } /* * Handle my child's reg-tuple #2 here by splitting my 16 byte * reg-tuple #4 into two 8 byte ranges based on the * the child's controller #. */ tmp = 8; rc = ddi_ctlops(dip, dip, ctlop, &rnumber, &tmp); /* * Allow for the possibility of less than 16 bytes by * by checking what's actually returned for my reg-tuple #4. */ if (controller == 1) { if (tmp < 8) tmp = 0; else tmp -= 8; } if (tmp > 8) tmp = 8; *(off_t *)result = tmp; return (rc); default: return (ddi_ctlops(dip, rdip, ctlop, arg, result)); } } /* * IEEE 1275 Working Group Proposal #414 says that the Primary * controller is "ata@0" and the Secondary controller "ata@1". * * By the time we get here, boot Bootconf (2.6+) has created devinfo * nodes with the appropriate "reg", "assigned-addresses" and "interrupts" * properites on the pci-ide node and both ide child nodes. * * In compatibility mode the "reg" and "assigned-addresses" properties * of the pci-ide node are set up like this: * * 1. PCI-IDE Nexus * * interrupts=0 * (addr-hi addr-mid addr-low size-hi size-low) * reg= assigned-addresses=00000000.00000000.00000000.00000000.00000000 * 81000000.00000000.000001f0.00000000.00000008 * 81000000.00000000.000003f4.00000000.00000004 * 81000000.00000000,00000170.00000000.00000008 * 81000000.00000000,00000374.00000000.00000004 * 01000020.00000000,-[BAR4]-.00000000.00000010 * * In native PCI mode the "reg" and "assigned-addresses" properties * would be set up like this: * * 2. PCI-IDE Nexus * * interrupts=0 * reg= assigned-addresses=00000000.00000000.00000000.00000000.00000000 * 01000010.00000000.-[BAR0]-.00000000.00000008 * 01000014,00000000.-[BAR1]-.00000000.00000004 * 01000018.00000000.-[BAR2]-.00000000.00000008 * 0100001c.00000000.-[BAR3]-.00000000.00000004 * 01000020.00000000.-[BAR4]-.00000000.00000010 * * * In both modes the child nodes simply have the following: * * 2. primary controller (compatibility mode) * * interrupts=14 * reg=00000000 * * 3. secondary controller * * interrupts=15 * reg=00000001 * * The pciide_bus_map() function is responsible for turning requests * to map primary or secondary controller rnumbers into mapping requests * of the appropriate regspec on the pci-ide node. * */ static int pciide_initchild(dev_info_t *mydip, dev_info_t *cdip) { struct ddi_parent_private_data *pdptr; struct intrspec *ispecp; int vec; int *rp; uint_t proplen; char name[80]; int dev; PDBG(("pciide_initchild\n")); /* * Set the address portion of the node name based on * the controller number (0 or 1) from the 'reg' property. */ if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS, "reg", &rp, (uint_t *)&proplen) != DDI_PROP_SUCCESS) { PDBG(("pciide_intchild prop error\n")); return (DDI_NOT_WELL_FORMED); } /* * copy the controller number and * free the memory allocated by ddi_prop_lookup_int_array */ dev = *rp; ddi_prop_free(rp); /* * I only support two controllers per device, determine * which this one is and set its unit address. */ if (dev > 1) { PDBG(("pciide_initchild bad dev\n")); return (DDI_NOT_WELL_FORMED); } (void) sprintf(name, "%d", dev); ddi_set_name_addr(cdip, name); /* * determine if this instance is running in native or compat mode */ pciide_compat_setup(mydip, cdip, dev); /* interrupts property is required */ if (PCIIDE_NATIVE_MODE(cdip)) { vec = 1; } else { /* * In compatibility mode, dev 0 should always be * IRQ 14 and dev 1 is IRQ 15. If for some reason * this needs to be changed, do it via the interrupts * property in the ata.conf file. */ vec = ddi_prop_get_int(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS, "interrupts", -1); if (vec == -1) { /* setup compatibility mode interrupts */ if (dev == 0) { vec = 14; } else if (dev == 1) { vec = 15; } else { PDBG(("pciide_initchild bad intr\n")); return (DDI_NOT_WELL_FORMED); } } } pdptr = kmem_zalloc(PCIIDE_PDSIZE, KM_SLEEP); ispecp = (struct intrspec *)(pdptr + 1); pdptr->par_nintr = 1; pdptr->par_intr = ispecp; ispecp->intrspec_vec = vec; ddi_set_parent_data(cdip, pdptr); PDBG(("pciide_initchild okay\n")); return (DDI_SUCCESS); } static int pciide_bus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp, off_t offset, off_t len, caddr_t *vaddrp) { dev_info_t *pdip; int rnumber = mp->map_obj.rnumber; int controller; int rc; PDBG(("pciide_bus_map\n")); if (strcmp("0", ddi_get_name_addr(rdip)) == 0) controller = 0; else controller = 1; /* * Adjust the rnumbers based on which controller instance * is being mapped; adjust for the 2 tuples per controller. */ switch (rnumber) { case 0: case 1: mp->map_obj.rnumber += (controller * 2); break; case 2: /* * split the 16 I/O ports into two 8 port ranges */ mp->map_obj.rnumber = 4; if (offset + len > 8) { PDBG(("pciide_bus_map offset\n")); return (DDI_FAILURE); } if (len == 0) len = 8 - offset; offset += 8 * controller; break; default: PDBG(("pciide_bus_map default\n")); return (DDI_FAILURE); } if (PCIIDE_PRE26(dip)) { int old_rnumber; int new_rnumber; old_rnumber = mp->map_obj.rnumber; new_rnumber = pciide_pre26_rnumber_map(dip, old_rnumber); PDBG(("pciide rnumber old %d new %d\n", old_rnumber, new_rnumber)); mp->map_obj.rnumber = new_rnumber; } /* * Add 1 to skip over the PCI config space tuple */ mp->map_obj.rnumber++; /* * pass the adjusted request to my parent */ pdip = ddi_get_parent(dip); rc = ((*(DEVI(pdip)->devi_ops->devo_bus_ops->bus_map)) (pdip, dip, mp, offset, len, vaddrp)); PDBG(("pciide_bus_map %s\n", rc == DDI_SUCCESS ? "okay" : "!ok")); return (rc); } static struct intrspec * pciide_get_ispec(dev_info_t *dip, dev_info_t *rdip, int inumber) { struct ddi_parent_private_data *ppdptr; PDBG(("pciide_get_ispec\n")); /* * Native mode PCI-IDE controllers share the parent's * PCI interrupt line. * * Compatibility mode PCI-IDE controllers have their * own intrspec which specifies ISA IRQ 14 or 15. * */ if (PCIIDE_NATIVE_MODE(rdip)) { ddi_intrspec_t is; is = pci_intx_get_ispec(dip, dip, inumber); PDBG(("pciide_get_ispec okay\n")); return ((struct intrspec *)is); } /* Else compatibility mode, use the ISA IRQ */ if ((ppdptr = ddi_get_parent_data(rdip)) == NULL) { PDBG(("pciide_get_ispec null\n")); return (NULL); } /* validate the interrupt number */ if (inumber >= ppdptr->par_nintr) { PDBG(("pciide_get_inum\n")); return (NULL); } PDBG(("pciide_get_ispec ok\n")); return ((struct intrspec *)&ppdptr->par_intr[inumber]); } static int pciide_get_pri(dev_info_t *dip, dev_info_t *rdip, ddi_intr_handle_impl_t *hdlp, int *pri) { struct intrspec *ispecp; int *intpriorities; uint_t num_intpriorities; PDBG(("pciide_get_pri\n")); if ((ispecp = pciide_get_ispec(dip, rdip, hdlp->ih_inum)) == NULL) { PDBG(("pciide_get_pri null\n")); return (DDI_FAILURE); } if (PCIIDE_NATIVE_MODE(rdip)) { *pri = ispecp->intrspec_pri; PDBG(("pciide_get_pri ok\n")); return (DDI_SUCCESS); } /* check if the intrspec has been initialized */ if (ispecp->intrspec_pri != 0) { *pri = ispecp->intrspec_pri; PDBG(("pciide_get_pri ok2\n")); return (DDI_SUCCESS); } /* Use a default of level 5 */ ispecp->intrspec_pri = 5; /* * If there's an interrupt-priorities property, use it to * over-ride the default interrupt priority. */ if (ddi_prop_lookup_int_array(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS, "interrupt-priorities", &intpriorities, &num_intpriorities) == DDI_PROP_SUCCESS) { if (hdlp->ih_inum < num_intpriorities) ispecp->intrspec_pri = intpriorities[hdlp->ih_inum]; ddi_prop_free(intpriorities); } *pri = ispecp->intrspec_pri; PDBG(("pciide_get_pri ok3\n")); return (DDI_SUCCESS); } static int pciide_intr_ops(dev_info_t *dip, dev_info_t *rdip, ddi_intr_op_t intr_op, ddi_intr_handle_impl_t *hdlp, void *result) { struct intrspec *ispecp; int rc; int pri = 0; PDBG(("pciide_intr_ops: dip %p rdip %p op %x hdlp %p\n", (void *)dip, (void *)rdip, intr_op, (void *)hdlp)); switch (intr_op) { case DDI_INTROP_SUPPORTED_TYPES: *(int *)result = DDI_INTR_TYPE_FIXED; break; case DDI_INTROP_GETCAP: *(int *)result = DDI_INTR_FLAG_LEVEL; break; case DDI_INTROP_NINTRS: case DDI_INTROP_NAVAIL: *(int *)result = (!PCIIDE_NATIVE_MODE(rdip)) ? i_ddi_get_intx_nintrs(rdip) : 1; break; case DDI_INTROP_ALLOC: if ((ispecp = pciide_get_ispec(dip, rdip, hdlp->ih_inum)) == NULL) return (DDI_FAILURE); *(int *)result = hdlp->ih_scratch1; break; case DDI_INTROP_FREE: break; case DDI_INTROP_GETPRI: if (pciide_get_pri(dip, rdip, hdlp, &pri) != DDI_SUCCESS) { *(int *)result = 0; return (DDI_FAILURE); } *(int *)result = pri; break; case DDI_INTROP_ADDISR: if ((ispecp = pciide_get_ispec(dip, rdip, hdlp->ih_inum)) == NULL) return (DDI_FAILURE); ((ihdl_plat_t *)hdlp->ih_private)->ip_ispecp = ispecp; ispecp->intrspec_func = hdlp->ih_cb_func; break; case DDI_INTROP_REMISR: if ((ispecp = pciide_get_ispec(dip, rdip, hdlp->ih_inum)) == NULL) return (DDI_FAILURE); ispecp->intrspec_func = (uint_t (*)()) 0; break; case DDI_INTROP_ENABLE: /* FALLTHRU */ case DDI_INTROP_DISABLE: if (PCIIDE_NATIVE_MODE(rdip)) { rdip = dip; dip = ddi_get_parent(dip); } else { /* get ptr to the root node */ dip = ddi_root_node(); } rc = (*(DEVI(dip)->devi_ops->devo_bus_ops->bus_intr_op))(dip, rdip, intr_op, hdlp, result); #ifdef DEBUG if (intr_op == DDI_INTROP_ENABLE) { PDBG(("pciide_enable rc=%d", rc)); } else PDBG(("pciide_disable rc=%d", rc)); #endif /* DEBUG */ return (rc); default: return (DDI_FAILURE); } return (DDI_SUCCESS); } /* * This is one of the places where controller specific setup needs to be * considered. * At this point the controller was already pre-qualified as a known and * supported pciide controller. * Some controllers do not provide PCI_MASS_IDE sub-class code and IDE * programming interface code but rather PCI_MASS_OTHER sub-class code * without any additional data. * For those controllers IDE programming interface cannot be extracted * from PCI class - we assume that they are pci-native type and we fix * the programming interface used by other functions. * The programming interface byte is set to indicate pci-native mode * for both controllers and the Bus Master DMA capabilitiy of the controller. */ static void pciide_compat_setup(dev_info_t *mydip, dev_info_t *cdip, int dev) { int class_code; int rc = DDI_PROP_SUCCESS; class_code = ddi_prop_get_int(DDI_DEV_T_ANY, mydip, DDI_PROP_DONTPASS, "class-code", 0); if (((class_code & 0x00FF00) >> 8) == PCI_MASS_IDE) { /* * Controller provides PCI_MASS_IDE sub-class code first * (implied IDE programming interface) */ if ((dev == 0 && !(class_code & PCI_IDE_IF_NATIVE_PRI)) || (dev == 1 && !(class_code & PCI_IDE_IF_NATIVE_SEC))) { rc = ddi_prop_update_int(DDI_DEV_T_NONE, cdip, "compatibility-mode", 1); if (rc != DDI_PROP_SUCCESS) cmn_err(CE_WARN, "pciide prop error %d compat-mode", rc); } } else { /* * Pci-ide controllers not providing PCI_MASS_IDE sub-class are * assumed to be of pci-native type and bus master DMA capable. * Programming interface part of the class-code property is * fixed here. */ class_code &= 0x00ffff00; class_code |= PCI_IDE_IF_BM_CAP_MASK | PCI_IDE_IF_NATIVE_PRI | PCI_IDE_IF_NATIVE_SEC; rc = ddi_prop_update_int(DDI_DEV_T_NONE, mydip, "class-code", class_code); if (rc != DDI_PROP_SUCCESS) cmn_err(CE_WARN, "pciide prop error %d class-code", rc); } } static int pciide_pre26_rnumber_map(dev_info_t *mydip, int rnumber) { int pri_native; int sec_native; int class_code; class_code = ddi_prop_get_int(DDI_DEV_T_ANY, mydip, DDI_PROP_DONTPASS, "class-code", 0); pri_native = (class_code & PCI_IDE_IF_NATIVE_PRI) ? TRUE : FALSE; sec_native = (class_code & PCI_IDE_IF_NATIVE_SEC) ? TRUE : FALSE; return (pciide_map_rnumber(rnumber, pri_native, sec_native)); } /* * The canonical order of the reg property tuples for the * Base Address Registers is supposed to be: * * primary controller (BAR 0) * primary controller (BAR 1) * secondary controller (BAR 2) * secondary controller (BAR 3) * bus mastering regs (BAR 4) * * For 2.6, bootconf has been fixed to always generate the * reg property (and assigned-addresses property) tuples * in the above order. * * But in releases prior to 2.6 the order varies depending * on whether compatibility or native mode is being used for * each controller. There ends up being four possible * orders: * * BM, P0, P1, S0, S1 primary compatible, secondary compatible * S0, S1, BM, P0, P1 primary compatible, secondary native * P0, P1, BM, S0, S1 primary native, secondary compatible * P0, P1, S0, S1, BM primary native, secondary native * * where: Px is the primary tuples, Sx the secondary tuples, and * B the Bus Master tuple. * * Here's the results for each of the four states: * * 0, 1, 2, 3, 4 * * CC 1, 2, 3, 4, 0 * CN 3, 4, 0, 1, 2 * NC 0, 1, 3, 4, 2 * NN 0, 1, 2, 3, 4 * * C = compatible(!native) == 0 * N = native == 1 * * Here's the transformation matrix: */ static int pciide_transform[2][2][5] = { /* P S */ /* [C][C] */ +1, +1, +1, +1, -4, /* [C][N] */ +3, +3, -2, -2, -2, /* [N][C] */ +0, +0, +1, +1, -2, /* [N][N] */ +0, +0, +0, +0, +0 }; static int pciide_map_rnumber(int rnumber, int pri_native, int sec_native) { /* transform flags into indexes */ pri_native = pri_native ? 1 : 0; sec_native = sec_native ? 1 : 0; rnumber += pciide_transform[pri_native][sec_native][rnumber]; return (rnumber); }