#include <sys/param.h>
#include <sys/systm.h>
#include <sys/uio.h>
#include <sys/kernel.h>
#include <sys/proc.h>
#include <sys/buf.h>
#include <sys/conf.h>
#include <sys/event.h>
#include <sys/vnode.h>
#include <sys/malloc.h>
#include <sys/objcache.h>
#include <sys/ctype.h>
#include <sys/syslog.h>
#include <sys/udev.h>
#include <sys/devfs.h>
#include <libprop/proplib.h>
MALLOC_DEFINE(M_UDEV, "udev", "udev allocs");
static struct objcache *udev_event_kernel_cache;
static cdev_t udev_dev;
static d_open_t udev_dev_open;
static d_close_t udev_dev_close;
static d_read_t udev_dev_read;
static d_kqfilter_t udev_dev_kqfilter;
static d_ioctl_t udev_dev_ioctl;
static d_clone_t udev_dev_clone;
struct udev_prop_ctx {
prop_array_t cdevs;
int error;
};
struct udev_event_kernel {
struct udev_event ev;
TAILQ_ENTRY(udev_event_kernel) link;
};
struct udev_softc {
TAILQ_ENTRY(udev_softc) entry;
int opened;
int initiated;
int unit;
cdev_t dev;
struct udev_event_kernel marker;
};
struct cmd_function {
const char *cmd;
int (*fn)(struct udev_softc *, struct plistref *,
u_long, prop_dictionary_t);
};
static int _udev_dict_set_cstr(prop_dictionary_t, const char *, char *);
static int _udev_dict_set_int(prop_dictionary_t, const char *, int64_t);
static int _udev_dict_set_uint(prop_dictionary_t, const char *, uint64_t);
static int _udev_dict_delete_key(prop_dictionary_t, const char *);
static prop_dictionary_t udev_init_dict_event(cdev_t, const char *);
static int udev_init_dict(cdev_t);
static int udev_destroy_dict(cdev_t);
static void udev_event_insert(int, prop_dictionary_t);
static void udev_clean_events_locked(void);
static char *udev_event_externalize(struct udev_event_kernel *);
static void udev_getdevs_scan_callback(char *, cdev_t, bool, void *);
static int udev_getdevs_ioctl(struct udev_softc *, struct plistref *,
u_long, prop_dictionary_t);
static void udev_dev_filter_detach(struct knote *);
static int udev_dev_filter_read(struct knote *, long);
static struct dev_ops udev_dev_ops = {
{ "udev", 0, 0 },
.d_open = udev_dev_open,
.d_close = udev_dev_close,
.d_read = udev_dev_read,
.d_kqfilter = udev_dev_kqfilter,
.d_ioctl = udev_dev_ioctl
};
static struct cmd_function cmd_fn[] = {
{ .cmd = "getdevs", .fn = udev_getdevs_ioctl},
{NULL, NULL}
};
DEVFS_DEFINE_CLONE_BITMAP(udev);
static TAILQ_HEAD(, udev_softc) udevq;
static TAILQ_HEAD(, udev_event_kernel) udev_evq;
static struct kqinfo udev_kq;
static struct lock udev_lk;
static int udev_evqlen;
static int udev_initiated_count;
static int udev_open_count;
static int udev_seqwait;
static int udev_seq;
static struct lock udev_dict_lk = LOCK_INITIALIZER("dict", 0, 0);
static prop_dictionary_t
udev_get_dict(cdev_t dev)
{
prop_dictionary_t udict;
lockmgr(&udev_dict_lk, LK_EXCLUSIVE|LK_CANRECURSE);
udict = dev->si_dict;
if (udict)
prop_object_retain(udict);
return udict;
}
static void
udev_put_dict(cdev_t dev, prop_dictionary_t udict)
{
if (udict)
prop_object_release(udict);
lockmgr(&udev_dict_lk, LK_RELEASE);
}
static int
_udev_dict_set_cstr(prop_dictionary_t dict, const char *key, char *str)
{
prop_string_t ps;
KKASSERT(dict != NULL);
ps = prop_string_create_cstring(str);
if (ps == NULL) {
return ENOMEM;
}
if (prop_dictionary_set(dict, key, ps) == false) {
prop_object_release(ps);
return ENOMEM;
}
prop_object_release(ps);
return 0;
}
static int
_udev_dict_set_int(prop_dictionary_t dict, const char *key, int64_t val)
{
prop_number_t pn;
KKASSERT(dict != NULL);
pn = prop_number_create_integer(val);
if (pn == NULL)
return ENOMEM;
if (prop_dictionary_set(dict, key, pn) == false) {
prop_object_release(pn);
return ENOMEM;
}
prop_object_release(pn);
return 0;
}
static int
_udev_dict_set_uint(prop_dictionary_t dict, const char *key, uint64_t val)
{
prop_number_t pn;
KKASSERT(dict != NULL);
pn = prop_number_create_unsigned_integer(val);
if (pn == NULL)
return ENOMEM;
if (prop_dictionary_set(dict, key, pn) == false) {
prop_object_release(pn);
return ENOMEM;
}
prop_object_release(pn);
return 0;
}
static int
_udev_dict_delete_key(prop_dictionary_t dict, const char *key)
{
KKASSERT(dict != NULL);
prop_dictionary_remove(dict, key);
return 0;
}
static prop_dictionary_t
udev_init_dict_event(cdev_t dev, const char *key)
{
prop_dictionary_t dict;
uint64_t kptr;
int error;
kptr = (uint64_t)(uintptr_t)dev;
KKASSERT(dev != NULL);
dict = prop_dictionary_create();
if (dict == NULL) {
log(LOG_DEBUG, "udev_init_dict_event: prop_dictionary_create() failed\n");
return NULL;
}
if ((error = _udev_dict_set_cstr(dict, "name", dev->si_name)))
goto error_out;
if ((error = _udev_dict_set_uint(dict, "devnum", dev->si_inode)))
goto error_out;
if ((error = _udev_dict_set_uint(dict, "devtype", (dev_dflags(dev) & D_TYPEMASK))))
goto error_out;
if ((error = _udev_dict_set_uint(dict, "kptr", kptr)))
goto error_out;
if ((error = _udev_dict_set_cstr(dict, "key", __DECONST(char *, key))))
goto error_out;
return dict;
error_out:
prop_object_release(dict);
return NULL;
}
int
udev_dict_set_cstr(cdev_t dev, const char *key, char *str)
{
prop_dictionary_t dict;
prop_dictionary_t udict;
int error;
KKASSERT(dev != NULL);
while ((udict = udev_get_dict(dev)) == NULL) {
error = udev_init_dict(dev);
udev_put_dict(dev, udict);
if (error)
return -1;
}
dict = udev_init_dict_event(dev, key);
if (dict == NULL) {
error = ENOMEM;
goto errout;
}
if ((error = _udev_dict_set_cstr(dict, "value", str))) {
prop_object_release(dict);
goto errout;
}
udev_event_insert(UDEV_EV_KEY_UPDATE, dict);
prop_object_release(dict);
error = _udev_dict_set_cstr(udict, key, str);
errout:
udev_put_dict(dev, udict);
return error;
}
int
udev_dict_set_int(cdev_t dev, const char *key, int64_t val)
{
prop_dictionary_t dict;
prop_dictionary_t udict;
int error;
KKASSERT(dev != NULL);
while ((udict = udev_get_dict(dev)) == NULL) {
error = udev_init_dict(dev);
udev_put_dict(dev, udict);
if (error)
return -1;
}
dict = udev_init_dict_event(dev, key);
if (dict == NULL) {
error = ENOMEM;
goto errout;
}
if ((error = _udev_dict_set_int(dict, "value", val))) {
prop_object_release(dict);
goto errout;
}
udev_event_insert(UDEV_EV_KEY_UPDATE, dict);
prop_object_release(dict);
error = _udev_dict_set_int(udict, key, val);
errout:
udev_put_dict(dev, udict);
return error;
}
int
udev_dict_set_uint(cdev_t dev, const char *key, uint64_t val)
{
prop_dictionary_t dict;
prop_dictionary_t udict;
int error;
KKASSERT(dev != NULL);
while ((udict = udev_get_dict(dev)) == NULL) {
error = udev_init_dict(dev);
udev_put_dict(dev, udict);
if (error)
return -1;
}
dict = udev_init_dict_event(dev, key);
if (dict == NULL) {
error = ENOMEM;
goto errout;
}
if ((error = _udev_dict_set_uint(dict, "value", val))) {
prop_object_release(dict);
goto errout;
}
udev_event_insert(UDEV_EV_KEY_UPDATE, dict);
prop_object_release(dict);
error = _udev_dict_set_uint(udict, key, val);
errout:
udev_put_dict(dev, udict);
return error;
}
int
udev_dict_delete_key(cdev_t dev, const char *key)
{
prop_dictionary_t dict;
prop_dictionary_t udict;
int error;
KKASSERT(dev != NULL);
udict = udev_get_dict(dev);
if (udict == NULL) {
error = ENOMEM;
goto errout;
}
dict = udev_init_dict_event(dev, key);
if (dict == NULL) {
error = ENOMEM;
goto errout;
}
udev_event_insert(UDEV_EV_KEY_REMOVE, dict);
prop_object_release(dict);
error = _udev_dict_delete_key(udict, key);
errout:
udev_put_dict(dev, udict);
return error;
}
static int
udev_init_dict(cdev_t dev)
{
prop_dictionary_t dict;
uint64_t kptr;
int error;
kptr = (uint64_t)(uintptr_t)dev;
KKASSERT(dev != NULL);
if (dev->si_dict != NULL) {
log(LOG_DEBUG,
"udev_init_dict: new dict for %s, but has "
"dict already (%p)!\n",
dev->si_name, dev->si_dict);
return 0;
}
dict = prop_dictionary_create();
if (dict == NULL) {
log(LOG_DEBUG,
"udev_init_dict: prop_dictionary_create() failed\n");
return ENOMEM;
}
if ((error = _udev_dict_set_cstr(dict, "name", dev->si_name)))
goto error_out;
if ((error = _udev_dict_set_uint(dict, "devnum", dev->si_inode)))
goto error_out;
if ((error = _udev_dict_set_uint(dict, "kptr", kptr)))
goto error_out;
if ((error = _udev_dict_set_uint(dict, "devtype",
(dev_dflags(dev) & D_TYPEMASK)))) {
goto error_out;
}
if ((error = _udev_dict_set_uint(dict, "uid", dev->si_uid)))
goto error_out;
if ((error = _udev_dict_set_uint(dict, "gid", dev->si_gid)))
goto error_out;
if ((error = _udev_dict_set_int(dict, "mode", dev->si_perms)))
goto error_out;
if ((error = _udev_dict_set_int(dict, "major", dev->si_umajor)))
goto error_out;
if ((error = _udev_dict_set_int(dict, "minor", dev->si_uminor)))
goto error_out;
if (dev->si_ops->head.name != NULL) {
if ((error = _udev_dict_set_cstr(dict, "driver",
__DECONST(char *, dev->si_ops->head.name))))
goto error_out;
}
dev->si_dict = dict;
return 0;
error_out:
dev->si_dict = NULL;
prop_object_release(dict);
return error;
}
static int
udev_destroy_dict(cdev_t dev)
{
prop_dictionary_t udict;
KKASSERT(dev != NULL);
udict = udev_get_dict(dev);
if (dev->si_dict) {
prop_object_release(dev->si_dict);
dev->si_dict = NULL;
}
udev_put_dict(dev, udict);
return 0;
}
static void
udev_event_insert(int ev_type, prop_dictionary_t dict)
{
struct udev_event_kernel *ev;
prop_dictionary_t dict_copy;
if (udev_initiated_count) {
dict_copy = prop_dictionary_copy(dict);
if (dict_copy == NULL)
return;
ev = objcache_get(udev_event_kernel_cache, M_WAITOK);
ev->ev.ev_dict = dict_copy;
ev->ev.ev_type = ev_type;
lockmgr(&udev_lk, LK_EXCLUSIVE);
TAILQ_INSERT_TAIL(&udev_evq, ev, link);
++udev_evqlen;
++udev_seq;
if (udev_seqwait)
wakeup(&udev_seqwait);
lockmgr(&udev_lk, LK_RELEASE);
wakeup(&udev_evq);
KNOTE(&udev_kq.ki_note, 0);
} else if (udev_open_count) {
lockmgr(&udev_lk, LK_EXCLUSIVE);
++udev_seq;
if (udev_seqwait)
wakeup(&udev_seqwait);
lockmgr(&udev_lk, LK_RELEASE);
KNOTE(&udev_kq.ki_note, 0);
}
}
static void
udev_clean_events_locked(void)
{
struct udev_event_kernel *ev;
while ((ev = TAILQ_FIRST(&udev_evq)) &&
ev->ev.ev_dict != NULL) {
TAILQ_REMOVE(&udev_evq, ev, link);
objcache_put(udev_event_kernel_cache, ev);
--udev_evqlen;
}
}
static char *
udev_event_externalize(struct udev_event_kernel *ev)
{
prop_dictionary_t dict;
char *xml;
int error;
dict = prop_dictionary_create();
if (dict == NULL) {
log(LOG_DEBUG,
"udev_event_externalize: prop_dictionary_create() failed\n");
return NULL;
}
if ((error = _udev_dict_set_int(dict, "evtype", ev->ev.ev_type))) {
prop_object_release(dict);
return NULL;
}
if (prop_dictionary_set(dict, "evdict", ev->ev.ev_dict) == false) {
prop_object_release(dict);
return NULL;
}
prop_object_release(ev->ev.ev_dict);
xml = prop_dictionary_externalize(dict);
prop_object_release(dict);
return xml;
}
int
udev_event_attach(cdev_t dev, char *name, int alias)
{
prop_dictionary_t dict;
prop_dictionary_t udict;
int error;
KKASSERT(dev != NULL);
error = ENOMEM;
udict = udev_get_dict(dev);
if (alias) {
if (udict == NULL)
goto error_out;
dict = prop_dictionary_copy(udict);
if (dict == NULL)
goto error_out;
if ((error = _udev_dict_set_cstr(dict, "name", name))) {
prop_object_release(dict);
goto error_out;
}
_udev_dict_set_int(dict, "alias", 1);
udev_event_insert(UDEV_EVENT_ATTACH, dict);
prop_object_release(dict);
} else {
while (udict == NULL) {
error = udev_init_dict(dev);
if (error)
goto error_out;
udev_put_dict(dev, udict);
udict = udev_get_dict(dev);
}
_udev_dict_set_int(udict, "alias", 0);
udev_event_insert(UDEV_EVENT_ATTACH, udict);
}
error_out:
udev_put_dict(dev, udict);
return error;
}
int
udev_event_detach(cdev_t dev, char *name, int alias)
{
prop_dictionary_t dict;
prop_dictionary_t udict;
KKASSERT(dev != NULL);
udict = udev_get_dict(dev);
if (alias) {
dict = prop_dictionary_copy(udict);
if (dict == NULL)
goto error_out;
if (_udev_dict_set_cstr(dict, "name", name)) {
prop_object_release(dict);
goto error_out;
}
_udev_dict_set_int(dict, "alias", 1);
udev_event_insert(UDEV_EVENT_DETACH, dict);
prop_object_release(dict);
} else {
if (udict)
udev_event_insert(UDEV_EVENT_DETACH, udict);
}
error_out:
udev_destroy_dict(dev);
udev_put_dict(dev, udict);
return 0;
}
static int
udev_dev_clone(struct dev_clone_args *ap)
{
struct udev_softc *softc;
int unit;
unit = devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(udev), 1000);
if (unit < 0) {
ap->a_dev = NULL;
return 1;
}
softc = kmalloc(sizeof(*softc), M_UDEV, M_WAITOK | M_ZERO);
softc->unit = unit;
lockmgr(&udev_lk, LK_EXCLUSIVE);
TAILQ_INSERT_TAIL(&udevq, softc, entry);
lockmgr(&udev_lk, LK_RELEASE);
softc->dev = make_only_dev(&udev_dev_ops, unit, ap->a_cred->cr_ruid,
0, 0600, "udevs/%d", unit);
softc->dev->si_drv1 = softc;
ap->a_dev = softc->dev;
return 0;
}
static int
udev_dev_open(struct dev_open_args *ap)
{
struct udev_softc *softc = ap->a_head.a_dev->si_drv1;
lockmgr(&udev_lk, LK_EXCLUSIVE);
if (softc == NULL || softc->opened) {
lockmgr(&udev_lk, LK_RELEASE);
return EBUSY;
}
softc->opened = 1;
++udev_open_count;
lockmgr(&udev_lk, LK_RELEASE);
return 0;
}
static int
udev_dev_close(struct dev_close_args *ap)
{
struct udev_softc *softc = ap->a_head.a_dev->si_drv1;
KKASSERT(softc->dev == ap->a_head.a_dev);
KKASSERT(softc->opened == 1);
destroy_dev(ap->a_head.a_dev);
lockmgr(&udev_lk, LK_EXCLUSIVE);
TAILQ_REMOVE(&udevq, softc, entry);
if (softc->initiated) {
TAILQ_REMOVE(&udev_evq, &softc->marker, link);
softc->initiated = 0;
--udev_initiated_count;
udev_clean_events_locked();
}
softc->opened = 0;
softc->dev = NULL;
ap->a_head.a_dev->si_drv1 = NULL;
--udev_open_count;
lockmgr(&udev_lk, LK_RELEASE);
devfs_clone_bitmap_put(&DEVFS_CLONE_BITMAP(udev), softc->unit);
wakeup(&udev_evq);
kfree(softc, M_UDEV);
return 0;
}
static struct filterops udev_dev_read_filtops =
{ FILTEROP_ISFD | FILTEROP_MPSAFE, NULL,
udev_dev_filter_detach, udev_dev_filter_read };
static int
udev_dev_kqfilter(struct dev_kqfilter_args *ap)
{
struct udev_softc *softc = ap->a_head.a_dev->si_drv1;
struct knote *kn = ap->a_kn;
struct klist *klist;
ap->a_result = 0;
lockmgr(&udev_lk, LK_EXCLUSIVE);
switch (kn->kn_filter) {
case EVFILT_READ:
kn->kn_fop = &udev_dev_read_filtops;
kn->kn_hook = (caddr_t)softc;
break;
default:
ap->a_result = EOPNOTSUPP;
lockmgr(&udev_lk, LK_RELEASE);
return (0);
}
klist = &udev_kq.ki_note;
knote_insert(klist, kn);
lockmgr(&udev_lk, LK_RELEASE);
return (0);
}
static void
udev_dev_filter_detach(struct knote *kn)
{
struct klist *klist;
lockmgr(&udev_lk, LK_EXCLUSIVE);
klist = &udev_kq.ki_note;
knote_remove(klist, kn);
lockmgr(&udev_lk, LK_RELEASE);
}
static int
udev_dev_filter_read(struct knote *kn, long hint)
{
struct udev_softc *softc = (void *)kn->kn_hook;
struct udev_event_kernel *ev;
int ready = 0;
lockmgr(&udev_lk, LK_EXCLUSIVE);
if (softc->initiated) {
ev = TAILQ_NEXT(&softc->marker, link);
while (ev && ev->ev.ev_dict == NULL)
ev = TAILQ_NEXT(ev, link);
if (ev)
ready = 1;
}
lockmgr(&udev_lk, LK_RELEASE);
return (ready);
}
static int
udev_dev_read(struct dev_read_args *ap)
{
struct udev_softc *softc = ap->a_head.a_dev->si_drv1;
struct udev_event_kernel *ev;
struct uio *uio = ap->a_uio;
char *xml;
size_t len;
int error;
lockmgr(&udev_lk, LK_EXCLUSIVE);
if (softc->initiated == 0) {
softc->initiated = 1;
++udev_initiated_count;
TAILQ_INSERT_HEAD(&udev_evq, &softc->marker, link);
}
error = 0;
for (;;) {
if (softc->initiated) {
ev = TAILQ_NEXT(&softc->marker, link);
while (ev && ev->ev.ev_dict == NULL)
ev = TAILQ_NEXT(ev, link);
if (ev) {
if ((xml = udev_event_externalize(ev)) == NULL) {
error = ENOMEM;
break;
}
len = strlen(xml) + 1;
if (uio->uio_resid < len)
error = ENOMEM;
else
error = uiomove((caddr_t)xml, len, uio);
kfree(xml, M_TEMP);
TAILQ_REMOVE(&udev_evq, &softc->marker, link);
TAILQ_INSERT_AFTER(&udev_evq,
ev, &softc->marker, link);
udev_clean_events_locked();
break;
}
}
if (ap->a_ioflag & IO_NDELAY) {
error = EWOULDBLOCK;
break;
}
if ((error = lksleep(&udev_evq, &udev_lk, PCATCH, "udevq", 0)))
break;
}
lockmgr(&udev_lk, LK_RELEASE);
return error;
}
static int
udev_dev_ioctl(struct dev_ioctl_args *ap)
{
struct udev_softc *softc = ap->a_head.a_dev->si_drv1;
prop_dictionary_t dict;
prop_object_t po;
prop_string_t ps;
struct plistref *pref;
int i, error;
int seq;
error = 0;
switch(ap->a_cmd) {
case UDEVPROP:
pref = (struct plistref *)ap->a_data;
error = prop_dictionary_copyin_ioctl(pref, ap->a_cmd, &dict);
if (error)
return error;
po = prop_dictionary_get(dict, "command");
if (po == NULL || prop_object_type(po) != PROP_TYPE_STRING) {
log(LOG_DEBUG, "udev: prop_dictionary_get() failed\n");
prop_object_release(dict);
return EINVAL;
}
ps = po;
for(i = 0; cmd_fn[i].cmd != NULL; i++) {
if (prop_string_equals_cstring(ps, cmd_fn[i].cmd))
break;
}
if (cmd_fn[i].cmd != NULL) {
error = cmd_fn[i].fn(softc, pref, ap->a_cmd, dict);
} else {
error = EINVAL;
}
prop_object_release(dict);
break;
case UDEVWAIT:
lockmgr(&udev_lk, LK_EXCLUSIVE);
seq = *(int *)ap->a_data;
++udev_seqwait;
while (seq == udev_seq) {
error = lksleep(&udev_seqwait, &udev_lk,
PCATCH, "udevw", 0);
if (error)
break;
}
--udev_seqwait;
*(int *)ap->a_data = udev_seq;
lockmgr(&udev_lk, LK_RELEASE);
break;
default:
error = ENOTTY;
break;
}
return(error);
}
static void
udev_getdevs_scan_callback(char *name, cdev_t cdev, bool is_alias, void *arg)
{
struct udev_prop_ctx *ctx = arg;
KKASSERT(arg != NULL);
if (cdev->si_dict == NULL)
return;
if (prop_array_add(ctx->cdevs, cdev->si_dict) == false) {
ctx->error = EINVAL;
return;
}
}
static int
udev_getdevs_ioctl(struct udev_softc *softc, struct plistref *pref,
u_long cmd, prop_dictionary_t dict)
{
prop_dictionary_t odict;
struct udev_prop_ctx ctx;
int error;
lockmgr(&udev_lk, LK_EXCLUSIVE);
if (softc->initiated == 0) {
softc->initiated = 1;
++udev_initiated_count;
TAILQ_INSERT_HEAD(&udev_evq, &softc->marker, link);
}
lockmgr(&udev_lk, LK_RELEASE);
ctx.error = 0;
ctx.cdevs = prop_array_create();
if (ctx.cdevs == NULL) {
log(LOG_DEBUG,
"udev_getdevs_ioctl: prop_array_create() failed\n");
return EINVAL;
}
devfs_scan_callback(udev_getdevs_scan_callback, &ctx);
if (ctx.error != 0) {
prop_object_release(ctx.cdevs);
return (ctx.error);
}
odict = prop_dictionary_create();
if (odict == NULL) {
return ENOMEM;
}
if ((prop_dictionary_set(odict, "array", ctx.cdevs)) == 0) {
log(LOG_DEBUG,
"udev_getdevs_ioctl: prop_dictionary_set failed\n");
prop_object_release(odict);
return ENOMEM;
}
error = prop_dictionary_copyout_ioctl(pref, cmd, odict);
prop_object_release(odict);
return error;
}
static void
udev_init(void)
{
lockinit(&udev_lk, "udevlk", 0, LK_CANRECURSE);
TAILQ_INIT(&udevq);
TAILQ_INIT(&udev_evq);
udev_event_kernel_cache = objcache_create_simple(M_UDEV, sizeof(struct udev_event_kernel));
}
static void
udev_uninit(void)
{
objcache_destroy(udev_event_kernel_cache);
}
static void
udev_dev_init(void)
{
udev_dev = make_autoclone_dev(&udev_dev_ops, &DEVFS_CLONE_BITMAP(udev),
udev_dev_clone,
UID_ROOT, GID_WHEEL, 0600, "udev");
}
static void
udev_dev_uninit(void)
{
destroy_dev(udev_dev);
}
SYSINIT(subr_udev_register, SI_SUB_CREATE_INIT, SI_ORDER_ANY,
udev_init, NULL);
SYSUNINIT(subr_udev_register, SI_SUB_CREATE_INIT, SI_ORDER_ANY,
udev_uninit, NULL);
SYSINIT(subr_udev_dev_register, SI_SUB_DRIVERS, SI_ORDER_ANY,
udev_dev_init, NULL);
SYSUNINIT(subr_udev_dev_register, SI_SUB_DRIVERS, SI_ORDER_ANY,
udev_dev_uninit, NULL);