#include <sys/ddi.h>
#include <sys/sunddi.h>
#include <sys/stat.h>
#include <sys/open.h>
#include <sys/types.h>
#include <sys/file.h>
#include <sys/conf.h>
#include <sys/avl.h>
#include <sys/stddef.h>
#include <sys/sysmacros.h>
#include <sys/id_space.h>
#include <sys/mkdev.h>
#include <sys/sunndi.h>
#include <sys/esunddi.h>
#include <sys/ctype.h>
#include <sys/fs/dv_node.h>
#include "eedev.h"
#define EEDEV_MINOR_MIN 1
#define EEDEV_MINOR_MAX MAXMIN32
typedef struct eedev {
kmutex_t eedev_mutex;
list_t eedev_list;
list_t eedev_dips;
id_space_t *eedev_idspace;
dev_info_t *eedev_dip;
} eedev_t;
static eedev_t eedev;
typedef enum {
EEDEV_F_READ_ONLY = 1 << 0,
EEDEV_F_ID_ALLOC = 1 << 1,
EEDEV_F_MINOR_PROPS = 1 << 2,
EEDEV_F_MINOR_VALID = 1 << 3,
EEDEV_F_BUSY = 1 << 4,
EEDEV_F_USABLE = 1 << 5,
EEDEV_F_HELD = 1 << 6
} eedev_flags_t;
typedef enum {
EEDEV_DIP_F_REMOVED = 1 << 0
} eedev_dip_flags_t;
typedef struct eedev_dip {
list_node_t ed_link;
dev_info_t *ed_dip;
char *ed_ua;
eedev_dip_flags_t ed_flags;
ddi_unbind_callback_t ed_cb;
list_t ed_devs;
} eedev_dip_t;
struct eedev_hdl {
list_node_t eh_link;
list_node_t eh_dip_link;
eedev_dip_t *eh_dip;
kcondvar_t eh_cv;
void *eh_driver;
char *eh_name;
const eedev_ops_t *eh_ops;
id_t eh_minor;
dev_t eh_dev;
uint32_t eh_size;
uint32_t eh_seg;
uint32_t eh_read_gran;
uint32_t eh_write_gran;
uint32_t eh_max_read;
uint32_t eh_max_write;
eedev_flags_t eh_flags;
uint32_t eh_nwaiters;
};
static uint32_t eedev_default_max_io = 128;
static eedev_dip_t *
eedev_dip_find(dev_info_t *dip)
{
VERIFY(MUTEX_HELD(&eedev.eedev_mutex));
for (eedev_dip_t *e = list_head(&eedev.eedev_dips); e != NULL;
e = list_next(&eedev.eedev_dips, e)) {
if (dip == e->ed_dip) {
return (e);
}
}
return (NULL);
}
static eedev_hdl_t *
eedev_lookup_by_id(dev_t dev)
{
mutex_enter(&eedev.eedev_mutex);
for (eedev_hdl_t *h = list_head(&eedev.eedev_list); h != NULL;
h = list_next(&eedev.eedev_list, h)) {
if (h->eh_dev != dev)
continue;
if ((h->eh_flags & EEDEV_F_HELD) == 0)
break;
mutex_exit(&eedev.eedev_mutex);
return (h);
}
mutex_exit(&eedev.eedev_mutex);
return (NULL);
}
static int
eedev_hold_by_id(dev_t dev)
{
eedev_hdl_t *hdl = NULL;
mutex_enter(&eedev.eedev_mutex);
for (eedev_hdl_t *h = list_head(&eedev.eedev_list); h != NULL;
h = list_next(&eedev.eedev_list, h)) {
if (h->eh_dev == dev) {
hdl = h;
break;
}
}
if (hdl == NULL) {
mutex_exit(&eedev.eedev_mutex);
return (ESTALE);
}
restart:
if ((hdl->eh_dip->ed_flags & EEDEV_DIP_F_REMOVED) != 0) {
mutex_exit(&eedev.eedev_mutex);
return (ESTALE);
}
const eedev_flags_t targ = EEDEV_F_HELD | EEDEV_F_USABLE;
if ((hdl->eh_flags & targ) == targ) {
VERIFY0(hdl->eh_flags & EEDEV_F_BUSY);
mutex_exit(&eedev.eedev_mutex);
return (0);
}
if ((hdl->eh_flags & EEDEV_F_BUSY) != 0) {
hdl->eh_nwaiters++;
while ((hdl->eh_flags & EEDEV_F_BUSY) != 0) {
int cv = cv_wait_sig(&hdl->eh_cv, &eedev.eedev_mutex);
if (cv == 0) {
hdl->eh_nwaiters--;
cv_broadcast(&hdl->eh_cv);
mutex_exit(&eedev.eedev_mutex);
return (EINTR);
}
}
hdl->eh_nwaiters--;
goto restart;
}
hdl->eh_flags |= EEDEV_F_BUSY;
dev_info_t *pdip = ddi_get_parent(hdl->eh_dip->ed_dip);
mutex_exit(&eedev.eedev_mutex);
ndi_devi_enter(pdip);
e_ddi_hold_devi(hdl->eh_dip->ed_dip);
ndi_devi_exit(pdip);
mutex_enter(&eedev.eedev_mutex);
hdl->eh_flags |= EEDEV_F_HELD;
if ((hdl->eh_dip->ed_flags & EEDEV_DIP_F_REMOVED) != 0) {
hdl->eh_flags &= ~(EEDEV_F_HELD | EEDEV_F_BUSY);
cv_broadcast(&hdl->eh_cv);
mutex_exit(&eedev.eedev_mutex);
ddi_release_devi(hdl->eh_dip->ed_dip);
return (ESTALE);
}
if ((hdl->eh_dip->ed_flags & EEDEV_F_USABLE) == 0) {
dev_info_t *child;
mutex_exit(&eedev.eedev_mutex);
if (ndi_devi_config_one(pdip, hdl->eh_dip->ed_ua, &child,
NDI_CONFIG | NDI_ONLINE_ATTACH | NDI_NO_EVENT) ==
NDI_SUCCESS) {
ddi_release_devi(child);
}
mutex_enter(&eedev.eedev_mutex);
if ((hdl->eh_dip->ed_flags & EEDEV_DIP_F_REMOVED) != 0 ||
(hdl->eh_flags & EEDEV_F_USABLE) == 0) {
hdl->eh_flags &= ~(EEDEV_F_HELD | EEDEV_F_BUSY);
cv_broadcast(&hdl->eh_cv);
mutex_exit(&eedev.eedev_mutex);
ddi_release_devi(hdl->eh_dip->ed_dip);
return (ESTALE);
}
}
hdl->eh_flags &= ~EEDEV_F_BUSY;
cv_broadcast(&hdl->eh_cv);
VERIFY3U(hdl->eh_flags & targ, ==, targ);
mutex_exit(&eedev.eedev_mutex);
return (0);
}
static void
eedev_dip_free(eedev_dip_t *e)
{
list_destroy(&e->ed_devs);
strfree(e->ed_ua);
kmem_free(e, sizeof (eedev_dip_t));
}
static void
eedev_free(eedev_hdl_t *eh)
{
if ((eh->eh_flags & EEDEV_F_MINOR_VALID) != 0) {
ddi_remove_minor_node(eedev.eedev_dip, eh->eh_name);
eh->eh_flags &= ~EEDEV_F_MINOR_VALID;
}
if ((eh->eh_flags & EEDEV_F_MINOR_PROPS) != 0) {
(void) ddi_prop_remove(eh->eh_dev, eedev.eedev_dip, "Size");
eh->eh_flags &= ~EEDEV_F_MINOR_PROPS;
}
if ((eh->eh_flags & EEDEV_F_ID_ALLOC) != 0) {
id_free(eedev.eedev_idspace, eh->eh_minor);
}
strfree(eh->eh_name);
cv_destroy(&eh->eh_cv);
kmem_free(eh, sizeof (eedev_hdl_t));
}
void
eedev_fini(eedev_hdl_t *eh)
{
if (eh == NULL) {
return;
}
mutex_enter(&eedev.eedev_mutex);
VERIFY0(eh->eh_flags & EEDEV_F_HELD);
VERIFY0(eh->eh_flags & EEDEV_F_BUSY);
VERIFY3U(eh->eh_flags & EEDEV_F_USABLE, !=, 0);
eh->eh_flags &= ~EEDEV_F_USABLE;
eh->eh_ops = NULL;
eh->eh_driver = NULL;
mutex_exit(&eedev.eedev_mutex);
}
static void
eedev_dip_unbind_taskq(void *arg)
{
eedev_hdl_t *hdl;
eedev_dip_t *ed = arg;
mutex_enter(&eedev.eedev_mutex);
while ((hdl = list_remove_head(&ed->ed_devs)) != NULL) {
while ((hdl->eh_flags & EEDEV_F_BUSY) != 0 ||
hdl->eh_nwaiters > 0) {
cv_wait(&hdl->eh_cv, &eedev.eedev_mutex);
}
eedev_free(hdl);
}
(void) devfs_clean(ddi_get_parent(eedev.eedev_dip), NULL, 0);
eedev_dip_free(ed);
mutex_exit(&eedev.eedev_mutex);
}
static void
eedev_dip_unbind_cb(void *arg, dev_info_t *dip)
{
eedev_dip_t *ed = arg;
mutex_enter(&eedev.eedev_mutex);
ed->ed_flags |= EEDEV_DIP_F_REMOVED;
list_remove(&eedev.eedev_dips, ed);
for (eedev_hdl_t *h = list_head(&ed->ed_devs); h != NULL;
h = list_next(&ed->ed_devs, h)) {
list_remove(&eedev.eedev_list, h);
}
mutex_exit(&eedev.eedev_mutex);
(void) taskq_dispatch(system_taskq, eedev_dip_unbind_taskq, ed,
TQ_SLEEP);
}
static eedev_dip_t *
eedev_dip_create(dev_info_t *dip)
{
eedev_dip_t *e;
e = kmem_zalloc(sizeof (eedev_dip_t), KM_SLEEP);
e->ed_dip = dip;
e->ed_ua = kmem_asprintf("%s@%s", ddi_node_name(dip),
ddi_get_name_addr(dip));
e->ed_cb.ddiub_cb = eedev_dip_unbind_cb;
e->ed_cb.ddiub_arg = e;
list_create(&e->ed_devs, sizeof (eedev_hdl_t),
offsetof(eedev_hdl_t, eh_dip_link));
e_ddi_register_unbind_callback(dip, &e->ed_cb);
return (e);
}
static bool
eedev_minor_create(eedev_hdl_t *hdl)
{
VERIFY(MUTEX_HELD(&eedev.eedev_mutex));
hdl->eh_dev = makedevice(ddi_driver_major(eedev.eedev_dip),
hdl->eh_minor);
if ((hdl->eh_flags & EEDEV_F_MINOR_PROPS) == 0) {
if (ddi_prop_update_int64(hdl->eh_dev, eedev.eedev_dip, "Size",
hdl->eh_size) != DDI_PROP_SUCCESS) {
dev_err(eedev.eedev_dip, CE_WARN, "!failed to set Size "
"property for minor %s (%d) for %s%d", hdl->eh_name,
hdl->eh_minor, ddi_driver_name(hdl->eh_dip->ed_dip),
ddi_get_instance(hdl->eh_dip->ed_dip));
return (false);
}
hdl->eh_flags |= EEDEV_F_MINOR_PROPS;
}
if ((hdl->eh_flags & EEDEV_F_MINOR_VALID) == 0) {
if (ddi_create_minor_node(eedev.eedev_dip, hdl->eh_name,
S_IFCHR, hdl->eh_minor, DDI_NT_EEPROM, 0) != DDI_SUCCESS) {
dev_err(eedev.eedev_dip, CE_WARN, "!failed to create "
"eeprom minor %s (%d) for %s%d", hdl->eh_name,
hdl->eh_minor, ddi_driver_name(hdl->eh_dip->ed_dip),
ddi_get_instance(hdl->eh_dip->ed_dip));
return (false);
}
}
hdl->eh_flags |= EEDEV_F_MINOR_VALID;
return (true);
}
int
eedev_create(const eedev_reg_t *reg, eedev_hdl_t **hdlp)
{
eedev_hdl_t *hdl;
eedev_dip_t *dip;
char *name;
if (reg->ereg_vers != EEDEV_REG_VERS0) {
return (ENOTSUP);
}
if (reg->ereg_size == 0 || reg->ereg_dip == NULL ||
reg->ereg_ops == NULL || reg->ereg_ops->eo_read == NULL) {
return (EINVAL);
}
if (!reg->ereg_ro && reg->ereg_ops->eo_write == NULL) {
return (EINVAL);
}
if (reg->ereg_seg > reg->ereg_size ||
reg->ereg_read_gran > reg->ereg_size ||
reg->ereg_write_gran > reg->ereg_size) {
return (EINVAL);
}
if (reg->ereg_name != NULL) {
size_t len = strnlen(reg->ereg_name, EEDEV_NAME_MAX);
if (len >= EEDEV_NAME_MAX || len == 0) {
return (EINVAL);
}
for (size_t i = 0; i < len; i++) {
if (!ISALNUM(reg->ereg_name[i])) {
return (EINVAL);
}
}
}
mutex_enter(&eedev.eedev_mutex);
dip = eedev_dip_find(reg->ereg_dip);
if (dip == NULL) {
dip = eedev_dip_create(reg->ereg_dip);
list_insert_tail(&eedev.eedev_dips, dip);
}
if (reg->ereg_name != NULL) {
name = kmem_asprintf("%s:%d:%s", ddi_driver_name(reg->ereg_dip),
ddi_get_instance(reg->ereg_dip), reg->ereg_name);
} else {
name = kmem_asprintf("%s:%d:eeprom",
ddi_driver_name(reg->ereg_dip),
ddi_get_instance(reg->ereg_dip));
}
hdl = NULL;
for (eedev_hdl_t *h = list_head(&dip->ed_devs); h != NULL;
h = list_next(&dip->ed_devs, h)) {
if (strcmp(h->eh_name, name) == 0) {
hdl = h;
break;
}
}
if (hdl != NULL) {
VERIFY0(hdl->eh_flags & EEDEV_F_USABLE);
strfree(name);
name = NULL;
hdl->eh_ops = reg->ereg_ops;
hdl->eh_driver = reg->ereg_driver;
VERIFY3U(hdl->eh_size, ==, reg->ereg_size);
VERIFY3U(hdl->eh_seg, ==, reg->ereg_seg);
VERIFY3U(hdl->eh_read_gran, ==, reg->ereg_read_gran);
VERIFY3U(hdl->eh_write_gran, ==, reg->ereg_write_gran);
if (reg->ereg_max_read != 0) {
VERIFY3U(hdl->eh_max_read, ==, reg->ereg_max_read);
}
if (reg->ereg_max_write != 0) {
VERIFY3U(hdl->eh_max_write, ==, reg->ereg_max_write);
}
} else {
hdl = kmem_zalloc(sizeof (eedev_hdl_t), KM_SLEEP);
cv_init(&hdl->eh_cv, NULL, CV_DRIVER, NULL);
hdl->eh_dip = dip;
hdl->eh_driver = reg->ereg_driver;
hdl->eh_name = name;
name = NULL;
hdl->eh_ops = reg->ereg_ops;
hdl->eh_minor = id_alloc_nosleep(eedev.eedev_idspace);
if (hdl->eh_minor == -1) {
eedev_free(hdl);
return (EOVERFLOW);
}
hdl->eh_flags |= EEDEV_F_ID_ALLOC;
hdl->eh_ops = reg->ereg_ops;
hdl->eh_driver = reg->ereg_driver;
hdl->eh_size = reg->ereg_size;
hdl->eh_seg = reg->ereg_seg;
hdl->eh_read_gran = reg->ereg_read_gran;
hdl->eh_write_gran = reg->ereg_write_gran;
hdl->eh_max_read = reg->ereg_max_read;
hdl->eh_max_write = reg->ereg_max_write;
if (hdl->eh_max_read == 0) {
hdl->eh_max_read = MIN(eedev_default_max_io,
hdl->eh_size);
}
if (hdl->eh_max_write == 0) {
hdl->eh_max_write = MIN(eedev_default_max_io,
hdl->eh_size);
}
if (reg->ereg_ro) {
hdl->eh_flags |= EEDEV_F_READ_ONLY;
}
for (eedev_hdl_t *h = list_head(&eedev.eedev_list); h != NULL;
h = list_next(&eedev.eedev_list, h)) {
if (strcmp(h->eh_name, hdl->eh_name) == 0) {
eedev_free(hdl);
mutex_exit(&eedev.eedev_mutex);
return (EEXIST);
}
}
list_insert_tail(&eedev.eedev_list, hdl);
list_insert_tail(&dip->ed_devs, hdl);
}
hdl->eh_flags |= EEDEV_F_USABLE;
if (eedev.eedev_dip != NULL) {
if (!eedev_minor_create(hdl)) {
list_remove(&eedev.eedev_list, hdl);
list_remove(&dip->ed_devs, hdl);
eedev_free(hdl);
mutex_exit(&eedev.eedev_mutex);
return (ENXIO);
}
}
mutex_exit(&eedev.eedev_mutex);
*hdlp = hdl;
return (0);
}
static int
eedev_open(dev_t *devp, int flag, int otyp, cred_t *credp)
{
if (drv_priv(credp) != 0)
return (EPERM);
if (otyp != OTYP_CHR)
return (ENOTSUP);
if ((flag & (FNDELAY | FNONBLOCK | FEXCL)) != 0)
return (EINVAL);
if ((flag & (FREAD | FWRITE)) == 0)
return (EINVAL);
return (eedev_hold_by_id(*devp));
}
static int
eedev_read(dev_t dev, struct uio *uio, cred_t *credp)
{
uint32_t page, off, nbytes, end;
eedev_hdl_t *hdl = eedev_lookup_by_id(dev);
if (hdl == NULL)
return (ENXIO);
if ((uio->uio_fmode & FREAD) == 0)
return (EBADF);
if ((uio->uio_fmode & (FNONBLOCK | FNDELAY)) != 0)
return (EINVAL);
if ((uio->uio_offset % hdl->eh_read_gran) != 0 ||
(uio->uio_resid % hdl->eh_read_gran) != 0) {
return (EINVAL);
}
if (uio->uio_offset >= hdl->eh_size || uio->uio_resid == 0) {
return (0);
}
if (hdl->eh_seg != 0) {
page = uio->uio_offset / hdl->eh_seg;
off = uio->uio_offset % hdl->eh_seg;
end = (page + 1) * hdl->eh_seg;
} else {
page = 0;
off = uio->uio_offset;
end = hdl->eh_size;
}
nbytes = MIN(uio->uio_resid, end - uio->uio_offset);
nbytes = MIN(nbytes, hdl->eh_max_read);
return (hdl->eh_ops->eo_read(hdl->eh_driver, uio, page, off, nbytes));
}
static int
eedev_write(dev_t dev, struct uio *uio, cred_t *credp)
{
uint32_t page, off, nbytes, end;
eedev_hdl_t *hdl = eedev_lookup_by_id(dev);
if (hdl == NULL)
return (ENXIO);
if ((uio->uio_fmode & FWRITE) == 0)
return (EBADF);
if ((uio->uio_fmode & (FNONBLOCK | FNDELAY)) != 0)
return (EINVAL);
if ((uio->uio_offset % hdl->eh_write_gran) != 0 ||
(uio->uio_resid % hdl->eh_write_gran) != 0) {
return (EINVAL);
}
if (uio->uio_offset >= hdl->eh_size || uio->uio_resid <= 0) {
return (EINVAL);
}
if (hdl->eh_seg != 0) {
page = uio->uio_offset / hdl->eh_seg;
off = uio->uio_offset % hdl->eh_seg;
end = (page + 1) * hdl->eh_seg;
} else {
page = 0;
off = uio->uio_offset;
end = hdl->eh_size;
}
nbytes = MIN(uio->uio_resid, end - uio->uio_offset);
nbytes = MIN(nbytes, hdl->eh_max_write);
return (hdl->eh_ops->eo_write(hdl->eh_driver, uio, page, off, nbytes));
}
static int
eedev_close(dev_t dev, int flag, int otyp, cred_t *credp)
{
eedev_hdl_t *hdl;
if (otyp != OTYP_CHR)
return (EINVAL);
hdl = eedev_lookup_by_id(dev);
if (hdl == NULL)
return (ENXIO);
mutex_enter(&eedev.eedev_mutex);
VERIFY0(hdl->eh_flags & EEDEV_F_BUSY);
VERIFY3U(hdl->eh_flags & EEDEV_F_HELD, !=, 0);
VERIFY3U(hdl->eh_flags & EEDEV_F_USABLE, !=, 0);
hdl->eh_flags &= ~EEDEV_F_HELD;
mutex_exit(&eedev.eedev_mutex);
ddi_release_devi(hdl->eh_dip->ed_dip);
return (0);
}
static struct cb_ops eedev_cb_ops = {
.cb_open = eedev_open,
.cb_close = eedev_close,
.cb_strategy = nodev,
.cb_print = nodev,
.cb_dump = nodev,
.cb_read = eedev_read,
.cb_write = eedev_write,
.cb_ioctl = nodev,
.cb_devmap = nodev,
.cb_mmap = nodev,
.cb_segmap = nodev,
.cb_chpoll = nochpoll,
.cb_prop_op = ddi_prop_op,
.cb_flag = D_MP,
.cb_rev = CB_REV,
.cb_aread = nodev,
.cb_awrite = nodev
};
static int
eedev_attach(dev_info_t *dip, ddi_attach_cmd_t cmd)
{
if (cmd == DDI_RESUME) {
return (DDI_SUCCESS);
} else if (cmd != DDI_ATTACH) {
return (DDI_FAILURE);
}
if (ddi_get_instance(dip) != 0) {
dev_err(dip, CE_WARN, "only a single instance of eedev is "
"supported");
return (DDI_FAILURE);
}
mutex_enter(&eedev.eedev_mutex);
VERIFY3P(eedev.eedev_dip, ==, NULL);
eedev.eedev_dip = dip;
for (eedev_hdl_t *h = list_head(&eedev.eedev_list); h != NULL;
h = list_next(&eedev.eedev_list, h)) {
eedev_flags_t need = EEDEV_F_MINOR_PROPS | EEDEV_F_MINOR_VALID;
if ((h->eh_flags & need) != need) {
(void) eedev_minor_create(h);
}
}
mutex_exit(&eedev.eedev_mutex);
return (DDI_SUCCESS);
}
static int
eedev_getinfo(dev_info_t *dip, ddi_info_cmd_t cmd, void *arg, void **outp)
{
switch (cmd) {
case DDI_INFO_DEVT2DEVINFO:
VERIFY3P(eedev.eedev_dip, !=, NULL);
*outp = eedev.eedev_dip;
break;
case DDI_INFO_DEVT2INSTANCE:
VERIFY3P(eedev.eedev_dip, !=, NULL);
*outp = eedev.eedev_dip;
*outp = (void *)(uintptr_t)ddi_get_instance(eedev.eedev_dip);
break;
default:
return (DDI_FAILURE);
}
return (DDI_SUCCESS);
}
static int
eedev_detach(dev_info_t *dip, ddi_detach_cmd_t cmd)
{
if (cmd == DDI_SUSPEND) {
return (DDI_FAILURE);
} else if (cmd != DDI_DETACH) {
return (DDI_FAILURE);
}
VERIFY3P(dip, ==, eedev.eedev_dip);
mutex_enter(&eedev.eedev_mutex);
if (list_is_empty(&eedev.eedev_list)) {
mutex_exit(&eedev.eedev_mutex);
return (DDI_FAILURE);
}
ddi_remove_minor_node(eedev.eedev_dip, NULL);
eedev.eedev_dip = NULL;
mutex_exit(&eedev.eedev_mutex);
return (DDI_SUCCESS);
}
static struct dev_ops eedev_dev_ops = {
.devo_rev = DEVO_REV,
.devo_refcnt = 0,
.devo_getinfo = eedev_getinfo,
.devo_identify = nulldev,
.devo_probe = nulldev,
.devo_attach = eedev_attach,
.devo_detach = eedev_detach,
.devo_reset = nodev,
.devo_quiesce = ddi_quiesce_not_needed,
.devo_cb_ops = &eedev_cb_ops
};
static struct modldrv eedev_modldrv = {
.drv_modops = &mod_driverops,
.drv_linkinfo = "EEPROM support module",
.drv_dev_ops = &eedev_dev_ops
};
static struct modlinkage eedev_modlinkage = {
.ml_rev = MODREV_1,
.ml_linkage = { &eedev_modldrv, NULL }
};
static int
eedev_mod_init(void)
{
eedev.eedev_idspace = id_space_create("eedev_minors", EEDEV_MINOR_MIN,
EEDEV_MINOR_MAX);
if (eedev.eedev_idspace == NULL) {
return (ENOMEM);
}
mutex_init(&eedev.eedev_mutex, NULL, MUTEX_DRIVER, NULL);
list_create(&eedev.eedev_list, sizeof (eedev_hdl_t),
offsetof(eedev_hdl_t, eh_link));
list_create(&eedev.eedev_dips, sizeof (eedev_dip_t),
offsetof(eedev_dip_t, ed_link));
return (0);
}
static void
eedev_mod_fini(void)
{
list_destroy(&eedev.eedev_dips);
list_destroy(&eedev.eedev_list);
mutex_destroy(&eedev.eedev_mutex);
id_space_destroy(eedev.eedev_idspace);
}
int
_init(void)
{
int ret;
if ((ret = eedev_mod_init()) != 0) {
return (ret);
}
if ((ret = mod_install(&eedev_modlinkage)) != 0) {
eedev_mod_fini();
}
return (ret);
}
int
_info(struct modinfo *modinfop)
{
return (mod_info(&eedev_modlinkage, modinfop));
}
int
_fini(void)
{
int ret;
if ((ret = mod_remove(&eedev_modlinkage)) == 0) {
eedev_mod_fini();
}
return (ret);
}