#include <sys/conf.h>
#include <sys/sunddi.h>
#include <sys/ddi_impldefs.h>
#include <sys/kmem.h>
#include <sys/dma_i8237A.h>
#include <sys/isadma.h>
#include <sys/nexusdebug.h>
#define ISADMA_MAP_DEBUG 0x1
#define ISADMA_REGACCESS_DEBUG 0x2
static int isadma_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 isadma_attach(dev_info_t *dip, ddi_attach_cmd_t cmd);
static int isadma_detach(dev_info_t *dip, ddi_detach_cmd_t cmd);
static struct bus_ops isadma_bus_ops = {
BUSO_REV,
isadma_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,
ddi_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 isadma_ops = {
DEVO_REV,
0,
ddi_no_info,
nulldev,
0,
isadma_attach,
isadma_detach,
nodev,
(struct cb_ops *)0,
&isadma_bus_ops,
NULL,
ddi_quiesce_not_needed,
};
#include <sys/modctl.h>
static struct modldrv modldrv = {
&mod_driverops,
"isadma nexus driver",
&isadma_ops,
};
static struct modlinkage modlinkage = {
MODREV_1, (void *)&modldrv, NULL
};
static void *per_isadma_state;
uint64_t isadma_sleep_cnt = 0;
uint64_t isadma_wakeup_cnt = 0;
#ifdef DEBUG
int64_t isadma_max_waiter = 0;
int64_t isadma_min_waiter = 0xffffll;
uint64_t isadma_punt = 0;
uint64_t isadma_setting_wdip = 0;
uint64_t isadma_clearing_wdip = 0;
#endif
int
_init(void)
{
int e;
e = ddi_soft_state_init(&per_isadma_state,
sizeof (isadma_devstate_t), 1);
if (e != 0)
return (e);
e = mod_install(&modlinkage);
if (e != 0)
ddi_soft_state_fini(&per_isadma_state);
return (e);
}
int
_fini(void)
{
int e;
e = mod_remove(&modlinkage);
if (e != 0)
return (e);
ddi_soft_state_fini(&per_isadma_state);
return (e);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
static int
isadma_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
isadma_devstate_t *isadmap;
int32_t instance;
int ret = DDI_SUCCESS;
#ifdef DEBUG
debug_print_level = 0;
debug_info = 1;
#endif
switch (cmd) {
case DDI_ATTACH: {
instance = ddi_get_instance(dip);
if (ddi_soft_state_zalloc(per_isadma_state, instance)
!= DDI_SUCCESS) {
ret = DDI_FAILURE;
goto exit;
}
isadmap = ddi_get_soft_state(per_isadma_state, instance);
isadmap->isadma_dip = dip;
if (ddi_getlongprop(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"reg", (caddr_t)&isadmap->isadma_regp,
&isadmap->isadma_reglen) != DDI_SUCCESS) {
ret = DDI_FAILURE;
goto fail_get_prop;
}
mutex_init(&isadmap->isadma_access_lock, NULL, MUTEX_DRIVER,
NULL);
cv_init(&isadmap->isadma_access_cv, NULL, CV_DRIVER, NULL);
ddi_report_dev(dip);
goto exit;
}
case DDI_RESUME:
default:
goto exit;
}
fail_get_prop:
ddi_soft_state_free(per_isadma_state, instance);
exit:
return (ret);
}
static int
isadma_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
int instance = ddi_get_instance(dip);
isadma_devstate_t *isadmap =
ddi_get_soft_state(per_isadma_state, instance);
switch (cmd) {
case DDI_DETACH:
cv_destroy(&isadmap->isadma_access_cv);
mutex_destroy(&isadmap->isadma_access_lock);
kmem_free(isadmap->isadma_regp, isadmap->isadma_reglen);
ddi_soft_state_free(per_isadma_state, instance);
return (DDI_SUCCESS);
case DDI_SUSPEND:
return (DDI_SUCCESS);
}
return (DDI_FAILURE);
}
#ifdef DEBUG
static void
isadma_check_waiters(isadma_devstate_t *isadmap)
{
if (isadmap->isadma_want > isadma_max_waiter)
isadma_max_waiter = isadmap->isadma_want;
if (isadmap->isadma_want < isadma_min_waiter)
isadma_min_waiter = isadmap->isadma_want;
}
#endif
static void
isadma_dmawait(isadma_devstate_t *isadmap)
{
ASSERT(mutex_owned(&isadmap->isadma_access_lock));
while (isadmap->isadma_ldip != NULL) {
isadmap->isadma_want++;
cv_wait(&isadmap->isadma_access_cv,
&isadmap->isadma_access_lock);
isadmap->isadma_want--;
isadma_sleep_cnt++;
}
}
static void
isadma_wakeup(isadma_devstate_t *isadmap)
{
ASSERT(mutex_owned(&isadmap->isadma_access_lock));
if (isadmap->isadma_want > 0 && isadmap->isadma_ldip == NULL) {
cv_signal(&isadmap->isadma_access_cv);
isadma_wakeup_cnt++;
}
}
void
isadma_norep_get8(ddi_acc_impl_t *handle, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
isadma_norep_get16(ddi_acc_impl_t *handle, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
isadma_norep_get32(ddi_acc_impl_t *handle, uint32_t *host_addr,
uint32_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
isadma_norep_get64(ddi_acc_impl_t *handle, uint64_t *host_addr,
uint64_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
isadma_norep_put8(ddi_acc_impl_t *handle, uint8_t *host_addr,
uint8_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
isadma_norep_put16(ddi_acc_impl_t *handle, uint16_t *host_addr,
uint16_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
isadma_norep_put32(ddi_acc_impl_t *handle, uint32_t *host_addr,
uint32_t *dev_addr, size_t repcount, uint_t flags)
{
}
void
isadma_norep_put64(ddi_acc_impl_t *handle, uint64_t *host_addr,
uint64_t *dev_addr, size_t repcount, uint_t flags)
{
}
uint8_t
isadma_get8(ddi_acc_impl_t *hdlp, uint8_t *addr)
{
ddi_acc_handle_t phdl = hdlp->ahi_common.ah_platform_private;
isadma_devstate_t *isadmap = hdlp->ahi_common.ah_bus_private;
off_t offset = (caddr_t)addr - hdlp->ahi_common.ah_addr;
uint8_t ret = 0xff;
if (IN_CHILD_SPACE(offset)) {
#ifdef DEBUG
isadma_punt++;
#endif
return (ddi_get8(phdl, addr));
}
#ifdef DEBUG
isadma_check_waiters(isadmap);
#endif
mutex_enter(&isadmap->isadma_access_lock);
isadma_dmawait(isadmap);
if (IN_16BIT_SPACE(offset))
goto exit;
if (IS_SEQREG(offset))
goto exit;
ret = ddi_get8(phdl, addr);
exit:
isadma_wakeup(isadmap);
mutex_exit(&isadmap->isadma_access_lock);
return (ret);
}
uint16_t
isadma_get16(ddi_acc_impl_t *hdlp, uint16_t *addr)
{
ddi_acc_handle_t phdl = hdlp->ahi_common.ah_platform_private;
isadma_devstate_t *isadmap = hdlp->ahi_common.ah_bus_private;
off_t offset = (caddr_t)addr - hdlp->ahi_common.ah_addr;
uint16_t ret = 0xffff;
if (IN_CHILD_SPACE(offset)) {
#ifdef DEBUG
isadma_punt++;
#endif
return (ddi_get16(phdl, addr));
}
#ifdef DEBUG
isadma_check_waiters(isadmap);
#endif
mutex_enter(&isadmap->isadma_access_lock);
isadma_dmawait(isadmap);
if (!IN_16BIT_SPACE(offset))
goto exit;
ddi_put8(phdl, (uint8_t *)HDL_TO_SEQREG_ADDR(hdlp, offset), 0);
ret = ddi_get8(phdl, (uint8_t *)addr);
ret = (ddi_get8(phdl, (uint8_t *)addr) << 8) | ret;
exit:
isadma_wakeup(isadmap);
mutex_exit(&isadmap->isadma_access_lock);
return (ret);
}
uint32_t
isadma_noget32(ddi_acc_impl_t *hdlp, uint32_t *addr)
{
return (UINT32_MAX);
}
uint64_t
isadma_noget64(ddi_acc_impl_t *hdlp, uint64_t *addr)
{
return (UINT64_MAX);
}
void
isadma_put8(ddi_acc_impl_t *hdlp, uint8_t *addr, uint8_t value)
{
ddi_acc_handle_t phdl = hdlp->ahi_common.ah_platform_private;
isadma_devstate_t *isadmap = hdlp->ahi_common.ah_bus_private;
off_t offset = (caddr_t)addr - hdlp->ahi_common.ah_addr;
if (IN_CHILD_SPACE(offset)) {
#ifdef DEBUG
isadma_punt++;
#endif
ddi_put8(phdl, addr, value);
return;
}
#ifdef DEBUG
isadma_check_waiters(isadmap);
#endif
mutex_enter(&isadmap->isadma_access_lock);
if (isadmap->isadma_ldip == hdlp->ahi_common.ah_dip) {
if (END_ISADMA(offset, value)) {
isadmap->isadma_ldip = NULL;
#ifdef DEBUG
isadma_clearing_wdip++;
#endif
}
} else {
isadma_dmawait(isadmap);
if (BEGIN_ISADMA(offset, value)) {
isadmap->isadma_ldip = hdlp->ahi_common.ah_dip;
#ifdef DEBUG
isadma_setting_wdip++;
#endif
}
}
if (IN_16BIT_SPACE(offset))
goto exit;
if (IS_SEQREG(offset))
goto exit;
ddi_put8(phdl, addr, value);
exit:
isadma_wakeup(isadmap);
mutex_exit(&isadmap->isadma_access_lock);
}
void
isadma_put16(ddi_acc_impl_t *hdlp, uint16_t *addr, uint16_t value)
{
ddi_acc_handle_t phdl = hdlp->ahi_common.ah_platform_private;
isadma_devstate_t *isadmap = hdlp->ahi_common.ah_bus_private;
off_t offset = (caddr_t)addr - hdlp->ahi_common.ah_addr;
if (IN_CHILD_SPACE(offset)) {
#ifdef DEBUG
isadma_punt++;
#endif
ddi_put16(phdl, addr, value);
return;
}
#ifdef DEBUG
isadma_check_waiters(isadmap);
#endif
mutex_enter(&isadmap->isadma_access_lock);
isadma_dmawait(isadmap);
if (!IN_16BIT_SPACE(offset))
goto exit;
ddi_put8(phdl, (uint8_t *)HDL_TO_SEQREG_ADDR(hdlp, offset), 0);
ddi_put8(phdl, (uint8_t *)addr, value & 0xff);
ddi_put8(phdl, (uint8_t *)addr, (value >> 8) & 0xff);
exit:
isadma_wakeup(isadmap);
mutex_exit(&isadmap->isadma_access_lock);
}
void
isadma_noput32(ddi_acc_impl_t *hdlp, uint32_t *addr, uint32_t value) {}
void
isadma_noput64(ddi_acc_impl_t *hdlp, uint64_t *addr, uint64_t value) {}
#define IS_SAME_REG(r1, r2) (((r1)->ebus_addr_hi == (r2)->ebus_addr_hi) && \
((r1)->ebus_addr_low == (r2)->ebus_addr_low))
static int
isadma_map(dev_info_t *dip, dev_info_t *rdip, ddi_map_req_t *mp,
off_t off, off_t len, caddr_t *addrp)
{
isadma_devstate_t *isadmap = ddi_get_soft_state(per_isadma_state,
ddi_get_instance(dip));
dev_info_t *pdip = (dev_info_t *)DEVI(dip)->devi_parent;
ebus_regspec_t *child_regp, *regp;
int32_t rnumber = mp->map_obj.rnumber;
int32_t reglen;
int ret;
ddi_acc_impl_t *hp;
if (ddi_getlongprop(DDI_DEV_T_ANY, rdip, DDI_PROP_DONTPASS,
"reg", (caddr_t)®p, ®len) != DDI_SUCCESS) {
return (DDI_FAILURE);
}
child_regp = regp + rnumber;
DPRINTF(ISADMA_MAP_DEBUG, ("isadma_map: child regp %p "
"parent regp %p Child reg array %p\n", (void *)child_regp,
(void *)isadmap->isadma_regp, (void *)regp));
switch (mp->map_op) {
case DDI_MO_MAP_LOCKED:
ret = (DEVI(pdip)->devi_ops->devo_bus_ops->bus_map)
(pdip, rdip, mp, off, len, addrp);
if ((ret != DDI_SUCCESS) ||
!IS_SAME_REG(child_regp, isadmap->isadma_regp))
break;
hp = kmem_alloc(sizeof (ddi_acc_impl_t), KM_SLEEP);
*hp = *(ddi_acc_impl_t *)mp->map_handlep;
impl_acc_hdl_get((ddi_acc_handle_t)mp->map_handlep)->
ah_platform_private = hp;
hp = (ddi_acc_impl_t *)mp->map_handlep;
hp->ahi_common.ah_bus_private = isadmap;
hp->ahi_get8 = isadma_get8;
hp->ahi_get16 = isadma_get16;
hp->ahi_get32 = isadma_noget32;
hp->ahi_get64 = isadma_noget64;
hp->ahi_put8 = isadma_put8;
hp->ahi_put16 = isadma_put16;
hp->ahi_put32 = isadma_noput32;
hp->ahi_put64 = isadma_noput64;
hp->ahi_rep_get8 = isadma_norep_get8;
hp->ahi_rep_get16 = isadma_norep_get16;
hp->ahi_rep_get32 = isadma_norep_get32;
hp->ahi_rep_get64 = isadma_norep_get64;
hp->ahi_rep_put8 = isadma_norep_put8;
hp->ahi_rep_put16 = isadma_norep_put16;
hp->ahi_rep_put32 = isadma_norep_put32;
hp->ahi_rep_put64 = isadma_norep_put64;
break;
case DDI_MO_UNMAP:
if (IS_SAME_REG(child_regp, isadmap->isadma_regp)) {
hp = impl_acc_hdl_get(
(ddi_acc_handle_t)mp->map_handlep)->
ah_platform_private;
*(ddi_acc_impl_t *)mp->map_handlep = *hp;
kmem_free(hp, sizeof (ddi_acc_impl_t));
}
ret = (DEVI(pdip)->devi_ops->devo_bus_ops->bus_map)
(pdip, rdip, mp, off, len, addrp);
break;
default:
ret = DDI_FAILURE;
break;
}
kmem_free(regp, reglen);
return (ret);
}