#include <sys/types.h>
#include <sys/conf.h>
#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_subrdefs.h>
#include <sys/pci.h>
#include <sys/autoconf.h>
#include <sys/cmn_err.h>
#include <sys/errno.h>
#include <sys/kmem.h>
#include <sys/debug.h>
#include <sys/sysmacros.h>
#include <sys/pmubus.h>
#include <sys/nexusdebug.h>
#define PMUBUS_MAP_DEBUG 0x1
#define PMUBUS_REGACCESS_DEBUG 0x2
#define PMUBUS_RW_DEBUG 0x4
static int pmubus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t off, off_t len, caddr_t *addrp);
static int pmubus_ctlops(dev_info_t *dip, dev_info_t *rdip,
ddi_ctl_enum_t op, void *arg, void *result);
static int pmubus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int pmubus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static struct bus_ops pmubus_bus_ops = {
BUSO_REV,
pmubus_map,
NULL,
NULL,
NULL,
i_ddi_map_fault,
NULL,
ddi_dma_allochdl,
ddi_dma_freehdl,
ddi_dma_bindhdl,
ddi_dma_unbindhdl,
ddi_dma_flush,
ddi_dma_win,
ddi_dma_mctl,
pmubus_ctlops,
ddi_bus_prop_op,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
0,
i_ddi_intr_ops
};
static struct dev_ops pmubus_ops = {
DEVO_REV,
0,
ddi_no_info,
nulldev,
0,
pmubus_attach,
pmubus_detach,
nodev,
(struct cb_ops *)0,
&pmubus_bus_ops,
NULL,
ddi_quiesce_not_needed,
};
#include <sys/modctl.h>
extern struct mod_ops mod_driverops;
static struct modldrv modldrv = {
&mod_driverops,
"pmubus nexus driver",
&pmubus_ops,
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modldrv, NULL
};
static void *per_pmubus_state;
int
_init(void)
{
int e;
e = ddi_soft_state_init(&per_pmubus_state,
sizeof (pmubus_devstate_t), 1);
if (e != 0)
return (e);
e = mod_install(&modlinkage);
if (e != 0)
ddi_soft_state_fini(&per_pmubus_state);
return (e);
}
int
_fini(void)
{
int e;
e = mod_remove(&modlinkage);
if (e != 0)
return (e);
ddi_soft_state_fini(&per_pmubus_state);
return (e);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
pmubus_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
pmubus_devstate_t *pmubusp;
int32_t instance;
switch (cmd) {
case DDI_ATTACH:
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(per_pmubus_state, instance) !=
DDI_SUCCESS) {
cmn_err(CE_WARN, "pmubus_attach: Can't allocate soft "
"state.\n");
goto fail_exit;
}
pmubusp = ddi_get_soft_state(per_pmubus_state, instance);
pmubusp->pmubus_dip = dip;
if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"reg", (caddr_t)&pmubusp->pmubus_regp,
&pmubusp->pmubus_reglen) != DDI_SUCCESS) {
cmn_err(CE_WARN, "pmubus_attach: Can't acquire reg "
"property.\n");
goto fail_get_regs;
}
if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"ranges", (caddr_t)&pmubusp->pmubus_rangep,
&pmubusp->pmubus_rnglen) != DDI_SUCCESS) {
cmn_err(CE_WARN, "pmubus_attach: Can't acquire the "
"ranges property.\n");
goto fail_get_ranges;
}
pmubusp->pmubus_nranges =
pmubusp->pmubus_rnglen / sizeof (pmu_rangespec_t);
if (pci_config_setup(dip, &pmubusp->pmubus_reghdl) !=
DDI_SUCCESS) {
cmn_err(CE_WARN, "pmubus_attach: Can't map in "
"register space.\n");
goto fail_map_regs;
}
mutex_init(&pmubusp->pmubus_reg_access_lock, NULL,
MUTEX_DRIVER, NULL);
ddi_report_dev(dip);
return (DDI_SUCCESS);
case DDI_RESUME:
return (DDI_SUCCESS);
}
fail_map_regs:
kmem_free(pmubusp->pmubus_rangep, pmubusp->pmubus_rnglen);
fail_get_ranges:
kmem_free(pmubusp->pmubus_regp, pmubusp->pmubus_reglen);
fail_get_regs:
ddi_soft_state_free(per_pmubus_state, instance);
fail_exit:
return (DDI_FAILURE);
}
static int
pmubus_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance = ddi_get_instance(dip);
pmubus_devstate_t *pmubusp = ddi_get_soft_state(per_pmubus_state,
instance);
switch (cmd) {
case DDI_DETACH:
mutex_destroy(&pmubusp->pmubus_reg_access_lock);
pci_config_teardown(&pmubusp->pmubus_reghdl);
kmem_free(pmubusp->pmubus_rangep, pmubusp->pmubus_rnglen);
kmem_free(pmubusp->pmubus_regp, pmubusp->pmubus_reglen);
ddi_soft_state_free(per_pmubus_state, instance);
break;
case DDI_SUSPEND:
default:
break;
}
return (DDI_SUCCESS);
}
void
pmubus_norep_get8(ddi_acc_impl_t *handle, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
pmubus_norep_get16(ddi_acc_impl_t *handle, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
pmubus_norep_get32(ddi_acc_impl_t *handle, uint32_t *host_addr,
uint32_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
pmubus_norep_get64(ddi_acc_impl_t *handle, uint64_t *host_addr,
uint64_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
pmubus_norep_put8(ddi_acc_impl_t *handle, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
pmubus_norep_put16(ddi_acc_impl_t *handle, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
pmubus_norep_put32(ddi_acc_impl_t *handle, uint32_t *host_addr,
uint32_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
pmubus_norep_put64(ddi_acc_impl_t *handle, uint64_t *host_addr,
uint64_t *dev_addr, size_t repcount, uint_t flags)
{
}
uint8_t
pmubus_get8(ddi_acc_impl_t *hdlp, uint8_t *addr)
{
ddi_acc_hdl_t *hp = (ddi_acc_hdl_t *)hdlp;
pmubus_mapreq_t *pmubus_mapreqp = hp->ah_bus_private;
pmubus_devstate_t *softsp = pmubus_mapreqp->mapreq_softsp;
off_t offset;
uint8_t value;
uint8_t mask;
offset = pmubus_mapreqp->mapreq_addr + (uintptr_t)addr;
offset &= PMUBUS_REGOFFSET;
if ((pmubus_mapreqp->mapreq_flags) & MAPREQ_SHARED_BITS) {
if (addr != 0 ||
pmubus_mapreqp->mapreq_size != sizeof (value)) {
cmn_err(CE_WARN, "pmubus_get8: load discarded, "
"incorrect access addr/size");
return ((uint8_t)-1);
}
mask = pmubus_mapreqp->mapreq_mask;
} else {
mask = (uint8_t)-1;
}
value = pci_config_get8(softsp->pmubus_reghdl, offset) & mask;
DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_get8: addr=%p offset=%lx value=%x "
"mask=%x\n", (void *)addr, offset, value, mask));
return (value);
}
uint16_t
pmubus_noget16(ddi_acc_impl_t *hdlp, uint16_t *addr)
{
return ((uint16_t)-1);
}
uint32_t
pmubus_get32(ddi_acc_impl_t *hdlp, uint32_t *addr)
{
ddi_acc_hdl_t *hp = (ddi_acc_hdl_t *)hdlp;
pmubus_mapreq_t *pmubus_mapreqp = hp->ah_bus_private;
pmubus_devstate_t *softsp = pmubus_mapreqp->mapreq_softsp;
off_t offset = (uintptr_t)addr & PMUBUS_REGOFFSET;
uint32_t value;
uint32_t mask;
offset = pmubus_mapreqp->mapreq_addr + (uintptr_t)addr;
offset &= PMUBUS_REGOFFSET;
if ((pmubus_mapreqp->mapreq_flags) & MAPREQ_SHARED_BITS) {
if (addr != 0 ||
pmubus_mapreqp->mapreq_size != sizeof (value)) {
cmn_err(CE_WARN, "pmubus_get32: load discarded, "
"incorrect access addr/size");
return ((uint32_t)-1);
}
mask = pmubus_mapreqp->mapreq_mask;
} else {
mask = (uint32_t)-1;
}
value = pci_config_get32(softsp->pmubus_reghdl, offset) & mask;
DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_get32: addr=%p offset=%lx value=%x "
"mask=%x\n", (void *)addr, offset, value, mask));
return (value);
}
uint64_t
pmubus_noget64(ddi_acc_impl_t *hdlp, uint64_t *addr)
{
return ((uint64_t)-1);
}
void
pmubus_put8(ddi_acc_impl_t *hdlp, uint8_t *addr, uint8_t value)
{
ddi_acc_hdl_t *hp = (ddi_acc_hdl_t *)hdlp;
pmubus_mapreq_t *pmubus_mapreqp = hp->ah_bus_private;
pmubus_devstate_t *softsp = pmubus_mapreqp->mapreq_softsp;
off_t offset;
uint8_t tmp;
offset = pmubus_mapreqp->mapreq_addr + (uintptr_t)addr;
offset &= PMUBUS_REGOFFSET;
if ((pmubus_mapreqp->mapreq_flags) & MAPREQ_SHARED_BITS) {
DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_put8: addr=%p offset=%lx "
"value=%x mask=%lx\n", (void *)addr, offset, value,
pmubus_mapreqp->mapreq_mask));
if (addr != 0 ||
pmubus_mapreqp->mapreq_size != sizeof (value)) {
cmn_err(CE_WARN, "pmubus_put8: store discarded, "
"incorrect access addr/size");
return;
}
mutex_enter(&softsp->pmubus_reg_access_lock);
tmp = pci_config_get8(softsp->pmubus_reghdl, offset);
tmp &= ~pmubus_mapreqp->mapreq_mask;
value &= pmubus_mapreqp->mapreq_mask;
tmp |= value;
pci_config_put8(softsp->pmubus_reghdl, offset, tmp);
mutex_exit(&softsp->pmubus_reg_access_lock);
} else {
DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_put8: addr=%p offset=%lx "
"value=%x\n", (void *)addr, offset, value));
pci_config_put8(softsp->pmubus_reghdl, offset, value);
}
tmp = pci_config_get8(softsp->pmubus_reghdl, offset);
}
void
pmubus_noput16(ddi_acc_impl_t *hdlp, uint16_t *addr, uint16_t value)
{
}
void
pmubus_put32(ddi_acc_impl_t *hdlp, uint32_t *addr, uint32_t value)
{
ddi_acc_hdl_t *hp = (ddi_acc_hdl_t *)hdlp;
pmubus_mapreq_t *pmubus_mapreqp = hp->ah_bus_private;
pmubus_devstate_t *softsp = pmubus_mapreqp->mapreq_softsp;
off_t offset;
uint32_t tmp;
offset = pmubus_mapreqp->mapreq_addr + (uintptr_t)addr;
offset &= PMUBUS_REGOFFSET;
if ((pmubus_mapreqp->mapreq_flags) & MAPREQ_SHARED_BITS) {
DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_put32: addr=%p offset=%lx "
"value=%x mask=%lx\n", (void *)addr, offset, value,
pmubus_mapreqp->mapreq_mask));
if (addr != 0 ||
pmubus_mapreqp->mapreq_size != sizeof (value)) {
cmn_err(CE_WARN, "pmubus_put32: store discarded, "
"incorrect access addr/size");
return;
}
mutex_enter(&softsp->pmubus_reg_access_lock);
tmp = pci_config_get32(softsp->pmubus_reghdl, offset);
tmp &= ~pmubus_mapreqp->mapreq_mask;
value &= pmubus_mapreqp->mapreq_mask;
tmp |= value;
pci_config_put32(softsp->pmubus_reghdl, offset, tmp);
mutex_exit(&softsp->pmubus_reg_access_lock);
} else {
DPRINTF(PMUBUS_RW_DEBUG, ("pmubus_put32: addr=%p offset=%lx "
"value=%x\n", (void *)addr, offset, value));
pci_config_put32(softsp->pmubus_reghdl, offset, value);
}
tmp = pci_config_get32(softsp->pmubus_reghdl, offset);
}
void
pmubus_noput64(ddi_acc_impl_t *hdlp, uint64_t *addr, uint64_t value)
{
}
int
pmubus_apply_range(pmubus_devstate_t *pmubusp, dev_info_t *rdip,
pmubus_regspec_t *regp, pci_regspec_t *pci_regp)
{
pmu_rangespec_t *rangep;
int nranges = pmubusp->pmubus_nranges;
int i;
off_t offset;
int ret = DDI_ME_REGSPEC_RANGE;
uint64_t addr;
addr = regp->reg_addr & ~MAPPING_SHARED_BITS_MASK;
for (i = 0, rangep = pmubusp->pmubus_rangep; i < nranges; i++, rangep++)
if ((rangep->rng_child <= addr) &&
((addr + regp->reg_size) <=
(rangep->rng_child + rangep->rng_size))) {
ret = DDI_SUCCESS;
break;
}
if (ret != DDI_SUCCESS)
return (ret);
offset = addr - rangep->rng_child;
pci_regp->pci_phys_hi = rangep->rng_parent_hi;
pci_regp->pci_phys_mid = rangep->rng_parent_mid;
pci_regp->pci_phys_low = rangep->rng_parent_low + offset;
pci_regp->pci_size_hi = 0;
pci_regp->pci_size_low = MIN(regp->reg_size, rangep->rng_size);
if (pci_regp->pci_phys_hi == pmubusp->pmubus_regp->pci_phys_hi) {
ret = MAPREQ_SHARED_REG;
if (regp->reg_addr & MAPPING_SHARED_BITS_MASK)
ret |= MAPREQ_SHARED_BITS;
}
return (ret);
}
static uint64_t
pmubus_mask(pmubus_obpregspec_t *regs, int32_t rnumber,
uint64_t *masks)
{
int i;
long n = -1;
for (i = 0; i <= rnumber; i++)
if (regs[i].reg_addr_hi & 0x80000000)
n++;
if (n == -1) {
cmn_err(CE_WARN, "pmubus_mask: missing mask");
return (0);
}
return (masks[n]);
}
static int
pmubus_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t off, off_t len, caddr_t *addrp)
{
pmubus_devstate_t *pmubusp = ddi_get_soft_state(per_pmubus_state,
ddi_get_instance(dip));
dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
pmubus_regspec_t pmubus_rp;
pmubus_obpregspec_t *pmubus_regs = NULL;
int pmubus_regs_size;
uint64_t *pmubus_regmask = NULL;
int pmubus_regmask_size;
pci_regspec_t pci_reg;
int32_t rnumber = mp->map_obj.rnumber;
pmubus_mapreq_t *pmubus_mapreqp;
int ret = DDI_SUCCESS;
char *map_fail1 = "Map Type Unknown";
char *map_fail2 = "DDI_MT_REGSPEC";
char *s = map_fail1;
*addrp = NULL;
DPRINTF(PMUBUS_MAP_DEBUG, ("rdip=%s%d: off=%lx len=%lx\n",
ddi_get_name(rdip), ddi_get_instance(rdip), off, len));
switch (mp->map_type) {
case DDI_MT_RNUMBER: {
int n;
rnumber = mp->map_obj.rnumber;
DPRINTF(PMUBUS_MAP_DEBUG, ("rdip=%s%d: rnumber=%x "
"handlep=%p\n", ddi_get_name(rdip), ddi_get_instance(rdip),
rnumber, (void *)mp->map_handlep));
if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS,
"reg", (caddr_t)&pmubus_regs, &pmubus_regs_size) !=
DDI_SUCCESS) {
DPRINTF(PMUBUS_MAP_DEBUG, ("can't get reg "
"property\n"));
ret = DDI_ME_RNUMBER_RANGE;
goto done;
}
n = pmubus_regs_size / sizeof (pmubus_obpregspec_t);
if (rnumber < 0 || rnumber >= n) {
DPRINTF(PMUBUS_MAP_DEBUG, ("rnumber out of range\n"));
ret = DDI_ME_RNUMBER_RANGE;
goto done;
}
pmubus_rp.reg_addr = ((uint64_t)
pmubus_regs[rnumber].reg_addr_hi << 32) |
(uint64_t)pmubus_regs[rnumber].reg_addr_lo;
pmubus_rp.reg_size = pmubus_regs[rnumber].reg_size;
(void) ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS,
"register-mask", (caddr_t)&pmubus_regmask,
&pmubus_regmask_size);
break;
}
case DDI_MT_REGSPEC:
s = map_fail2;
ret = DDI_ME_REGSPEC_RANGE;
default:
if (ret == DDI_SUCCESS)
ret = DDI_ME_INVAL;
DPRINTF(PMUBUS_MAP_DEBUG, ("rdip=%s%d: pmubus_map: "
"%s is an invalid map type.\nmap request handlep=0x%p\n",
ddi_get_name(rdip), ddi_get_instance(rdip), s, (void *)mp));
ret = DDI_ME_RNUMBER_RANGE;
goto done;
}
if ((pmubus_rp.reg_addr + off) >
(pmubus_rp.reg_addr + pmubus_rp.reg_size)) {
ret = DDI_ME_INVAL;
goto done;
}
pmubus_rp.reg_addr += off;
if (len && (len < pmubus_rp.reg_size))
pmubus_rp.reg_size = len;
ret = pmubus_apply_range(pmubusp, rdip, &pmubus_rp, &pci_reg);
if (ret < DDI_SUCCESS)
goto done;
if (ret > DDI_SUCCESS) {
switch (mp->map_op) {
case DDI_MO_MAP_LOCKED: {
ddi_acc_impl_t *hp = (ddi_acc_impl_t *)mp->map_handlep;
pmubus_mapreqp = kmem_alloc(sizeof (*pmubus_mapreqp),
KM_SLEEP);
pmubus_mapreqp->mapreq_flags = ret;
pmubus_mapreqp->mapreq_softsp = pmubusp;
pmubus_mapreqp->mapreq_addr = pmubus_rp.reg_addr;
pmubus_mapreqp->mapreq_size = pmubus_rp.reg_size;
if (ret & MAPREQ_SHARED_BITS) {
pmubus_mapreqp->mapreq_mask =
pmubus_mask(pmubus_regs, rnumber,
pmubus_regmask);
DPRINTF(PMUBUS_MAP_DEBUG, ("rnumber=%d "
"mask=%lx\n", rnumber,
pmubus_mapreqp->mapreq_mask));
if (pmubus_mapreqp->mapreq_mask == 0) {
kmem_free(pmubus_mapreqp,
sizeof (pmubus_mapreq_t));
ret = DDI_ME_INVAL;
break;
}
}
hp->ahi_common.ah_bus_private = pmubus_mapreqp;
hp->ahi_get8 = pmubus_get8;
hp->ahi_get16 = pmubus_noget16;
hp->ahi_get32 = pmubus_get32;
hp->ahi_get64 = pmubus_noget64;
hp->ahi_put8 = pmubus_put8;
hp->ahi_put16 = pmubus_noput16;
hp->ahi_put32 = pmubus_put32;
hp->ahi_put64 = pmubus_noput64;
hp->ahi_rep_get8 = pmubus_norep_get8;
hp->ahi_rep_get16 = pmubus_norep_get16;
hp->ahi_rep_get32 = pmubus_norep_get32;
hp->ahi_rep_get64 = pmubus_norep_get64;
hp->ahi_rep_put8 = pmubus_norep_put8;
hp->ahi_rep_put16 = pmubus_norep_put16;
hp->ahi_rep_put32 = pmubus_norep_put32;
hp->ahi_rep_put64 = pmubus_norep_put64;
ret = DDI_SUCCESS;
break;
}
case DDI_MO_UNMAP: {
ddi_acc_impl_t *hp = (ddi_acc_impl_t *)mp->map_handlep;
pmubus_mapreqp = hp->ahi_common.ah_bus_private;
kmem_free(pmubus_mapreqp, sizeof (pmubus_mapreq_t));
ret = DDI_SUCCESS;
break;
}
default:
ret = DDI_ME_UNSUPPORTED;
}
} else {
mp->map_type = DDI_MT_REGSPEC;
mp->map_obj.rp = (struct regspec *)&pci_reg;
ret = (DEVI(pdip)->devi_ops->devo_bus_ops->bus_map)
(pdip, rdip, mp, off, len, addrp);
}
done:
if (pmubus_regs != NULL)
kmem_free(pmubus_regs, pmubus_regs_size);
if (pmubus_regmask != NULL)
kmem_free(pmubus_regmask, pmubus_regmask_size);
return (ret);
}
static int
pmubus_ctlops(dev_info_t *dip, dev_info_t *rdip,
ddi_ctl_enum_t op, void *arg, void *result)
{
dev_info_t *child = (dev_info_t *)arg;
pmubus_obpregspec_t *pmubus_rp;
char name[9];
int reglen;
switch (op) {
case DDI_CTLOPS_INITCHILD:
if (ddi_getlongprop(DDI_DEV_T_ANY, child,
DDI_PROP_DONTPASS, "reg", (caddr_t)&pmubus_rp,
®len) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
if ((reglen % sizeof (pmubus_obpregspec_t)) != 0) {
cmn_err(CE_WARN,
"pmubus: reg property not well-formed for "
"%s size=%d\n", ddi_node_name(child), reglen);
kmem_free(pmubus_rp, reglen);
return (DDI_FAILURE);
}
(void) snprintf(name, sizeof (name), "%x,%x",
pmubus_rp->reg_addr_hi, pmubus_rp->reg_addr_lo);
ddi_set_name_addr(child, name);
kmem_free(pmubus_rp, reglen);
return (DDI_SUCCESS);
case DDI_CTLOPS_UNINITCHILD:
ddi_set_name_addr(child, NULL);
ddi_remove_minor_node(child, NULL);
impl_rem_dev_props(child);
return (DDI_SUCCESS);
default:
break;
}
return (ddi_ctlops(dip, rdip, op, arg, result));
}