#include <sys/modctl.h>
#include <sys/ksynch.h>
#include <sys/taskq.h>
#include <sys/disp.h>
#include <sys/cmn_err.h>
#include <sys/note.h>
#include <sys/mach_descrip.h>
#include <sys/mdesc.h>
#include <sys/mdeg.h>
#include <sys/ldc.h>
#include <sys/ds.h>
#include <sys/ds_impl.h>
static taskq_t *ds_taskq;
#define DS_MAX_TASKQ_THR NCPU
#define DS_DISPATCH(fn, arg) taskq_dispatch(ds_taskq, fn, arg, TQ_SLEEP)
ds_domain_hdl_t ds_my_domain_hdl = DS_DHDL_INVALID;
char *ds_my_domain_name = NULL;
#ifdef DEBUG
uint_t ds_debug = 0;
#endif
static void ds_init(void);
static void ds_fini(void);
static int ds_ports_init(void);
static int ds_ports_fini(void);
static int ds_port_add(md_t *mdp, mde_cookie_t port, mde_cookie_t chan);
static void ds_log_init(void);
static void ds_log_fini(void);
static int ds_log_remove(void);
static void ds_log_purge(void *arg);
static struct modlmisc modlmisc = {
&mod_miscops,
"Domain Services 1.9"
};
static struct modlinkage modlinkage = {
MODREV_1,
(void *)&modlmisc,
NULL
};
int
_init(void)
{
int rv;
ds_init();
(void) i_ddi_attach_hw_nodes("cnex");
if ((rv = ds_ports_init()) != 0) {
cmn_err(CE_WARN, "Domain Services initialization failed");
ds_fini();
return (rv);
}
if ((rv = mod_install(&modlinkage)) != 0) {
(void) ds_ports_fini();
ds_fini();
}
return (rv);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&modlinkage, modinfop));
}
int
_fini(void)
{
int rv;
if ((rv = mod_remove(&modlinkage)) == 0) {
(void) ds_ports_fini();
ds_fini();
}
return (rv);
}
static void
ds_fini(void)
{
ds_enabled = B_FALSE;
taskq_destroy(ds_taskq);
ds_log_fini();
mutex_enter(&ds_svcs.lock);
(void) ds_walk_svcs(ds_svc_free, NULL);
mutex_exit(&ds_svcs.lock);
DS_FREE(ds_svcs.tbl, ds_svcs.maxsvcs * sizeof (ds_svc_t *));
mutex_destroy(&ds_svcs.lock);
bzero(&ds_svcs, sizeof (ds_svcs));
}
static int
ds_ports_init(void)
{
int idx;
int rv = 0;
md_t *mdp;
int num_nodes;
int listsz;
mde_cookie_t rootnode;
mde_cookie_t dsnode;
mde_cookie_t *portp = NULL;
mde_cookie_t *chanp = NULL;
int nport;
int nchan;
if ((mdp = md_get_handle()) == NULL) {
cmn_err(CE_WARN, "Unable to initialize machine description");
return (-1);
}
num_nodes = md_node_count(mdp);
ASSERT(num_nodes > 0);
listsz = num_nodes * sizeof (mde_cookie_t);
portp = kmem_zalloc(listsz, KM_SLEEP);
chanp = kmem_zalloc(listsz, KM_SLEEP);
rootnode = md_root_node(mdp);
ASSERT(rootnode != MDE_INVAL_ELEM_COOKIE);
nport = md_scan_dag(mdp, rootnode, md_find_name(mdp, DS_MD_ROOT_NAME),
md_find_name(mdp, "fwd"), portp);
if (nport <= 0) {
DS_DBG_MD(CE_NOTE, "No '%s' node in MD", DS_MD_ROOT_NAME);
goto done;
}
if (nport != 1) {
DS_DBG_MD(CE_NOTE, "Expected one '%s' node in the MD, found %d",
DS_MD_ROOT_NAME, nport);
}
dsnode = portp[0];
nport = md_scan_dag(mdp, dsnode, md_find_name(mdp, DS_MD_PORT_NAME),
md_find_name(mdp, "fwd"), portp);
if (nport <= 0) {
DS_DBG_MD(CE_NOTE, "No '%s' nodes in MD", DS_MD_PORT_NAME);
goto done;
}
for (idx = 0; idx < nport; idx++) {
nchan = md_scan_dag(mdp, portp[idx],
md_find_name(mdp, DS_MD_CHAN_NAME),
md_find_name(mdp, "fwd"), chanp);
if (nchan <= 0) {
cmn_err(CE_WARN, "No '%s' node for DS port",
DS_MD_CHAN_NAME);
rv = -1;
goto done;
}
if (nchan != 1) {
DS_DBG_MD(CE_NOTE, "Expected one '%s' node for DS "
" port, found %d", DS_MD_CHAN_NAME, nchan);
}
if (ds_port_add(mdp, portp[idx], chanp[0]) != 0) {
rv = -1;
goto done;
}
}
done:
if (rv != 0)
(void) ds_ports_fini();
DS_FREE(portp, listsz);
DS_FREE(chanp, listsz);
(void) md_fini_handle(mdp);
return (rv);
}
static int
ds_ports_fini(void)
{
int idx;
for (idx = 0; idx < DS_MAX_PORTS; idx++) {
if (DS_PORT_IN_SET(ds_allports, idx)) {
(void) ds_remove_port(idx, 1);
}
}
return (0);
}
static int
ds_port_add(md_t *mdp, mde_cookie_t port, mde_cookie_t chan)
{
uint64_t port_id;
uint64_t ldc_id;
uint8_t *ldcidsp;
int len;
if (md_get_prop_val(mdp, port, "id", &port_id) != 0) {
cmn_err(CE_WARN, "%s: port 'id' property not found",
__func__);
return (-1);
}
if (port_id > DS_MAX_PORT_ID) {
cmn_err(CE_WARN, "%s: port ID %ld out of range",
__func__, port_id);
return (-1);
}
if (md_get_prop_val(mdp, chan, "id", &ldc_id) != 0) {
cmn_err(CE_WARN, "ds@%lx: %s: no channel 'id' property",
port_id, __func__);
return (-1);
}
if (ds_add_port(port_id, ldc_id, DS_DHDL_INVALID, NULL, 1) != 0)
return (-1);
if (ds_sp_port_id == DS_PORTID_INVALID &&
md_get_prop_data(mdp, port, "ldc-ids", &ldcidsp, &len) == 0) {
ds_sp_port_id = port_id;
}
return (0);
}
void
ds_set_my_dom_hdl_name(ds_domain_hdl_t dhdl, char *name)
{
ds_my_domain_hdl = dhdl;
if (ds_my_domain_name != NULL) {
DS_FREE(ds_my_domain_name, strlen(ds_my_domain_name)+1);
ds_my_domain_name = NULL;
}
if (name != NULL) {
ds_my_domain_name = ds_strdup(name);
}
}
void
ds_init()
{
ds_common_init();
ds_taskq = taskq_create("ds_taskq", 1, minclsyspri, 1,
DS_MAX_TASKQ_THR, TASKQ_PREPOPULATE | TASKQ_DYNAMIC);
ds_log_init();
}
int
ds_sys_dispatch_func(void (func)(void *), void *arg)
{
return (DS_DISPATCH(func, arg) == TASKQID_INVALID);
}
void
ds_sys_drain_events(ds_port_t *port)
{
_NOTE(ARGUNUSED(port))
}
void
ds_sys_port_init(ds_port_t *port)
{
_NOTE(ARGUNUSED(port))
}
void
ds_sys_port_fini(ds_port_t *port)
{
_NOTE(ARGUNUSED(port))
}
void
ds_sys_ldc_init(ds_port_t *port)
{
int rv;
char ebuf[DS_EBUFSIZE];
ASSERT(MUTEX_HELD(&port->lock));
if ((rv = ldc_open(port->ldc.hdl)) != 0) {
cmn_err(CE_WARN, "ds@%lx: %s: ldc_open: %s",
PORTID(port), __func__, ds_errno_to_str(rv, ebuf));
return;
}
(void) ldc_up(port->ldc.hdl);
(void) ldc_status(port->ldc.hdl, &port->ldc.state);
DS_DBG_LDC(CE_NOTE, "ds@%lx: %s: initial LDC state 0x%x",
PORTID(port), __func__, port->ldc.state);
port->state = DS_PORT_LDC_INIT;
}
static struct log {
ds_log_entry_t *head;
ds_log_entry_t *freelist;
size_t size;
uint32_t nentry;
kmutex_t lock;
} ds_log;
uint_t ds_log_sz = DS_LOG_DEFAULT_SZ;
static ds_log_entry_t ds_log_entry_pool[DS_LOG_NPOOL];
static void
ds_log_init(void)
{
mutex_init(&ds_log.lock, NULL, MUTEX_DRIVER, NULL);
mutex_enter(&ds_log.lock);
ds_log.head = NULL;
ds_log.size = 0;
ds_log.nentry = 0;
for (int i = 0; i < DS_LOG_NPOOL; i++) {
ds_log_entry_pool[i].next = ds_log.freelist;
ds_log.freelist = &ds_log_entry_pool[i];
}
mutex_exit(&ds_log.lock);
DS_DBG_LOG(CE_NOTE, "ds_log initialized: size=%d bytes, "
" limit=%d bytes, ninit=%ld", ds_log_sz, DS_LOG_LIMIT,
DS_LOG_NPOOL);
}
static void
ds_log_fini(void)
{
ds_log_entry_t *next;
mutex_enter(&ds_log.lock);
while (ds_log.nentry > 0)
(void) ds_log_remove();
while (ds_log.freelist != NULL) {
next = ds_log.freelist->next;
if (!DS_IS_POOL_ENTRY(ds_log.freelist)) {
kmem_free(ds_log.freelist, sizeof (ds_log_entry_t));
}
ds_log.freelist = next;
}
mutex_exit(&ds_log.lock);
mutex_destroy(&ds_log.lock);
}
static ds_log_entry_t *
ds_log_entry_alloc(void)
{
ds_log_entry_t *new = NULL;
ASSERT(MUTEX_HELD(&ds_log.lock));
if (ds_log.freelist != NULL) {
new = ds_log.freelist;
ds_log.freelist = ds_log.freelist->next;
}
if (new == NULL) {
new = kmem_zalloc(sizeof (ds_log_entry_t), KM_SLEEP);
}
ASSERT(new);
return (new);
}
static void
ds_log_entry_free(ds_log_entry_t *entry)
{
ASSERT(MUTEX_HELD(&ds_log.lock));
if (entry == NULL)
return;
if (entry->data != NULL) {
kmem_free(entry->data, entry->datasz);
entry->data = NULL;
}
entry->next = ds_log.freelist;
ds_log.freelist = entry;
}
static int
ds_log_add(ds_log_entry_t *new)
{
ASSERT(MUTEX_HELD(&ds_log.lock));
if (ds_log.head == NULL) {
new->prev = new;
new->next = new;
ds_log.head = new;
} else {
ds_log_entry_t *head = ds_log.head;
ds_log_entry_t *tail = ds_log.head->prev;
new->next = head;
new->prev = tail;
tail->next = new;
head->prev = new;
}
ds_log.size += DS_LOG_ENTRY_SZ(new);
ds_log.nentry++;
DS_DBG_LOG(CE_NOTE, "ds_log: added %ld data bytes, %ld total bytes",
new->datasz, DS_LOG_ENTRY_SZ(new));
return (0);
}
static int
ds_log_remove(void)
{
ds_log_entry_t *head;
ASSERT(MUTEX_HELD(&ds_log.lock));
head = ds_log.head;
if (head == NULL)
return (0);
if (head->next == ds_log.head) {
ds_log.head = NULL;
} else {
head->next->prev = head->prev;
head->prev->next = head->next;
ds_log.head = head->next;
}
DS_DBG_LOG(CE_NOTE, "ds_log: removed %ld data bytes, %ld total bytes",
head->datasz, DS_LOG_ENTRY_SZ(head));
ds_log.size -= DS_LOG_ENTRY_SZ(head);
ds_log.nentry--;
ds_log_entry_free(head);
return (0);
}
static int
ds_log_replace(int32_t dest, uint8_t *msg, size_t sz)
{
ds_log_entry_t *head;
ASSERT(MUTEX_HELD(&ds_log.lock));
head = ds_log.head;
DS_DBG_LOG(CE_NOTE, "ds_log: replaced %ld data bytes (%ld total) with "
" %ld data bytes (%ld total)", head->datasz,
DS_LOG_ENTRY_SZ(head), sz, sz + sizeof (ds_log_entry_t));
ds_log.size -= DS_LOG_ENTRY_SZ(head);
kmem_free(head->data, head->datasz);
head->data = msg;
head->datasz = sz;
head->timestamp = ddi_get_time();
head->dest = dest;
ds_log.size += DS_LOG_ENTRY_SZ(head);
ds_log.head = head->next;
return (0);
}
static void
ds_log_purge(void *arg)
{
_NOTE(ARGUNUSED(arg))
mutex_enter(&ds_log.lock);
DS_DBG_LOG(CE_NOTE, "ds_log: purging oldest log entries");
while ((ds_log.nentry) && (ds_log.size >= ds_log_sz)) {
(void) ds_log_remove();
}
mutex_exit(&ds_log.lock);
}
int
ds_log_add_msg(int32_t dest, uint8_t *msg, size_t sz)
{
int rv = 0;
void *data;
mutex_enter(&ds_log.lock);
data = kmem_alloc(sz, KM_SLEEP);
bcopy(msg, data, sz);
if ((ds_log.nentry) && ((ds_log.size + sz) >= ds_log_sz)) {
DS_DBG_LOG(CE_NOTE, "%s: replacing oldest entry with new entry",
__func__);
(void) ds_log_replace(dest, data, sz);
} else {
ds_log_entry_t *new;
new = ds_log_entry_alloc();
new->data = data;
new->datasz = sz;
new->timestamp = ddi_get_time();
new->dest = dest;
rv = ds_log_add(new);
}
if ((ds_log.nentry > 1) && (ds_log.size >= DS_LOG_LIMIT)) {
DS_DBG_LOG(CE_NOTE, "%s: log exceeded %d bytes, scheduling"
" a purge...", __func__, DS_LOG_LIMIT);
if (DS_DISPATCH(ds_log_purge, NULL) == TASKQID_INVALID) {
cmn_err(CE_NOTE, "%s: purge thread failed to start",
__func__);
}
}
mutex_exit(&ds_log.lock);
return (rv);
}
int
ds_add_port(uint64_t port_id, uint64_t ldc_id, ds_domain_hdl_t dhdl,
char *dom_name, int verbose)
{
ds_port_t *newport;
if (port_id > DS_MAX_PORT_ID) {
cmn_err(CE_WARN, "%s: port ID %ld out of range",
__func__, port_id);
return (EINVAL);
}
DS_DBG_MD(CE_NOTE, "%s: adding port ds@%ld, LDC: 0x%lx, dhdl: 0x%lx "
"name: '%s'", __func__, port_id, ldc_id, dhdl,
dom_name == NULL ? "NULL" : dom_name);
newport = &ds_ports[port_id];
if (newport->state != DS_PORT_FREE) {
if (verbose) {
cmn_err(CE_WARN, "ds@%lx: %s: port already exists",
port_id, __func__);
}
if (newport->domain_hdl == DS_DHDL_INVALID) {
newport->domain_hdl = dhdl;
}
if (newport->domain_name == NULL && dom_name != NULL) {
newport->domain_name = ds_strdup(dom_name);
}
return (EBUSY);
}
newport->id = port_id;
newport->ldc.id = ldc_id;
newport->domain_hdl = dhdl;
if (dom_name) {
newport->domain_name = ds_strdup(dom_name);
} else
newport->domain_name = NULL;
ds_port_common_init(newport);
return (0);
}
int
ds_remove_port(uint64_t port_id, int is_fini)
{
ds_port_t *port;
if (port_id >= DS_MAX_PORTS || !DS_PORT_IN_SET(ds_allports, port_id)) {
DS_DBG_MD(CE_NOTE, "%s: invalid port %lx", __func__,
port_id);
return (EINVAL);
}
DS_DBG_MD(CE_NOTE, "%s: removing port ds@%lx", __func__, port_id);
port = &ds_ports[port_id];
mutex_enter(&port->lock);
if (port->state >= DS_PORT_LDC_INIT) {
(void) ds_ldc_fini(port);
}
if (port->domain_name) {
DS_FREE(port->domain_name, strlen(port->domain_name) + 1);
port->domain_name = NULL;
}
port->domain_hdl = DS_DHDL_INVALID;
ds_port_common_fini(port);
mutex_exit(&port->lock);
return (0);
}
int
ds_service_lookup(ds_svc_hdl_t hdl, char **servicep, uint_t *is_client)
{
ds_svc_t *svc;
mutex_enter(&ds_svcs.lock);
if ((svc = ds_get_svc(hdl)) == NULL) {
mutex_exit(&ds_svcs.lock);
DS_DBG(CE_NOTE, "%s: handle 0x%llx not found", __func__,
(u_longlong_t)hdl);
return (ENXIO);
}
*servicep = svc->cap.svc_id;
*is_client = svc->flags & DSSF_ISCLIENT;
mutex_exit(&ds_svcs.lock);
return (0);
}
int
ds_domain_lookup(ds_svc_hdl_t hdl, ds_domain_hdl_t *dhdlp)
{
ds_svc_t *svc;
mutex_enter(&ds_svcs.lock);
if ((svc = ds_get_svc(hdl)) == NULL) {
mutex_exit(&ds_svcs.lock);
DS_DBG(CE_NOTE, "%s: handle 0x%llx not found", __func__,
(u_longlong_t)hdl);
return (ENXIO);
}
if (svc->port == NULL)
*dhdlp = ds_my_domain_hdl;
else
*dhdlp = svc->port->domain_hdl;
mutex_exit(&ds_svcs.lock);
return (0);
}
int
ds_hdl_isready(ds_svc_hdl_t hdl, uint_t *is_ready)
{
ds_svc_t *svc;
mutex_enter(&ds_svcs.lock);
if ((svc = ds_get_svc(hdl)) == NULL) {
mutex_exit(&ds_svcs.lock);
DS_DBG(CE_NOTE, "%s: handle 0x%llx not found", __func__,
(u_longlong_t)hdl);
return (ENXIO);
}
*is_ready = (svc->state == DS_SVC_ACTIVE);
mutex_exit(&ds_svcs.lock);
return (0);
}
int
ds_dom_name_to_hdl(char *domain_name, ds_domain_hdl_t *dhdlp)
{
int i;
ds_port_t *port;
if (domain_name == NULL) {
return (ENXIO);
}
if (ds_my_domain_name != NULL &&
strcmp(ds_my_domain_name, domain_name) == 0) {
*dhdlp = ds_my_domain_hdl;
return (0);
}
for (i = 0, port = ds_ports; i < DS_MAX_PORTS; i++, port++) {
if (port->state != DS_PORT_FREE &&
port->domain_name != NULL &&
strcmp(port->domain_name, domain_name) == 0) {
*dhdlp = port->domain_hdl;
return (0);
}
}
return (ENXIO);
}
int
ds_dom_hdl_to_name(ds_domain_hdl_t dhdl, char **domain_namep)
{
int i;
ds_port_t *port;
if (dhdl == ds_my_domain_hdl) {
if (ds_my_domain_name != NULL) {
*domain_namep = ds_my_domain_name;
return (0);
}
return (ENXIO);
}
for (i = 0, port = ds_ports; i < DS_MAX_PORTS; i++, port++) {
if (port->state != DS_PORT_FREE &&
port->domain_hdl == dhdl) {
*domain_namep = port->domain_name;
return (0);
}
}
return (ENXIO);
}
void
ds_unreg_all(int instance)
{
int idx;
ds_svc_t *svc;
ds_svc_hdl_t hdl;
DS_DBG_USR(CE_NOTE, "%s: entered", __func__);
mutex_enter(&ds_svcs.lock);
for (idx = 0; idx < ds_svcs.maxsvcs; idx++) {
svc = ds_svcs.tbl[idx];
if (DS_SVC_ISFREE(svc))
continue;
if ((svc->flags & DSSF_ISUSER) != 0 && svc->drvi == instance) {
hdl = svc->hdl;
mutex_exit(&ds_svcs.lock);
(void) ds_unreg_hdl(hdl);
mutex_enter(&ds_svcs.lock);
DS_DBG_USR(CE_NOTE, "%s: ds_unreg_hdl(0x%llx):",
__func__, (u_longlong_t)hdl);
}
}
mutex_exit(&ds_svcs.lock);
}
void
ds_cbarg_get_hdl(ds_cb_arg_t arg, ds_svc_hdl_t *hdlp)
{
ds_svc_t *svc = (ds_svc_t *)arg;
ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg);
*hdlp = svc->hdl;
}
void
ds_cbarg_get_flags(ds_cb_arg_t arg, uint32_t *flagsp)
{
ds_svc_t *svc = (ds_svc_t *)arg;
ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg);
*flagsp = svc->flags;
}
void
ds_cbarg_get_drv_info(ds_cb_arg_t arg, int *drvip)
{
ds_svc_t *svc = (ds_svc_t *)arg;
ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg);
*drvip = svc->drvi;
}
void
ds_cbarg_get_drv_per_svc_ptr(ds_cb_arg_t arg, void **dpspp)
{
ds_svc_t *svc = (ds_svc_t *)arg;
ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg);
*dpspp = svc->drv_psp;
}
void
ds_cbarg_get_domain(ds_cb_arg_t arg, ds_domain_hdl_t *dhdlp)
{
ds_svc_t *svc = (ds_svc_t *)arg;
ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg);
if (svc->port == NULL)
*dhdlp = ds_my_domain_hdl;
else
*dhdlp = svc->port->domain_hdl;
}
void
ds_cbarg_get_service_id(ds_cb_arg_t arg, char **servicep)
{
ds_svc_t *svc = (ds_svc_t *)arg;
ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg);
*servicep = svc->cap.svc_id;
}
void
ds_cbarg_set_drv_per_svc_ptr(ds_cb_arg_t arg, void *dpsp)
{
ds_svc_t *svc = (ds_svc_t *)arg;
ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg);
svc->drv_psp = dpsp;
}
void
ds_cbarg_set_cookie(ds_svc_t *svc)
{
svc->ops.cb_arg = (ds_cb_arg_t)(svc);
}
int
ds_hdl_get_cbarg(ds_svc_hdl_t hdl, ds_cb_arg_t *cbargp)
{
ds_svc_t *svc;
mutex_enter(&ds_svcs.lock);
if ((svc = ds_get_svc(hdl)) != NULL &&
(svc->flags & DSSF_ISUSER) != 0) {
ASSERT(svc == (ds_svc_t *)svc->ops.cb_arg);
*cbargp = svc->ops.cb_arg;
mutex_exit(&ds_svcs.lock);
return (0);
}
mutex_exit(&ds_svcs.lock);
return (ENXIO);
}
int
ds_is_my_hdl(ds_svc_hdl_t hdl, int instance)
{
ds_svc_t *svc;
int rv = 0;
mutex_enter(&ds_svcs.lock);
if ((svc = ds_get_svc(hdl)) == NULL) {
DS_DBG_USR(CE_NOTE, "%s: invalid hdl: 0x%llx\n", __func__,
(u_longlong_t)hdl);
rv = ENXIO;
} else if (instance == DS_INVALID_INSTANCE) {
if ((svc->flags & DSSF_ISUSER) != 0) {
DS_DBG_USR(CE_NOTE, "%s: unowned hdl: 0x%llx\n",
__func__, (u_longlong_t)hdl);
rv = EACCES;
}
} else if ((svc->flags & DSSF_ISUSER) == 0 || svc->drvi != instance) {
DS_DBG_USR(CE_NOTE, "%s: unowned hdl: 0x%llx\n", __func__,
(u_longlong_t)hdl);
rv = EACCES;
}
mutex_exit(&ds_svcs.lock);
return (rv);
}