#include <sys/conf.h>
#include <sys/membar.h>
#include <sys/modctl.h>
#include <sys/strlog.h>
#include <sys/types.h>
#include <sys/sunddi.h>
#include <sys/ddi.h>
#include <sys/rmc_comm_dp_boot.h>
#include <sys/rmc_comm_dp.h>
#include <sys/rmc_comm_drvintf.h>
#include <sys/rmc_comm.h>
#include <sys/cpu_sgnblk_defs.h>
#define MYNAME "rmc_comm"
#define NOMAJOR (~(major_t)0)
#define DUMMY_VALUE (~(int8_t)0)
static void *rmc_comm_statep;
static major_t rmc_comm_major = NOMAJOR;
static kmutex_t rmc_comm_attach_lock;
static ddi_device_acc_attr_t rmc_comm_dev_acc_attr[1] =
{
DDI_DEVICE_ATTR_V0,
DDI_STRUCTURE_LE_ACC,
DDI_STRICTORDER_ACC
};
static int watchdog_was_active;
extern int watchdog_activated;
extern int watchdog_enable;
extern void dp_reset(struct rmc_comm_state *, uint8_t, boolean_t, boolean_t);
static void sio_put_reg(struct rmc_comm_state *, uint_t, uint8_t);
static uint8_t sio_get_reg(struct rmc_comm_state *, uint_t);
static void sio_check_fault_status(struct rmc_comm_state *);
static boolean_t sio_data_ready(struct rmc_comm_state *);
static void rmc_comm_set_irq(struct rmc_comm_state *, boolean_t);
static uint_t rmc_comm_hi_intr(caddr_t);
static uint_t rmc_comm_softint(caddr_t);
static void rmc_comm_cyclic(void *);
static void rmc_comm_hw_reset(struct rmc_comm_state *);
static void rmc_comm_offline(struct rmc_comm_state *);
static int rmc_comm_online(struct rmc_comm_state *, dev_info_t *);
static void rmc_comm_unattach(struct rmc_comm_state *, dev_info_t *, int,
boolean_t, boolean_t, boolean_t);
static int rmc_comm_attach(dev_info_t *, ddi_attach_cmd_t);
static int rmc_comm_detach(dev_info_t *, ddi_detach_cmd_t);
int
rmc_comm_register()
{
struct rmc_comm_state *rcs;
mutex_enter(&rmc_comm_attach_lock);
rcs = ddi_get_soft_state(rmc_comm_statep, 0);
if ((rcs == NULL) || (!rcs->is_attached)) {
mutex_exit(&rmc_comm_attach_lock);
return (DDI_FAILURE);
}
rcs->n_registrations++;
mutex_exit(&rmc_comm_attach_lock);
return (DDI_SUCCESS);
}
void
rmc_comm_unregister()
{
struct rmc_comm_state *rcs;
mutex_enter(&rmc_comm_attach_lock);
rcs = ddi_get_soft_state(rmc_comm_statep, 0);
ASSERT(rcs != NULL);
ASSERT(rcs->n_registrations != 0);
rcs->n_registrations--;
mutex_exit(&rmc_comm_attach_lock);
}
struct rmc_comm_state *
rmc_comm_getstate(dev_info_t *dip, int instance, const char *caller)
{
struct rmc_comm_state *rcs = NULL;
dev_info_t *sdip = NULL;
major_t dmaj = NOMAJOR;
if (dip != NULL) {
instance = ddi_get_instance(dip);
dmaj = ddi_driver_major(dip);
if (rmc_comm_major == NOMAJOR && dmaj != NOMAJOR)
rmc_comm_major = dmaj;
else if (dmaj != rmc_comm_major) {
cmn_err(CE_WARN,
"%s: major number mismatch (%d vs. %d) in %s(),"
"probably due to child misconfiguration",
MYNAME, rmc_comm_major, dmaj, caller);
instance = -1;
}
}
if (instance >= 0)
rcs = ddi_get_soft_state(rmc_comm_statep, instance);
if (rcs != NULL) {
sdip = rcs->dip;
if (dip == NULL && sdip == NULL)
rcs = NULL;
else if (dip != NULL && sdip != NULL && sdip != dip) {
cmn_err(CE_WARN,
"%s: devinfo mismatch (%p vs. %p) in %s(), "
"probably due to child misconfiguration", MYNAME,
(void *)dip, (void *)sdip, caller);
rcs = NULL;
}
}
return (rcs);
}
static void
sio_put_reg(struct rmc_comm_state *rcs, uint_t reg, uint8_t val)
{
DPRINTF(rcs, DSER, (CE_CONT, "REG[%d]<-$%02x", reg, val));
if (rcs->sd_state.sio_handle != NULL && !rcs->sd_state.sio_fault) {
ddi_put8(rcs->sd_state.sio_handle,
rcs->sd_state.sio_regs + reg, val);
ddi_put8(rcs->sd_state.sio_handle,
rcs->sd_state.sio_regs + SIO_SCR, val);
membar_sync();
(void) ddi_get8(rcs->sd_state.sio_handle,
rcs->sd_state.sio_regs + SIO_SCR);
}
}
static uint8_t
sio_get_reg(struct rmc_comm_state *rcs, uint_t reg)
{
uint8_t val;
if (rcs->sd_state.sio_handle && !rcs->sd_state.sio_fault)
val = ddi_get8(rcs->sd_state.sio_handle,
rcs->sd_state.sio_regs + reg);
else
val = DUMMY_VALUE;
DPRINTF(rcs, DSER, (CE_CONT, "$%02x<-REG[%d]", val, reg));
return (val);
}
static void
sio_check_fault_status(struct rmc_comm_state *rcs)
{
rcs->sd_state.sio_fault =
ddi_check_acc_handle(rcs->sd_state.sio_handle) != DDI_SUCCESS;
}
boolean_t
rmc_comm_faulty(struct rmc_comm_state *rcs)
{
if (!rcs->sd_state.sio_fault)
sio_check_fault_status(rcs);
return (rcs->sd_state.sio_fault);
}
static boolean_t
sio_data_ready(struct rmc_comm_state *rcs)
{
uint8_t status;
status = sio_get_reg(rcs, SIO_LSR);
return ((status & SIO_LSR_RXDA) != 0 && !rmc_comm_faulty(rcs));
}
static void
rmc_comm_set_irq(struct rmc_comm_state *rcs, boolean_t newstate)
{
uint8_t val;
val = newstate ? SIO_IER_RXHDL_IE : 0;
sio_put_reg(rcs, SIO_IER, SIO_IER_STD | val);
rcs->sd_state.hw_int_enabled = newstate;
}
static uint_t
rmc_comm_hi_intr(caddr_t arg)
{
struct rmc_comm_state *rcs = (void *)arg;
uint_t claim;
claim = DDI_INTR_UNCLAIMED;
if (rcs->sd_state.cycid != NULL) {
if (ddi_in_panic() != 0) {
if (mutex_tryenter(rcs->sd_state.hw_mutex) == 0) {
return (claim);
}
} else {
mutex_enter(rcs->sd_state.hw_mutex);
}
if (rcs->sd_state.hw_int_enabled) {
rmc_comm_set_irq(rcs, B_FALSE);
ddi_trigger_softintr(rcs->sd_state.softid);
claim = DDI_INTR_CLAIMED;
}
mutex_exit(rcs->sd_state.hw_mutex);
}
return (claim);
}
void
rmc_comm_serdev_receive(struct rmc_comm_state *rcs)
{
uint8_t data;
DPRINTF(rcs, DSER, (CE_CONT, "serdev_receive: soft int handler\n"));
if (!rmc_comm_faulty(rcs)) {
char *rx_buf = rcs->sd_state.serdev_rx_buf;
uint16_t rx_buflen = 0;
mutex_enter(rcs->sd_state.hw_mutex);
while (sio_data_ready(rcs)) {
data = sio_get_reg(rcs, SIO_RXD);
rx_buf[rx_buflen++] = data;
if (rx_buflen >= SIO_MAX_RXBUF_SIZE)
break;
}
rcs->sd_state.serdev_rx_count = rx_buflen;
DATASCOPE(rcs, 'R', rx_buf, rx_buflen)
rmc_comm_set_irq(rcs, B_TRUE);
mutex_exit(rcs->sd_state.hw_mutex);
rmc_comm_dp_drecv(rcs, (uint8_t *)rx_buf, rx_buflen);
}
}
static uint_t
rmc_comm_softint(caddr_t arg)
{
struct rmc_comm_state *rcs = (void *)arg;
mutex_enter(rcs->dp_state.dp_mutex);
rmc_comm_serdev_receive(rcs);
mutex_exit(rcs->dp_state.dp_mutex);
return (DDI_INTR_CLAIMED);
}
static void
rmc_comm_cyclic(void *arg)
{
struct rmc_comm_state *rcs = (void *)arg;
mutex_enter(rcs->dp_state.dp_mutex);
rmc_comm_serdev_receive(rcs);
mutex_exit(rcs->dp_state.dp_mutex);
}
void
rmc_comm_serdev_send(struct rmc_comm_state *rcs, char *buf, int buflen)
{
uint8_t *p;
uint8_t status;
sio_check_fault_status(rcs);
DATASCOPE(rcs, 'S', buf, buflen)
mutex_enter(rcs->sd_state.hw_mutex);
p = (uint8_t *)buf;
while (p < (uint8_t *)&buf[buflen]) {
status = sio_get_reg(rcs, SIO_LSR);
while ((status & SIO_LSR_XHRE) == 0) {
drv_usecwait(100);
status = sio_get_reg(rcs, SIO_LSR);
}
sio_put_reg(rcs, SIO_TXD, *p++);
}
mutex_exit(rcs->sd_state.hw_mutex);
}
void
rmc_comm_serdev_drain(struct rmc_comm_state *rcs)
{
uint8_t status;
mutex_enter(rcs->sd_state.hw_mutex);
status = sio_get_reg(rcs, SIO_LSR);
while ((status & SIO_LSR_XHRE) == 0) {
drv_usecwait(100);
status = sio_get_reg(rcs, SIO_LSR);
}
mutex_exit(rcs->sd_state.hw_mutex);
}
static void
rmc_comm_hw_reset(struct rmc_comm_state *rcs)
{
uint16_t divisor;
rmc_comm_set_irq(rcs, B_FALSE);
sio_put_reg(rcs, SIO_FCR, SIO_FCR_RXSR | SIO_FCR_TXSR);
sio_put_reg(rcs, SIO_LCR, SIO_LCR_STD);
if (rcs->baud < SIO_BAUD_MIN || rcs->baud > SIO_BAUD_MAX) {
divisor = SIO_BAUD_TO_DIVISOR(SIO_BAUD_DEFAULT) *
rcs->baud_divisor_factor;
} else {
divisor = SIO_BAUD_TO_DIVISOR(rcs->baud) *
rcs->baud_divisor_factor;
}
sio_put_reg(rcs, SIO_BSR, SIO_BSR_BANK1);
sio_put_reg(rcs, SIO_LBGDL, 0xff);
sio_put_reg(rcs, SIO_LBGDH, divisor >> 8);
sio_put_reg(rcs, SIO_LBGDL, divisor & 0xff);
sio_put_reg(rcs, SIO_BSR, SIO_BSR_BANK0);
sio_put_reg(rcs, SIO_MCR, SIO_MCR_STD);
sio_put_reg(rcs, SIO_FCR, SIO_FCR_STD);
}
static void
rmc_comm_offline(struct rmc_comm_state *rcs)
{
if (rcs->sd_state.sio_handle != NULL)
ddi_regs_map_free(&rcs->sd_state.sio_handle);
rcs->sd_state.sio_handle = NULL;
rcs->sd_state.sio_regs = NULL;
}
static int
rmc_comm_online(struct rmc_comm_state *rcs, dev_info_t *dip)
{
ddi_acc_handle_t h;
caddr_t p;
int nregs;
int err;
if (ddi_dev_nregs(dip, &nregs) != DDI_SUCCESS)
nregs = 0;
switch (nregs) {
default:
case 1:
err = ddi_regs_map_setup(dip, 0, &p, 0, 0,
rmc_comm_dev_acc_attr, &h);
if (err != DDI_SUCCESS)
return (EIO);
rcs->sd_state.sio_handle = h;
rcs->sd_state.sio_regs = (void *)p;
break;
case 0:
break;
}
rmc_comm_hw_reset(rcs);
return (0);
}
int
rmc_comm_serdev_init(struct rmc_comm_state *rcs, dev_info_t *dip)
{
int err = DDI_SUCCESS;
rcs->sd_state.cycid = NULL;
err = rmc_comm_online(rcs, dip);
if (err != 0)
return (-1);
err = ddi_get_soft_iblock_cookie(dip, DDI_SOFTINT_LOW,
&rcs->dp_state.dp_iblk);
if (err != DDI_SUCCESS)
return (-1);
err = ddi_get_iblock_cookie(dip, 0, &rcs->sd_state.hw_iblk);
if (err != DDI_SUCCESS)
return (-1);
mutex_init(rcs->dp_state.dp_mutex, NULL, MUTEX_DRIVER,
rcs->dp_state.dp_iblk);
mutex_init(rcs->sd_state.hw_mutex, NULL, MUTEX_DRIVER,
rcs->sd_state.hw_iblk);
err = ddi_add_softintr(dip, DDI_SOFTINT_LOW, &rcs->sd_state.softid,
&rcs->dp_state.dp_iblk, NULL, rmc_comm_softint, (caddr_t)rcs);
if (err != DDI_SUCCESS) {
mutex_destroy(rcs->dp_state.dp_mutex);
mutex_destroy(rcs->sd_state.hw_mutex);
return (-1);
}
if (rcs->sd_state.sio_handle != NULL) {
err = ddi_add_intr(dip, 0, &rcs->sd_state.hw_iblk, NULL,
rmc_comm_hi_intr, (caddr_t)rcs);
if (err != DDI_SUCCESS) {
ddi_remove_softintr(rcs->sd_state.softid);
mutex_destroy(rcs->dp_state.dp_mutex);
mutex_destroy(rcs->sd_state.hw_mutex);
return (-1);
}
}
rcs->sd_state.cycid = ddi_periodic_add(rmc_comm_cyclic, rcs,
5 * RMC_COMM_ONE_SEC, DDI_IPL_1);
return (0);
}
void
rmc_comm_serdev_fini(struct rmc_comm_state *rcs, dev_info_t *dip)
{
rmc_comm_hw_reset(rcs);
if (rcs->sd_state.cycid != NULL) {
ddi_periodic_delete(rcs->sd_state.cycid);
rcs->sd_state.cycid = NULL;
if (rcs->sd_state.sio_handle != NULL)
ddi_remove_intr(dip, 0, rcs->sd_state.hw_iblk);
ddi_remove_softintr(rcs->sd_state.softid);
mutex_destroy(rcs->sd_state.hw_mutex);
mutex_destroy(rcs->dp_state.dp_mutex);
}
rmc_comm_offline(rcs);
}
static void
rmc_comm_unattach(struct rmc_comm_state *rcs, dev_info_t *dip, int instance,
boolean_t drvi_init, boolean_t dp_init, boolean_t sd_init)
{
if (rcs != NULL) {
rmc_comm_set_irq(rcs, B_FALSE);
if (drvi_init)
rmc_comm_drvintf_fini(rcs);
if (dp_init)
rmc_comm_dp_fini(rcs);
if (sd_init)
rmc_comm_serdev_fini(rcs, dip);
ddi_set_driver_private(dip, NULL);
}
ddi_soft_state_free(rmc_comm_statep, instance);
}
static int
rmc_comm_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
struct rmc_comm_state *rcs = NULL;
sig_state_t *current_sgn_p;
int instance;
instance = ddi_get_instance(dip);
if (instance != 0)
return (DDI_FAILURE);
switch (cmd) {
default:
return (DDI_FAILURE);
case DDI_RESUME:
if ((rcs = rmc_comm_getstate(dip, instance,
"rmc_comm_attach")) == NULL)
return (DDI_FAILURE);
rmc_comm_hw_reset(rcs);
rmc_comm_set_irq(rcs, B_TRUE);
rcs->dip = dip;
mutex_enter(&tod_lock);
if (watchdog_enable && tod_ops.tod_set_watchdog_timer != NULL &&
watchdog_was_active) {
(void) tod_ops.tod_set_watchdog_timer(0);
}
mutex_exit(&tod_lock);
mutex_enter(rcs->dp_state.dp_mutex);
dp_reset(rcs, INITIAL_SEQID, 1, 1);
mutex_exit(rcs->dp_state.dp_mutex);
current_sgn_p = (sig_state_t *)modgetsymvalue(
"current_sgn", 0);
if ((current_sgn_p != NULL) &&
(current_sgn_p->state_t.sig != 0)) {
CPU_SIGNATURE(current_sgn_p->state_t.sig,
current_sgn_p->state_t.state,
current_sgn_p->state_t.sub_state, -1);
}
return (DDI_SUCCESS);
case DDI_ATTACH:
break;
}
if (ddi_soft_state_zalloc(rmc_comm_statep, instance) != DDI_SUCCESS)
return (DDI_FAILURE);
if ((rcs = rmc_comm_getstate(dip, instance, "rmc_comm_attach")) ==
NULL) {
rmc_comm_unattach(rcs, dip, instance, 0, 0, 0);
return (DDI_FAILURE);
}
ddi_set_driver_private(dip, rcs);
rcs->dip = NULL;
rcs->baud = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"baud-rate", 0);
rcs->debug = ddi_prop_get_int(DDI_DEV_T_ANY, dip, DDI_PROP_DONTPASS,
"debug", 0);
rcs->baud_divisor_factor = ddi_prop_get_int(DDI_DEV_T_ANY, dip,
DDI_PROP_DONTPASS, "baud-divisor-factor", SIO_BAUD_DIVISOR_MIN);
if ((rcs->baud_divisor_factor < SIO_BAUD_DIVISOR_MIN) ||
(rcs->baud_divisor_factor > SIO_BAUD_DIVISOR_MAX))
rcs->baud_divisor_factor = SIO_BAUD_DIVISOR_MIN;
if (rmc_comm_serdev_init(rcs, dip) != 0) {
rmc_comm_unattach(rcs, dip, instance, 0, 0, 0);
return (DDI_FAILURE);
}
rmc_comm_dp_init(rcs);
if (rmc_comm_drvintf_init(rcs) != 0) {
rmc_comm_unattach(rcs, dip, instance, 0, 1, 1);
return (DDI_FAILURE);
}
rcs->majornum = ddi_driver_major(dip);
rcs->instance = instance;
rcs->dip = dip;
rmc_comm_set_irq(rcs, B_TRUE);
ddi_report_dev(dip);
mutex_enter(&rmc_comm_attach_lock);
rcs->is_attached = B_TRUE;
mutex_exit(&rmc_comm_attach_lock);
return (DDI_SUCCESS);
}
static int
rmc_comm_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
struct rmc_comm_state *rcs;
int instance;
instance = ddi_get_instance(dip);
if ((rcs = rmc_comm_getstate(dip, instance, "rmc_comm_detach")) == NULL)
return (DDI_FAILURE);
switch (cmd) {
case DDI_SUSPEND:
mutex_enter(&tod_lock);
if (watchdog_enable && watchdog_activated &&
tod_ops.tod_clear_watchdog_timer != NULL) {
watchdog_was_active = 1;
(void) tod_ops.tod_clear_watchdog_timer();
} else {
watchdog_was_active = 0;
}
mutex_exit(&tod_lock);
rcs->dip = NULL;
rmc_comm_hw_reset(rcs);
return (DDI_SUCCESS);
case DDI_DETACH:
mutex_enter(&rmc_comm_attach_lock);
if (rcs->n_registrations != 0) {
mutex_exit(&rmc_comm_attach_lock);
return (DDI_FAILURE);
}
rcs->is_attached = B_FALSE;
mutex_exit(&rmc_comm_attach_lock);
rmc_comm_unattach(rcs, dip, instance, 1, 1, 1);
return (DDI_SUCCESS);
default:
return (DDI_FAILURE);
}
}
static int
rmc_comm_reset(dev_info_t *dip, ddi_reset_cmd_t cmd)
{
struct rmc_comm_state *rcs;
if ((rcs = rmc_comm_getstate(dip, -1, "rmc_comm_reset")) == NULL)
return (DDI_FAILURE);
rmc_comm_hw_reset(rcs);
return (DDI_SUCCESS);
}
static struct dev_ops rmc_comm_dev_ops =
{
DEVO_REV,
0,
nodev,
nulldev,
nulldev,
rmc_comm_attach,
rmc_comm_detach,
rmc_comm_reset,
(struct cb_ops *)NULL,
(struct bus_ops *)NULL,
nulldev,
ddi_quiesce_not_supported,
};
static struct modldrv modldrv =
{
&mod_driverops,
"rmc_comm driver",
&rmc_comm_dev_ops
};
static struct modlinkage modlinkage =
{
MODREV_1,
{
&modldrv,
NULL
}
};
int
_init(void)
{
int err;
mutex_init(&rmc_comm_attach_lock, NULL, MUTEX_DRIVER, NULL);
err = ddi_soft_state_init(&rmc_comm_statep,
sizeof (struct rmc_comm_state), 0);
if (err == DDI_SUCCESS)
if ((err = mod_install(&modlinkage)) != 0) {
ddi_soft_state_fini(&rmc_comm_statep);
}
if (err != DDI_SUCCESS)
mutex_destroy(&rmc_comm_attach_lock);
return (err);
}
int
_info(struct modinfo *mip)
{
return (mod_info(&modlinkage, mip));
}
int
_fini(void)
{
int err;
if ((err = mod_remove(&modlinkage)) == 0) {
ddi_soft_state_fini(&rmc_comm_statep);
rmc_comm_major = NOMAJOR;
mutex_destroy(&rmc_comm_attach_lock);
}
return (err);
}