#include <sys/types.h>
#include <sys/cmn_err.h>
#include <sys/conf.h>
#include <sys/errno.h>
#include <sys/debug.h>
#include <sys/ddidmareq.h>
#include <sys/ddi_impldefs.h>
#include <sys/dma_engine.h>
#include <sys/modctl.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/sunndi.h>
#include <sys/mach_intr.h>
#include <sys/kmem.h>
#include <sys/pci.h>
#include <sys/promif.h>
#include <sys/pci_intr_lib.h>
#include <sys/apic.h>
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
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);
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);
static int pciide_alloc_intr(dev_info_t *, dev_info_t *,
ddi_intr_handle_impl_t *, void *);
static int pciide_free_intr(dev_info_t *, dev_info_t *,
ddi_intr_handle_impl_t *);
extern int (*psm_intr_ops)(dev_info_t *, ddi_intr_handle_impl_t *,
psm_intr_op_t, int *);
struct bus_ops pciide_bus_ops = {
BUSO_REV,
pciide_bus_map,
0,
0,
0,
i_ddi_map_fault,
0,
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,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
pciide_intr_ops
};
struct dev_ops pciide_ops = {
DEVO_REV,
0,
ddi_no_info,
nulldev,
nulldev,
pciide_attach,
pciide_detach,
nodev,
(struct cb_ops *)0,
&pciide_bus_ops,
NULL,
ddi_quiesce_not_needed,
};
static struct modldrv modldrv = {
&mod_driverops,
"pciide nexus driver for 'PCI-IDE' 1.26",
&pciide_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:
rc = pci_config_setup(dip, &conf_hdl);
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:
if (pci_restore_config_regs(dip) != DDI_SUCCESS) {
cmn_err(CE_WARN,
"Couldn't restore PCI config regs for %s(%p)",
ddi_node_name(dip), (void *) dip);
}
#ifdef DEBUG
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);
}
int
pciide_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
switch (cmd) {
case DDI_DETACH:
return (DDI_SUCCESS);
case DDI_SUSPEND:
if (pci_save_config_regs(dip) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
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:
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;
}
rnumber++;
if (*(int *)arg != 2) {
return (ddi_ctlops(dip, dip, ctlop, &rnumber, result));
}
tmp = 8;
rc = ddi_ctlops(dip, dip, ctlop, &rnumber, &tmp);
if (controller == 1) {
if (tmp < 8)
tmp = 0;
else
tmp -= 8;
}
if (tmp > 8)
tmp = 8;
*(off_t *)result = tmp;
return (rc);
case DDI_CTLOPS_ATTACH:
case DDI_CTLOPS_DETACH:
return (DDI_SUCCESS);
default:
return (ddi_ctlops(dip, rdip, ctlop, arg, result));
}
}
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"));
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);
}
dev = *rp;
ddi_prop_free(rp);
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);
pciide_compat_setup(mydip, cdip, dev);
if (PCIIDE_NATIVE_MODE(cdip)) {
vec = 1;
} else {
vec = ddi_prop_get_int(DDI_DEV_T_ANY, cdip, DDI_PROP_DONTPASS,
"interrupts", -1);
if (vec == -1) {
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;
switch (rnumber) {
case 0:
case 1:
mp->map_obj.rnumber += (controller * 2);
break;
case 2:
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;
}
mp->map_obj.rnumber++;
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"));
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);
}
if ((ppdptr = ddi_get_parent_data(rdip)) == NULL) {
PDBG(("pciide_get_ispec null\n"));
return (NULL);
}
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);
}
if (ispecp->intrspec_pri != 0) {
*pri = ispecp->intrspec_pri;
PDBG(("pciide_get_pri ok2\n"));
return (DDI_SUCCESS);
}
ispecp->intrspec_pri = 5;
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:
return (pciide_alloc_intr(dip, rdip, hdlp, result));
case DDI_INTROP_FREE:
return (pciide_free_intr(dip, rdip, hdlp));
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:
case DDI_INTROP_DISABLE:
if (PCIIDE_NATIVE_MODE(rdip)) {
rdip = dip;
dip = ddi_get_parent(dip);
} else {
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
return (rc);
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
int
pciide_alloc_intr(dev_info_t *dip, dev_info_t *rdip,
ddi_intr_handle_impl_t *hdlp, void *result)
{
struct intrspec *ispec;
ddi_intr_handle_impl_t info_hdl;
int ret;
int free_phdl = 0;
apic_get_type_t type_info;
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
if ((ispec = pciide_get_ispec(dip, rdip, hdlp->ih_inum)) == NULL)
return (DDI_FAILURE);
bzero(&info_hdl, sizeof (ddi_intr_handle_impl_t));
info_hdl.ih_private = &type_info;
if ((*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_APIC_TYPE, NULL) ==
PSM_SUCCESS && strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) {
if (hdlp->ih_private == NULL) {
free_phdl = 1;
i_ddi_alloc_intr_phdl(hdlp);
}
((ihdl_plat_t *)hdlp->ih_private)->ip_ispecp = ispec;
if (PCIIDE_NATIVE_MODE(rdip)) {
rdip = dip;
dip = ddi_get_parent(dip);
} else {
dip = ddi_root_node();
}
ret = (*psm_intr_ops)(rdip, hdlp,
PSM_INTR_OP_ALLOC_VECTORS, result);
if (free_phdl) {
free_phdl = 0;
i_ddi_free_intr_phdl(hdlp);
}
} else {
*(int *)result = hdlp->ih_scratch1;
ret = DDI_SUCCESS;
}
return (ret);
}
int
pciide_free_intr(dev_info_t *dip, dev_info_t *rdip,
ddi_intr_handle_impl_t *hdlp)
{
struct intrspec *ispec;
ddi_intr_handle_impl_t info_hdl;
apic_get_type_t type_info;
if (psm_intr_ops == NULL)
return (DDI_FAILURE);
bzero(&info_hdl, sizeof (ddi_intr_handle_impl_t));
info_hdl.ih_private = &type_info;
if ((*psm_intr_ops)(NULL, &info_hdl, PSM_INTR_OP_APIC_TYPE, NULL) ==
PSM_SUCCESS && strcmp(type_info.avgi_type, APIC_APIX_NAME) == 0) {
if ((ispec = pciide_get_ispec(dip, rdip, hdlp->ih_inum)) ==
NULL)
return (DDI_FAILURE);
((ihdl_plat_t *)hdlp->ih_private)->ip_ispecp = ispec;
if (PCIIDE_NATIVE_MODE(rdip)) {
rdip = dip;
dip = ddi_get_parent(dip);
} else {
dip = ddi_root_node();
}
return ((*psm_intr_ops)(rdip, hdlp,
PSM_INTR_OP_FREE_VECTORS, NULL));
}
return (DDI_SUCCESS);
}
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) {
if ((dev == 0 && !(class_code & PCI_IDE_IF_NATIVE_PRI)) ||
(dev == 1 && !(class_code & PCI_IDE_IF_NATIVE_SEC))) {
rc = ndi_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 {
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));
}
static int pciide_transform[2][2][5] = {
+1, +1, +1, +1, -4,
+3, +3, -2, -2, -2,
+0, +0, +1, +1, -2,
+0, +0, +0, +0, +0
};
static int
pciide_map_rnumber(int rnumber, int pri_native, int sec_native)
{
pri_native = pri_native ? 1 : 0;
sec_native = sec_native ? 1 : 0;
rnumber += pciide_transform[pri_native][sec_native][rnumber];
return (rnumber);
}