modctl.c revision 7c77441ede3f5919f2a903e9fd35efbd1fe6fd68
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2005 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#pragma ident "%Z%%M% %I% %E% SMI"
/*
* modctl system call for loadable module support.
*/
#include <sys/ndi_impldefs.h>
#include <sys/ddi_impldefs.h>
#include <sys/ddi_implfuncs.h>
#include <sys/bootconf.h>
#include <sys/devpolicy.h>
#include <sys/autoconf.h>
#include <sys/sysmacros.h>
#include <sys/sysevent.h>
#include <sys/sysevent_impl.h>
#include <sys/instance.h>
#include <sys/modhash_impl.h>
#include <sys/dacf_impl.h>
#include <sys/pathname.h>
#include <ipp/ipp_impl.h>
static int mod_circdep(struct modctl *);
static void mod_uninstall_all(void);
static struct modctl *allocate_modp(char *, char *);
static void mod_unload(struct modctl *);
static int modinstall(struct modctl *);
static int moduninstall(struct modctl *);
static struct modctl *mod_hold_installed_mod(char *, int, int *);
static void mod_release(struct modctl *);
static int mod_install_requisites(struct modctl *);
static void check_esc_sequences(char *, char *);
/*
* module loading thread control structure. Calls to kobj_load_module()() are
* handled off to a separate thead using this structure.
*/
struct loadmt {
int usepath;
int retval;
};
static void modload_thread(struct loadmt *);
/* and the uninstall daemon. */
/* mod_busy, mod_want, and mod_ref. */
/* blocking operations while holding */
/* mod_lock should be avoided */
int isminiroot; /* set if running as miniroot */
int modrootloaded; /* set after root driver and fs are loaded */
int swaploaded; /* set after swap driver and fs are loaded */
int bop_io_quiesced = 0; /* set when BOP I/O can no longer be used */
int last_module_id;
struct devnames orphanlist;
#define MAJBINDFILE "/etc/name_to_major"
#define SYSBINDFILE "/etc/name_to_sysnum"
static char majbind[] = MAJBINDFILE;
static char sysbind[] = SYSBINDFILE;
extern int obpdebug;
extern int make_mbind(char *, int, char *, struct bind **);
static int minorperm_loaded = 0;
void
mod_setup(void)
{
int num_devs;
int i;
/*
* Initialize the list of loaded driver dev_ops.
* XXX - This must be done before reading the system file so that
* forceloads of drivers will work.
*/
/*
* Since read_binding_file is common code, it doesn't enforce that all
* of the binding file entries have major numbers <= MAXMAJ32. Thus,
* ensure that we don't allocate some massive amount of space due to a
* bad entry. We can't have major numbers bigger than MAXMAJ32
* until file system support for larger major numbers exists.
*/
/*
* Leave space for expansion, but not more than L_MAXMAJ32
*/
for (i = 0; i < devcnt; i++)
devopsp[i] = &mod_nodev_ops;
/*
* Sync up with the work that the stand-alone linker has already done.
*/
(void) kobj_sync();
/*
* Initialize streams device implementation structures.
*/
/*
* If the cl_bootstrap module is present,
* we should be configured as a cluster. Loading this module
* will set "cluster_bootflags" to non-zero.
*/
/*
* Start up dynamic autoconfiguration framework (dacf).
*/
dacf_init();
/*
* Start up IP policy framework (ipp).
*/
ipp_init();
/*
* Allocate loadable native system call locks.
*/
if (LOADABLE_SYSCALL(callp)) {
} else {
}
#ifdef DEBUG
} else {
/*
* Do some sanity checks on the sysent table
*/
case SE_32RVAL1:
/* only r_val1 returned */
case SE_32RVAL1 | SE_32RVAL2:
/* r_val1 and r_val2 returned */
case SE_64RVAL:
/* 64-bit rval returned */
break;
default:
}
#endif
}
}
#ifdef _SYSCALL32_IMPL
/*
* Allocate loadable system call locks for 32-bit compat syscalls
*/
if (LOADABLE_SYSCALL(callp)) {
} else {
}
#ifdef DEBUG
} else {
/*
* Do some sanity checks on the sysent table
*/
case SE_32RVAL1:
/* only r_val1 returned */
case SE_32RVAL1 | SE_32RVAL2:
/* r_val1 and r_val2 returned */
case SE_64RVAL:
/* 64-bit rval returned */
break;
default:
goto skip;
}
/*
* Cross-check the native and compatibility tables.
*/
continue;
/*
* If only one or the other slot is loadable, then
* there's an error -- they should match!
*/
callnum);
}
/*
* This is more of a heuristic test -- if the
* system call returns two values in the 32-bit
* world, it should probably return two 32-bit
* values in the 64-bit world too.
*/
callnum);
}
skip:;
#endif /* DEBUG */
}
}
#endif /* _SYSCALL32_IMPL */
/*
* Allocate loadable exec locks. (Assumes all execs are loadable)
*/
}
/* init thread specific structure for mod_uninstall_all */
}
static int
{
int retval = 0;
char *filenamep;
int modid;
goto out;
}
goto out;
out:
return (retval);
}
static int
{
int rval = 0;
if (id == 0) {
#ifdef DEBUG
/*
* Turn on mod_uninstall_daemon
*/
if (mod_uninstall_interval == 0) {
mod_uninstall_interval = 60;
modreap();
return (rval);
}
#endif
} else {
}
return (rval);
}
static int
{
int retval;
#if defined(_SYSCALL32_IMPL)
int nobase;
#endif
if (get_udatamodel() == DATAMODEL_NATIVE) {
return (EFAULT);
}
#ifdef _SYSCALL32_IMPL
else {
return (EFAULT);
}
#endif
/*
* This flag is -only- for the kernels use.
*/
if (retval)
return (retval);
if (get_udatamodel() == DATAMODEL_NATIVE) {
#ifdef _SYSCALL32_IMPL
} else {
int i;
return (EOVERFLOW);
for (i = 0; i < MODMAXLINK32; i++) {
}
#endif
}
return (retval);
}
/*
* Return the last major number in the range of permissible major numbers.
*/
/*ARGSUSED*/
static int
{
return (EFAULT);
return (0);
}
static int
modctl_add_major(int *data)
{
int i, rv;
char name[MAXMODCONFNAME];
char cname[MAXMODCONFNAME];
char *drvname;
if (get_udatamodel() == DATAMODEL_NATIVE) {
return (EFAULT);
}
#ifdef _SYSCALL32_IMPL
else {
struct modconfig32 modc32;
return (EFAULT);
else {
}
}
#endif
/*
* If the driver is already in the mb_hashtab, and the name given
* doesn't match that driver's name, fail. Otherwise, pass, since
* we may be adding aliases.
*/
return (EINVAL);
/*
* Add each supplied driver alias to mb_hashtab
*/
for (i = 0; i < mc.num_aliases; i++) {
if (get_udatamodel() == DATAMODEL_NATIVE) {
return (EFAULT);
return (EINVAL);
return (EFAULT);
return (EINVAL);
}
#ifdef _SYSCALL32_IMPL
else {
return (EFAULT);
return (EINVAL);
return (EFAULT);
return (EINVAL);
}
#endif
}
/*
* Try to establish an mbinding for mc.drvname, and add it to devnames.
* Add class if any after establishing the major number
*/
if (rv == 0) {
}
return (rv);
}
static int
{
return (EINVAL);
/* mark devnames as removed */
return (EINVAL);
}
(void) i_ddi_unload_drvconf(major);
return (0);
}
static struct vfs *
path_to_vfs(char *name)
{
return (NULL);
return (vfsp);
}
static int
{
static int n_modpath = 0;
static char *modpath_copy;
static struct pathvfs {
char *path;
} *pathvfs;
int i, new_vfs = 0;
if (n_modpath != 0) {
for (i = 0; i < n_modpath; i++) {
if (vfsp)
new_vfs = 1;
}
}
return (new_vfs);
}
/*
* First call, initialize the pathvfs structure
*/
tmp = modpath_copy;
n_modpath = 1;
while (tmp1) {
*tmp1 = '\0';
n_modpath++;
}
tmp = modpath_copy;
for (i = 0; i < n_modpath; i++) {
}
return (1); /* always reread driver.conf the first time */
}
{
int ret;
if (ret == 0)
return (ret);
}
/*
* We are invoked to rescan new driver.conf files. It is
* only necessary if a new file system was mounted in the
* module_path. Because rescanning driver.conf files can
* take some time on older platforms (sun4m), the following
* code skips unnecessary driver.conf rescans to optimize
* boot performance.
*/
if (new_vfs_in_modpath()) {
/*
* If we are still initializing io subsystem,
* load drivers with ddi-forceattach property
*/
if (!i_ddi_io_initialized())
}
return (0);
}
static int
{
int ret;
return (EINVAL);
if (ret != 0)
return (ret);
(void) i_ddi_unbind_devs(major);
return (0);
}
static void
{
int i;
char *p;
if (*str != '\\') {
} else {
p = str + 1;
/*
* we only handle octal escape sequences for SPACE
*/
if (*p++ == '0' && *p++ == '4' && *p == '0') {
*cstr = ' ';
str += 3;
} else {
}
}
}
*cstr = 0;
}
static int
modctl_getmodpathlen(int *data)
{
int len;
return (EFAULT);
return (0);
}
static int
modctl_getmodpath(char *data)
{
return (EFAULT);
return (0);
}
static int
{
return (0);
}
static int
{
char name[256];
int retval;
return (retval);
return (ENODEV);
return (EFAULT);
return (0);
}
static int
{
char *name;
return (EFAULT);
return (ENODEV);
return (ENOSPC);
}
static int
{
int instance;
return (EINVAL);
}
/*
* Return the sizeof of the device id.
*/
static int
{
/* get device id */
return (EINVAL);
/* copyout device id size */
return (EFAULT);
return (0);
}
/*
* Return a copy of the device id.
*/
static int
{
int err = 0;
/* get device id */
return (EINVAL);
/* Error if device id is larger than space allocated */
return (ENOSPC);
}
/* copy out device id */
return (err);
}
/*
* return the /devices paths associated with the specified devid and
* minor name.
*/
/*ARGSUSED*/
static int
{
int devid_len;
char *minor_name = NULL;
struct ddi_minor_data *dmdp;
int ulens;
int lens;
int len;
int ndevs;
int i;
int ret = 0;
/*
* If upaths is NULL then we are only computing the amount of space
* needed to hold the paths and returning the value in *ulensp. If we
* are copying out paths then we get the amount of space allocated by
* the caller. If the actual space needed for paths is larger, or
* things are changing out from under us, then we return EAGAIN.
*/
if (upaths) {
return (EINVAL);
return (EFAULT);
}
/*
* copyin enough of the devid to determine the length then
* reallocate and copy in the entire devid.
*/
goto out;
}
goto out;
}
/* copyin the minor name if specified. */
if ((minor_name != DEVID_MINOR_NAME_ALL) &&
(minor_name != DEVID_MINOR_NAME_ALL_CHR) &&
(minor_name != DEVID_MINOR_NAME_ALL_BLK)) {
goto out;
}
}
/*
* Use existing function to resolve the devid into a devlist.
*
* NOTE: there is a loss of spectype information in the current
* ddi_lyr_devid_to_devlist implementation. We work around this by not
* passing down DEVID_MINOR_NAME_ALL here, but reproducing all minor
* node forms in the loop processing the devlist below. It would be
* best if at some point the use of this interface here was replaced
* with a path oriented call.
*/
(minor_name == DEVID_MINOR_NAME_ALL) ?
goto out;
}
/*
* loop over the devlist, converting each devt to a path and doing
* a copyout of the path and computation of the amount of space
* needed to hold all the paths
*/
/* find the dip associated with the dev_t */
continue;
/* loop over all the minor nodes, skipping ones we don't want */
continue;
if ((minor_name != DEVID_MINOR_NAME_ALL) &&
(minor_name != DEVID_MINOR_NAME_ALL_CHR) &&
(minor_name != DEVID_MINOR_NAME_ALL_BLK) &&
continue;
else {
if ((minor_name == DEVID_MINOR_NAME_ALL_CHR) &&
continue;
if ((minor_name == DEVID_MINOR_NAME_ALL_BLK) &&
continue;
}
/* XXX need ddi_pathname_minor(dmdp, path); interface */
path) != DDI_SUCCESS) {
goto out;
}
/* copyout the path with double terminations */
if (upaths) {
goto out;
}
goto out;
}
}
}
}
lens++; /* add one for double termination */
/* copy out the amount of space needed to hold the paths */
goto out;
}
ret = 0;
if (path)
if (devlist)
if (minor_name &&
(minor_name != DEVID_MINOR_NAME_ALL) &&
(minor_name != DEVID_MINOR_NAME_ALL_CHR) &&
if (devid)
return (ret);
}
/*
* Return the size of the minor name.
*/
static int
{
char *name;
/* get the minor name */
return (EINVAL);
/* copy out the size of the minor name */
return (EFAULT);
return (0);
}
/*
* Return the minor name.
*/
static int
{
char *name;
int err = 0;
/* get the minor name */
return (EINVAL);
/* Error if the minor name is larger than the space allocated */
return (ENOSPC);
}
/* copy out the minor name */
return (err);
}
/*
* Return the size of the devfspath name.
*/
static int
{
char *name;
/* get the path name */
return (EINVAL);
}
/* copy out the size of the path name */
return (EFAULT);
return (0);
}
/*
* Return the devfspath name.
*/
static int
{
char *name;
int err = 0;
/* get the path name */
return (EINVAL);
}
/* Error if the path name is larger than the space allocated */
return (ENOSPC);
}
/* copy out the path name */
return (err);
}
static int
modctl_get_fbname(char *path)
{
int rval = 0;
/* make sure fbdev is set before we plunge in */
return (ENODEV);
pathname)) == DDI_SUCCESS) {
}
}
return (rval);
}
/*
* modctl_reread_dacf()
* Reread the dacf rules database from the named binding file.
* If NULL is specified, pass along the NULL, it means 'use the default'.
*/
static int
modctl_reread_dacf(char *path)
{
int rval = 0;
} else {
goto out;
}
}
out:
return (rval);
}
/*ARGSUSED*/
static int
{
int error = 0;
char *filenamep;
switch (subcmd) {
case MODEVENTS_FLUSH:
/* flush all currently queued events */
break;
/*
* bind door_upcall to filename
* this should only be done once per invocation
* of the event daemon.
*/
} else {
}
break;
case MODEVENTS_GETDATA:
break;
case MODEVENTS_FREEDATA:
break;
case MODEVENTS_POST_EVENT:
(sysevent_id_t *)a4);
break;
case MODEVENTS_REGISTER_EVENT:
(se_pubsub_t *)a4);
break;
default:
}
return (error);
}
static void
{
int len;
if (mp->mp_minorname) {
}
}
#define MP_NO_DRV_ERR \
"/etc/minor_perm: no driver for %s\n"
#define MP_EMPTY_MINOR \
"/etc/minor_perm: empty minor name for driver %s\n"
#define MP_NO_MINOR \
"/etc/minor_perm: no minor matching %s for driver %s\n"
/*
* Remove mperm entry with matching minorname
*/
static void
{
if (*wildmp)
} else {
while (*mp_head) {
mp->mp_minorname) != 0) {
continue;
}
/* remove the entry */
break;
}
}
if (freemp) {
if (moddebug & MODDEBUG_MINORPERM) {
}
} else {
if (moddebug & MODDEBUG_MINORPERM) {
}
}
}
/*
* Add minor perm entry
*/
static void
{
/*
* Note that update_drv replace semantics require
* replacing matching entries with the new permissions.
*/
if (*wildmp)
} else {
if (v == NULL)
else
freemp = p;
goto replaced;
}
}
if (p == NULL) {
} else {
}
}
}
if (freemp) {
if (moddebug & MODDEBUG_MINORPERM) {
}
}
if (moddebug & MODDEBUG_MINORPERM) {
}
}
static int
{
char *minor;
char *name;
int is_clone;
is_clone = 0;
if (moddebug & MODDEBUG_MINORPERM) {
}
minor = "*";
}
/*
* The minor name of a node using the clone
* driver must be the driver name. To avoid
* multiple searches, we map entries in the form
* clone:<driver> to <driver>:*. This also allows us
* to filter out some of the litter in /etc/minor_perm.
* Minor perm alias entries where the name is not
* the driver kept on the clone list itself.
* This all seems very fragile as a driver could
* be introduced with an existing alias name.
*/
if (moddebug & MODDEBUG_MINORPERM) {
"mapping %s:%s to %s:*\n",
}
minor = "*";
is_clone = 1;
}
}
if (mp) {
mp->mp_minorname =
}
} else {
if (moddebug & MODDEBUG_MINORPERM) {
}
}
/* mode */
if (mp)
/* uid */
if (mp)
/* gid */
if (mp) {
if (cmd == MODREMMINORPERM) {
free_mperm(mp);
} else {
}
}
}
if (cmd == MODLOADMINORPERM)
minorperm_loaded = 1;
/*
* Reset permissions of cached dv_nodes
*/
(void) devfs_reset_perm(DV_RESET_PERM);
return (0);
}
static int
{
int error;
return (error);
}
if (error)
return (error);
return (error);
}
struct walk_args {
char *wa_drvname;
};
struct path_elem {
char *pe_dir;
char *pe_nodename;
int pe_dirlen;
};
/*ARGSUSED*/
static int
{
char *nodename;
return (INST_WALK_CONTINUE);
*nodename++ = 0;
return (INST_WALK_CONTINUE);
}
static int
modctl_remdrv_cleanup(const char *u_drvname)
{
char *drvname;
return (err);
}
/*
* First go through the instance database. For each
* instance of a device bound to the driver being
* removed, remove any underlying devfs attribute nodes.
*
* This is a two-step process. First we go through
* the instance data itself, constructing a list of
* the nodes discovered. The second step is then
* to find and remove any devfs attribute nodes
* for the instances discovered in the first step.
* The two-step process avoids any difficulties
* which could arise by holding the instance data
* lock with simultaneous devfs operations.
*/
(const char *)pe->pe_nodename);
if (rval == 0)
}
}
/*
* Pseudo nodes aren't recorded in the instance database
* so any such nodes need to be handled separately.
*/
if (rval == 0)
return (rval);
}
static int
modctl_allocpriv(const char *name)
{
int error;
return (error);
}
if (error < 0)
else
error = 0;
return (error);
}
/*ARGSUSED5*/
int
{
switch (cmd) {
case MODLOAD: /* load a module */
break;
case MODUNLOAD: /* unload a module */
break;
case MODINFO: /* get module status */
break;
case MODRESERVED: /* get last major number in range */
break;
case MODSETMINIROOT: /* we are running in miniroot */
isminiroot = 1;
error = 0;
break;
case MODADDMAJBIND: /* read major binding file */
break;
case MODGETPATHLEN: /* get modpath length */
break;
case MODGETPATH: /* get modpath */
break;
case MODREADSYSBIND: /* read system call binding file */
break;
case MODGETMAJBIND: /* get major number for named device */
break;
case MODGETNAME: /* get name of device given major number */
break;
case MODDEVT2INSTANCE:
if (get_udatamodel() == DATAMODEL_NATIVE) {
}
#ifdef _SYSCALL32_IMPL
else {
}
#endif
break;
case MODSIZEOF_DEVID: /* sizeof device id of device given dev_t */
if (get_udatamodel() == DATAMODEL_NATIVE) {
}
#ifdef _SYSCALL32_IMPL
else {
}
#endif
break;
case MODGETDEVID: /* get device id of device given dev_t */
if (get_udatamodel() == DATAMODEL_NATIVE) {
}
#ifdef _SYSCALL32_IMPL
else {
}
#endif
break;
case MODSIZEOF_MINORNAME: /* sizeof minor nm of dev_t/spectype */
if (get_udatamodel() == DATAMODEL_NATIVE) {
}
#ifdef _SYSCALL32_IMPL
else {
}
#endif
break;
case MODGETMINORNAME: /* get minor name of dev_t and spec type */
if (get_udatamodel() == DATAMODEL_NATIVE) {
}
#ifdef _SYSCALL32_IMPL
else {
}
#endif
break;
case MODGETDEVFSPATH_LEN: /* sizeof path nm of dev_t/spectype */
if (get_udatamodel() == DATAMODEL_NATIVE) {
}
#ifdef _SYSCALL32_IMPL
else {
}
#endif
break;
case MODGETDEVFSPATH: /* get path name of dev_t and spec type */
if (get_udatamodel() == DATAMODEL_NATIVE) {
}
#ifdef _SYSCALL32_IMPL
else {
}
#endif
break;
case MODEVENTS:
break;
case MODGETFBNAME: /* get the framebuffer name */
break;
case MODREREADDACF: /* reread dacf rule database from given file */
break;
case MODLOADDRVCONF: /* load driver.conf file for major */
break;
case MODUNLOADDRVCONF: /* unload driver.conf file for major */
break;
case MODREMMAJBIND: /* remove a major binding */
break;
case MODDEVID2PATHS: /* get paths given devid */
break;
case MODSETDEVPOLICY: /* establish device policy */
break;
case MODGETDEVPOLICY: /* get device policy */
(devplcysys_t *)a3);
break;
case MODALLOCPRIV:
break;
case MODGETDEVPOLICYBYNAME:
break;
case MODLOADMINORPERM:
case MODADDMINORPERM:
case MODREMMINORPERM:
break;
case MODREMDRVCLEANUP:
break;
default:
break;
}
}
/*
* Calls to kobj_load_module()() are handled off to this routine in a
* separate thread.
*/
static void
{
/* load the module and signal the creator of this thread */
/* borrow the devi lock from thread which invoked us */
thread_exit();
}
/*
* load a module, adding a reference if caller specifies rmodp. If rmodp
* is specified then an errno is returned, otherwise a module index is
* returned (-1 on error).
*/
static int
{
char *fullname;
int id = -1;
if (rmodp)
/*
* refuse / in filename to prevent "../" escapes.
*/
/*
* allocate enough space for <subdir>/<filename><NULL>
*/
} else {
}
if (rmodp) {
/* add mod_ref and return *rmodp */
}
}
}
/*
* This is the primary kernel interface to load a module. It loads and
* installs the named module. It does not hold mod_ref of the module, so
* a module unload attempt can occur at any time - it is up to the
* _fini/mod_remove implementation to determine if unload will succeed.
*/
int
{
}
/*
* Load a module.
*/
int
{
char *fullname;
/*
* allocate enough space for <subdir>/<filename><NULL>
*/
} else {
}
if (modp) {
}
if (retval == 0)
return (id);
return (-1);
}
/*
* Try to uninstall and unload a module, removing a reference if caller
* specifies rmodp.
*/
static int
{
int retval;
if (rmodp)
return (EINVAL);
if (rmodp) {
}
if (unload) {
if (retval == 0) {
retval = 0; /* already unloaded, not an error */
} else
retval = 0;
return (retval);
}
/*
* Uninstall and unload a module.
*/
int
{
}
/*
* Return status of a loaded module.
*/
static int
{
int i;
break;
}
return (EINVAL);
} else {
return (EINVAL);
(modp->mod_installed == 0)) {
return (EINVAL);
}
}
for (i = 0; i < MODMAXLINK; i++) {
}
if (modp->mod_loaded) {
}
if (modp->mod_installed) {
}
return (0);
}
static char mod_stub_err[] = "mod_hold_stub: Couldn't load stub module %s";
static char no_err[] = "No error function for weak stub %s";
/*
* used by the stubs themselves to load and hold a module.
* Returns 0 if the module is successfully held;
* the stub needs to call mod_release_stub().
* -1 if the stub should just call the err_fcn.
* Note that this code is stretched out so that we avoid subroutine calls
* and optimize for the most likely case. That is, the case where the
* module is loaded and installed and not held. In that case we just inc
* the mod_ref count and continue.
*/
int
{
struct mod_modinfo *mip;
/* we do mod_hold_by_modctl inline for speed */
if (mp->mod_installed) {
/* increment the reference count */
return (0);
} else {
}
} else {
/*
* wait one time and then go see if someone
* else has resolved the stub (set mip->mp).
*/
if (mod_hold_by_modctl(mp,
goto mod_check_again;
/*
* what we have now may have been unloaded!, in
* that case, mip->mp will be NULL, we'll hit this
* module and load again..
*/
}
} else {
/* first time we've hit this module */
}
/*
* If we are here, it means that the following conditions
* are satisfied.
*
* mip->mp != NULL
* this thread has set the mp->mod_busy = 1
* mp->mod_installed = 0
*
*/
if (mp->mod_installed == 0) {
/* Module not loaded, if weak stub don't load it */
}
} else {
/* Not a weak stub so load the module */
/*
* If mod_load() was successful
* and modinstall() failed, then
* unload the module.
*/
if (mp->mod_loaded)
mod_unload(mp);
} else {
return (-1);
}
}
}
}
/*
* At this point module is held and loaded. Release
* the mod_busy and mod_inprogress_thread before
* returning. We actually call mod_release() here so
* that if another stub wants to access this module,
* it can do so. mod_ref is incremented before mod_release()
* is called to prevent someone else from snatching the
* module from this thread.
*/
return (0);
}
void
{
/* inline mod_release_mod */
}
}
static struct modctl *
{
int retval;
/*
* Hold the module.
*/
if (modp) {
if (retval != 0) {
}
} else {
}
/*
* if dep is not NULL, clear the module dependency information.
* This information is set in mod_hold_by_name_common().
*/
}
return (modp);
}
/*
* hold, load, and install the named module
*/
static struct modctl *
{
int retval;
/*
* Verify that that module in question actually exists on disk
* before allocation of module structure by mod_hold_by_name.
*/
if (modrootloaded && swaploaded) {
*r = ENOENT;
return (NULL);
}
}
/*
* Hold the module.
*/
if (modp) {
if (retval != 0) {
*r = retval;
} else {
if ((*r = modinstall(modp)) != 0) {
/*
* We loaded it, but failed to _init() it.
* Be kind to developers -- force it
* out of memory now so that the next
* attempt to use the module will cause
* a reload. See 1093793.
*/
}
}
} else {
*r = ENOSPC;
}
return (modp);
}
static char mod_excl_msg[] =
"module %s(%s) is EXCLUDED and will not be loaded\n";
static char mod_init_msg[] = "loadmodule:%s(%s): _init() error %d\n";
/*
* This routine is needed for dependencies. Users specify dependencies
* by declaring a character array initialized to filenames of dependents.
* So the code that handles dependents deals with filenames (and not
* module names) because that's all it has. We load by filename and once
* we've loaded a file we can get the module name.
* Unfortunately there isn't a single unified filename/modulename namespace.
* C'est la vie.
*
* We allow the name being looked up to be prepended by an optional
*/
struct modctl *
{
else
sublen = 0;
do {
if (sublen) {
return (mp);
}
return (mp);
}
return (NULL);
}
/*
* Check for circular dependencies. This is called from do_dependents()
* in kobj.c. If we are the thread already loading this module, then
* we're trying to load a dependent that we're already loading which
* means the user specified circular dependencies.
*/
static int
{
/*
* Check the mod_inprogress_thread first.
* mod_inprogress_thread is used in mod_hold_stub()
* directly to improve performance.
*/
return (1);
/*
* Check the module circular dependencies.
*/
/*
* Check if there is a module circular dependency.
*/
return (1);
}
return (0);
}
static int
{
int retval;
/* primary modules don't do getinfo */
return (0);
modp->mod_filename);
/*
* The semantics of mod_info(9F) are that 0 is failure
* and non-zero is success.
*/
retval = 0;
} else
if (moddebug & MODDEBUG_USERDEBUG)
return (retval);
}
static void
{
}
/*ARGSUSED*/
static struct modctl *
{
return (mp);
}
/*
* Get the value of a symbol. This is a wrapper routine that
* calls kobj_getsymvalue(). kobj_getsymvalue() may go away but this
* wrapper will prevent callers from noticing.
*/
{
}
/*
* Get the symbol nearest an address. This is a wrapper routine that
* calls kobj_getsymname(). kobj_getsymname() may go away but this
* wrapper will prevent callers from noticing.
*/
char *
{
}
/*
* Lookup a symbol in a specified module. This is a wrapper routine that
* calls kobj_lookup(). kobj_lookup() may go away but this
* wrapper will prevent callers from noticing.
*/
{
return (0);
return (val);
}
/*
* Ask the user for the name of the system file and the default path
* for modules.
*/
void
{
static char s0[64];
kobj_close(fd);
else
systemfile = NULL;
/*CONSTANTCONDITION*/
while (1) {
printf("Name of system file [%s]: ",
if (s0[0] == '\0')
break;
systemfile = NULL;
break;
} else {
kobj_close(fd);
systemfile = s0;
break;
}
}
}
}
static char loading_msg[] = "loading '%s' id %d\n";
static char load_msg[] = "load '%s' id %d loaded @ 0x%p/0x%p size %d/%d\n";
/*
* Common code for loading a module (but not installing it).
* Handoff the task of module loading to a seperate thread
* with a large stack if possible, since this code may recurse a few times.
* Return zero if there are no errors or an errno value.
*/
static int
{
int retval;
if (mp->mod_loaded)
return (0);
if (moddebug & MODDEBUG_LOADMSG) {
mp->mod_modname);
}
return (ENXIO);
}
if (moddebug & MODDEBUG_LOADMSG2)
/* create thread to hand of call to */
/* wait for thread to complete kobj_load_module */
} else
mp->mod_loadcnt++;
if (moddebug & MODDEBUG_LOADMSG) {
}
/*
* XXX - There should be a better way to get this.
*/
else {
}
/*
* DCS: bootstrapping code. If the driver is loaded
* before root mount, it is assumed that the driver
* may be used before mounting root. In order to
* access mappings of global to local minor no.'s
* during installation/open of the driver, we load
* them into memory here while the BOP_interfaces
* are still up.
*/
}
/*
* Now that the module is loaded, we need to give DTrace
* a chance to notify its providers. This is done via
* the dtrace_modload function pointer.
*/
(*dtrace_modload)(mp);
}
} else {
/*
* If load failed then we need to release any requisites
* that we had established.
*/
if (moddebug & MODDEBUG_ERRMSG)
printf("error loading '%s', error %d\n",
}
return (retval);
}
static char unload_msg[] = "unloading %s, module id %d, loadcnt %d.\n";
static void
{
if (moddebug & MODDEBUG_LOADMSG)
/*
* If mod_ref is not zero, it means some modules might still refer
* to this module. Then you can't unload this module right now.
* Instead, set 1 to mod_delay_unload to notify the system of
* unloading this module later when it's not required any more.
*/
if (moddebug & MODDEBUG_LOADMSG2) {
printf("module %s not unloaded,"
" non-zero reference count (%d)",
}
return;
}
/*
* A DEBUG kernel would ASSERT panic above, the code is broken
* if we get this warning.
*/
return;
}
/* reset stub functions to call the binder again */
/*
* mark module as unloaded before the modctl structure is freed.
* This is required not to reuse the modctl structure before
* the module is marked as unloaded.
*/
mp->mod_loaded = 0;
/* free the memory */
if (mp->mod_delay_unload) {
mp->mod_delay_unload = 0;
if (moddebug & MODDEBUG_LOADMSG2) {
printf("deferred unload of module %s"
" (id %d) successful",
}
}
/* release hold on requisites */
/*
* Now that the module is gone, we need to give DTrace a chance to
* remove any probes that it may have had in the module. This is
* done via the dtrace_modunload function pointer.
*/
(*dtrace_modunload)(mp);
}
}
static int
{
int val;
int (*func)(void);
if (mp->mod_installed)
return (0);
/*
* If mod_delay_unload is on, it means the system chose the deferred
* unload for this module. Then you can't install this module until
* it's unloaded from the system.
*/
if (mp->mod_delay_unload)
return (ENXIO);
if (moddebug & MODDEBUG_LOADMSG)
printf("installing %s, module id %d.\n",
if (mod_install_requisites(mp) != 0) {
/*
* Note that we can't call mod_unload(mp) here since
* if modinstall() was called by mod_install_requisites(),
* we won't be able to hold the dependent modules
* (otherwise there would be a deadlock).
*/
return (ENXIO);
}
if (moddebug & MODDEBUG_ERRMSG) {
printf("init '%s' id %d loaded @ 0x%p/0x%p size %lu/%lu\n",
}
mp->mod_filename);
return (EFAULT);
}
if (moddebug & MODDEBUG_USERDEBUG) {
printf("breakpoint before calling %s:_init()\n",
mp->mod_modname);
if (DEBUGGER_PRESENT)
debug_enter("_init");
}
if (moddebug & MODDEBUG_USERDEBUG)
if (val == 0) {
/*
* Set the MODS_INSTALLED flag to enable this module
* being called now.
*/
} else if (moddebug & MODDEBUG_ERRMSG)
return (val);
}
static int
detach_driver(char *name)
{
int error;
/*
* If being called from mod_uninstall_all() then the appropriate
* driver detaches (leaf only) have already been done.
*/
if (mod_in_autounload())
return (0);
return (0);
}
static char finiret_msg[] = "Returned from _fini for %s, status = %x\n";
static int
{
int status = 0;
int (*func)(void);
/*
* Verify that we need to do something and can uninstall the module.
*
* If we should not uninstall the module or if the module is not in
* the correct state to start an uninstall we return EBUSY to prevent
* us from progressing to mod_unload. If the module has already been
* uninstalled and unloaded we return EALREADY.
*/
return (EBUSY);
return (EALREADY);
/*
* To avoid devinfo / module deadlock we must release this module
* prior to initiating the detach_driver, otherwise the detach_driver
* might deadlock on a devinfo node held by another thread
* coming top down and involving the module we have locked.
*
* When we regrab the module we must reverify that it is OK
* to proceed with the uninstall operation.
*/
/* check detach status and reverify state with lock */
return (EBUSY);
}
return (EALREADY);
}
if (moddebug & MODDEBUG_LOADMSG2)
/*
* lookup _fini, return EBUSY if not defined.
*
* The MODDEBUG_FINI_EBUSY is usefull in resolving leaks in
* detach(9E) - it allows bufctl addresses to be resolved.
*/
return (EBUSY);
/* verify that _fini is in this module */
mp->mod_filename);
return (EFAULT);
}
/* call _fini() */
if (status == 0) {
/* _fini returned success, the module is no longer installed */
if (moddebug & MODDEBUG_LOADMSG)
/*
* Even though we only set mod_installed to zero here, a zero
* return value means we are commited to a code path were
* mod_loaded will also end up as zero - we have no other
* way to get the module data and bss back to the pre _init
* state except a reload. To ensure this, after return,
* mod_busy must stay set until mod_loaded is cleared.
*/
mp->mod_installed = 0;
/*
* Clear the MODS_INSTALLED flag not to call functions
* in the module directly from now on.
*/
} else {
if (moddebug & MODDEBUG_USERDEBUG)
/*
* By definition _fini is only allowed to return EBUSY or the
* result of mod_remove (EBUSY or EINVAL). In the off chance
* that a driver returns EALREADY we convert this to EINVAL
* since to our caller EALREADY means module was already
* removed.
*/
}
return (status);
}
/*
* Uninstall all modules.
*/
static void
mod_uninstall_all(void)
{
/* mark this thread as doing autounloading */
/*
* Skip modules with the MOD_NOAUTOUNLOAD flag set
*/
continue;
}
if (moduninstall(mp) == 0) {
mod_unload(mp);
}
}
}
static int modunload_disable_count;
void
modunload_disable(void)
{
}
void
modunload_enable(void)
{
}
void
mod_uninstall_daemon(void)
{
for (;;) {
/*
* In DEBUG kernels, unheld drivers are uninstalled periodically
* every mod_uninstall_interval seconds. Periodic uninstall can
* be disabled by setting mod_uninstall_interval to 0 which is
* the default for a non-DEBUG kernel.
*/
if (mod_uninstall_interval) {
ticks = ddi_get_lbolt() +
(void) cv_timedwait(&mod_uninstall_cv,
} else {
}
/*
* The whole daemon is safe for CPR except we don't want
* the daemon to run if FREEZE is issued and this daemon
* wakes up from the cv_wait above. In this case, it'll be
* blocked in CALLB_CPR_SAFE_END until THAW is issued.
*
* The reason of calling CALLB_CPR_SAFE_BEGIN twice is that
* mod_uninstall_lock is used to protect cprinfo and
* CALLB_CPR_SAFE_BEGIN assumes that this lock is held when
* called.
*/
if ((modunload_disable_count == 0) &&
((moddebug & MODDEBUG_NOAUTOUNLOAD) == 0)) {
}
}
}
/*
* Unload all uninstalled modules.
*/
void
modreap(void)
{
}
/*
* Hold the specified module. This is the module holding primitive.
*
* If MOD_LOCK_HELD then the caller already holds the mod_lock.
*
* Return values:
* 0 ==> the module is held
* 1 ==> the module is not held and the MOD_WAIT_ONCE caller needs
* to determine how to retry.
*/
int
{
((f & (MOD_WAIT_ONCE | MOD_WAIT_FOREVER)) !=
(MOD_WAIT_ONCE | MOD_WAIT_FOREVER)));
((f & (MOD_LOCK_HELD | MOD_LOCK_NOT_HELD)) !=
(MOD_LOCK_HELD | MOD_LOCK_NOT_HELD)));
if (f & MOD_LOCK_NOT_HELD)
/*
* Module may be unloaded by daemon.
* Nevertheless, modctl structure is still in linked list
* (i.e., off &modules), not freed!
* Caller is not supposed to assume "mp" is valid, but there
* is no reasonable way to detect this but using
* mp->mod_modinfo->mp == NULL check (follow the back pointer)
* (or similar check depending on calling context)
* DON'T free modctl structure, it will be very very
* problematic.
*/
if (f & MOD_WAIT_ONCE) {
if (f & MOD_LOCK_NOT_HELD)
return (1); /* caller decides how to retry */
}
}
if (f & MOD_LOCK_NOT_HELD)
return (0);
}
static struct modctl *
{
char *modname;
int found = 0;
else
modname++;
do {
found = 1;
break;
}
if (found == 0) {
}
/*
* if dep is not NULL, set the mp in mod_requisite_loading for
* the module circular dependency check. This field is used in
* mod_circdep(), but it's cleard in mod_hold_loaded_mod().
*/
}
/*
* If the module was held, then it must be us who has it held.
*/
if (mod_circdep(mp))
else {
/*
* If the name hadn't been set or has changed, allocate
* space and set it. Free space used by previous name.
*
* Do not change the name of primary modules, for primary
* modules the mod_filename was allocated in standalone mode:
* it is illegal to kobj_alloc in standalone mode and kobj_free
* in non-standalone mode.
*/
}
}
return (mp);
}
static struct modctl *
{
}
struct modctl *
mod_hold_by_name(char *filename)
{
}
static struct modctl *
{
int found = 0;
do {
found = 1;
break;
}
else
return (mp);
}
static struct modctl *
{
int found = 0;
if (modid < -1)
return (NULL);
do {
found = 1;
break;
}
else
return (mp);
}
static void
{
}
}
void
{
if (moddebug & MODDEBUG_LOADMSG2)
}
mod_name_to_modid(char *filename)
{
char *modname;
else
modname++;
do {
}
return (-1);
}
int
mod_remove_by_name(char *name)
{
int retval;
return (EINVAL);
/*
* Do not unload forceloaded modules
*/
return (0);
}
mod_unload(mp);
retval = 0; /* already unloaded, not an error */
return (retval);
}
/*
* Record that module "dep" is dependent on module "on_mod."
*/
static void
{
struct modctl_list *mlp;
struct modctl_list *new;
/*
* Search dependent's requisite list to see if on_mod is recorded.
* List is ordered by id.
*/
break;
/* Create and insert if not already recorded */
/*
* Increment the mod_ref count in our new requisite module.
* This is what keeps a module that has other modules
* which are dependent on it from being uninstalled and
* unloaded. "on_mod"'s mod_ref count decremented in
* mod_release_requisites when the "dependent" module
* unload is complete. "on_mod" must be loaded, but may not
* yet be installed.
*/
}
}
/*
* release the hold associated with mod_make_requisite mod_ref++
* as part of unload.
*/
void
{
struct modctl_list *modl;
struct modctl_list *next;
/*
* Check if the module has to be unloaded or not.
*/
struct modctl_list *new;
/*
* Allocate the modclt_list holding the garbage
* module which should be unloaded later.
*/
KM_SLEEP);
else {
mod_garbage = new;
}
}
/* free the list as we go */
}
/*
* Unload the garbage modules.
*/
/*
* Hold this module until it's unloaded completely.
*/
(void) mod_hold_by_modctl(mp,
/*
* Check if the module is not unloaded yet and nobody requires
* the module. If it's unloaded already or somebody still
* requires the module, don't unload it now.
*/
mod_unload(mp);
}
}
/*
* Process dependency of the module represented by "dep" on the
* module named by "on."
*
* Called from kobj_do_dependents() to load a module "on" on which
* "dep" depends.
*/
struct modctl *
{
int retval;
} else if (moddebug & MODDEBUG_ERRMSG) {
printf("error processing %s on which module %s depends\n",
}
return (on_mod);
}
static int
{
struct modctl_list *modl;
int status = 0;
(void) mod_hold_by_modctl(req,
if (status != 0)
break;
}
return (status);
}
/*
* returns 1 if this thread is doing autounload, 0 otherwise.
* see mod_uninstall_all.
*/
int
{
}
/*
* gmatch adapted from libc, stripping the wchar stuff
*/
#define popchar(p, c) \
c = *p++; \
if (c == 0) \
return (0);
static int
gmatch(const char *s, const char *p)
{
int c, sc;
sc = *s++;
c = *p++;
if (c == 0)
return (sc == c); /* nothing matches nothing */
switch (c) {
case '\\':
/* skip to quoted character */
popchar(p, c)
/*FALLTHRU*/
default:
/* straight comparison */
if (c != sc)
return (0);
/*FALLTHRU*/
case '?':
/* first char matches, move to remainder */
case '*':
while (*p == '*')
p++;
/* * matches everything */
if (*p == 0)
return (1);
/* undo skip at the beginning & iterate over substrings */
--s;
while (*s) {
if (gmatch(s, p))
return (1);
s++;
}
return (0);
case '[':
/* match any char within [] */
if (sc == 0)
return (0);
if (*p == '!') {
notflag = 1;
p++;
}
popchar(p, c)
do {
/* test sc against range [c1-c2] */
popchar(p, c)
if (c == '\\') {
popchar(p, c)
}
if (notflag) {
/* return 0 on mismatch */
return (0);
ok++;
ok++;
}
/* keep going, may get a match next */
} else if (c == '\\') {
/* skip to quoted character */
popchar(p, c)
}
lc = c;
if (notflag) {
return (0);
ok++;
ok++;
}
popchar(p, c)
} while (c != ']');
/* recurse on remainder of string */
}
/*NOTREACHED*/
}
/*
* Get default perm for device from /etc/minor_perm. Return 0 if match found.
*
* Pure wild-carded patterns are handled separately so the ordering of
* these patterns doesn't matter. We're still dependent on ordering
* however as the first matching entry is the one returned.
* Not ideal but all existing examples and usage do imply this
* ordering implicitly.
*
* Drivers using the clone driver are always good for some entertainment.
* Clone nodes under pseudo have the form clone@0:<driver>. Some minor
* perm entries have the form clone:<driver>, others use <driver>:*
* Examples are clone:llc1 vs. llc2:*, for example.
*
* Minor perms in the clone:<driver> form are mapped to the drivers's
* mperm list, not the clone driver, as wildcard entries for clone
* reference only. In other words, a clone wildcard will match
* references for clone@0:<driver> but never <driver>@<minor>.
*
* Additional minor perms in the standard form are also supported,
* for mixed usage, ie a node with an entry clone:<driver> could
* provide further entries <driver>:<minor>.
*
* Finally, some uses of clone use an alias as the minor name rather
* than the driver name, with the alias as the minor perm entry.
* This case is handled by attaching the driver to bring its
* minor list into existence, then discover the alias via DDI_ALIAS.
* The clone device's minor perm list can then be searched for
* that alias.
*/
static int
{
struct ddi_minor_data *dmd;
/*
* Attach the driver named by the minor node, then
* search its first instance's minor list for an
* alias node.
*/
return (1);
break;
}
}
}
if (moddebug & MODDEBUG_MINORPERM)
"no alias for %s\n", minor_name);
return (1);
}
/*
* Go through the clone driver's mperm list looking
* for a match for the specified alias.
*/
break;
}
}
if (mp) {
if (moddebug & MODDEBUG_MP_MATCH) {
"minor perm defaults: %s %s 0%o %d %d (aliased)\n",
}
}
}
int
{
char *minor_name;
int is_clone = 0;
if (!minorperm_loaded) {
if (moddebug & MODDEBUG_MINORPERM)
"%s: minor perm not yet loaded\n", name);
return (1);
}
if (minor_name == NULL)
return (1);
minor_name++;
/*
* If it's the clone driver, search the driver as named
* by the minor. All clone minor perm entries other than
* alias nodes are actually installed on the real driver's list.
*/
if (moddebug & MODDEBUG_MINORPERM)
"%s: no such driver\n", minor_name);
return (1);
}
is_clone = 1;
} else {
}
/*
* Go through the driver's mperm list looking for
* a match for the specified minor. If there's
* no matching pattern, use the wild card.
* Defer to the clone wild for clone if specified,
* otherwise fall back to the normal form.
*/
break;
}
}
if (is_clone)
}
if (mp) {
if (moddebug & MODDEBUG_MP_MATCH) {
"minor perm defaults: %s %s 0%o %d %d\n",
}
}
/*
* If no match can be found for a clone node,
* search for a possible match for an alias.
* with minor perm entry clone:ptmx.
*/
}
}
/*
*/
/*ARGSUSED*/
{
char *subdir;
char *mod;
int subdirlen;
goto out;
/* find optional first '/' in modname */
goto out; /* only one '/' is legal */
if (mod) {
/* for subdir string without modification to argument */
mod++;
} else {
subdirlen = 0;
subdir = "misc";
}
/* reference load with errno return value */
if (subdirlen)
if (moddebug & MODDEBUG_DDI_MOD)
printf("ddi_modopen %s mode %x: %s %p %d\n",
return ((ddi_modhandle_t)hmodp);
}
void *
{
void *f;
int retval;
f = NULL;
} else {
if (f)
retval = 0;
else
}
if (moddebug & MODDEBUG_DDI_MOD)
printf("ddi_modsym in %s of %s: %d %p\n",
if (errnop)
return (f);
}
int
{
int retval;
goto out;
}
retval = 0; /* EBUSY is not an error */
if (retval == 0) {
}
printf("ddi_modclose %s: %d\n",
return (retval);
}