2010-05-17 22:18:00 +00:00
|
|
|
/*****************************************************************************\
|
|
|
|
* Copyright (C) 2007-2010 Lawrence Livermore National Security, LLC.
|
|
|
|
* Copyright (C) 2007 The Regents of the University of California.
|
|
|
|
* Produced at Lawrence Livermore National Laboratory (cf, DISCLAIMER).
|
|
|
|
* Written by Brian Behlendorf <behlendorf1@llnl.gov>.
|
2008-05-26 04:38:26 +00:00
|
|
|
* UCRL-CODE-235197
|
|
|
|
*
|
2010-05-17 22:18:00 +00:00
|
|
|
* This file is part of the SPL, Solaris Porting Layer.
|
|
|
|
* For details, see <http://github.com/behlendorf/spl/>.
|
|
|
|
*
|
|
|
|
* The SPL is free software; you can redistribute it and/or modify it
|
|
|
|
* under the terms of the GNU General Public License as published by the
|
|
|
|
* Free Software Foundation; either version 2 of the License, or (at your
|
|
|
|
* option) any later version.
|
2008-05-26 04:38:26 +00:00
|
|
|
*
|
2010-05-17 22:18:00 +00:00
|
|
|
* The SPL is distributed in the hope that it will be useful, but WITHOUT
|
2008-05-26 04:38:26 +00:00
|
|
|
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
|
|
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
|
|
|
|
* for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
2010-05-17 22:18:00 +00:00
|
|
|
* with the SPL. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*****************************************************************************
|
|
|
|
* Solaris Porting Layer (SPL) Module Implementation.
|
|
|
|
\*****************************************************************************/
|
2008-05-26 04:38:26 +00:00
|
|
|
|
2008-03-20 23:30:15 +00:00
|
|
|
#include <sys/sunddi.h>
|
2010-07-19 21:16:05 +00:00
|
|
|
#include <spl-debug.h>
|
2008-03-20 23:30:15 +00:00
|
|
|
|
2010-07-20 18:55:37 +00:00
|
|
|
#ifdef SS_DEBUG_SUBSYS
|
|
|
|
#undef SS_DEBUG_SUBSYS
|
2008-04-21 17:29:47 +00:00
|
|
|
#endif
|
|
|
|
|
2010-07-20 18:55:37 +00:00
|
|
|
#define SS_DEBUG_SUBSYS SS_MODULE
|
2008-04-21 17:29:47 +00:00
|
|
|
|
2008-03-20 23:30:15 +00:00
|
|
|
static spinlock_t dev_info_lock = SPIN_LOCK_UNLOCKED;
|
|
|
|
static LIST_HEAD(dev_info_list);
|
|
|
|
|
|
|
|
static struct dev_info *
|
|
|
|
get_dev_info(dev_t dev)
|
|
|
|
{
|
|
|
|
struct dev_info *di;
|
|
|
|
|
|
|
|
spin_lock(&dev_info_lock);
|
|
|
|
|
|
|
|
list_for_each_entry(di, &dev_info_list, di_list)
|
|
|
|
if (di->di_dev == dev)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
di = NULL;
|
|
|
|
out:
|
|
|
|
spin_unlock(&dev_info_lock);
|
|
|
|
return di;
|
|
|
|
}
|
|
|
|
|
2010-11-10 20:58:07 +00:00
|
|
|
static long
|
|
|
|
mod_generic_unlocked_ioctl(struct file *file,
|
|
|
|
unsigned int cmd, unsigned long arg)
|
2008-03-20 23:30:15 +00:00
|
|
|
{
|
2010-11-10 20:58:07 +00:00
|
|
|
struct inode *ino = file->f_dentry->d_inode;
|
2008-03-20 23:30:15 +00:00
|
|
|
struct dev_info *di;
|
2008-11-13 21:43:30 +00:00
|
|
|
int rc, flags = 0, rvalp = 0;
|
2008-03-20 23:30:15 +00:00
|
|
|
cred_t *cr = NULL;
|
|
|
|
|
|
|
|
di = get_dev_info(MKDEV(imajor(ino), iminor(ino)));
|
|
|
|
if (di == NULL)
|
2009-07-23 23:14:52 +00:00
|
|
|
return -EINVAL;
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
rc = di->di_ops->devo_cb_ops->cb_ioctl(di->di_dev,
|
2009-07-21 17:13:58 +00:00
|
|
|
(int)cmd, (intptr_t)arg,
|
2008-11-13 21:43:30 +00:00
|
|
|
flags, cr, &rvalp);
|
2009-07-23 23:14:52 +00:00
|
|
|
/*
|
|
|
|
* The Solaris the kernel returns positive error codes to indicate
|
|
|
|
* a failure. Under linux the kernel is expected to return a
|
|
|
|
* small negative value which is trapped by libc and used to
|
|
|
|
* set errno correctly. For this reason we negate the Solaris
|
|
|
|
* return code to ensure errno gets set correctly.
|
|
|
|
*/
|
|
|
|
return -rc;
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
|
2009-07-21 17:13:58 +00:00
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
/* Compatibility handler for ioctls from 32-bit ELF binaries */
|
|
|
|
static long
|
|
|
|
mod_generic_compat_ioctl(struct file *file,
|
|
|
|
unsigned int cmd, unsigned long arg)
|
|
|
|
{
|
2010-11-10 20:58:07 +00:00
|
|
|
return mod_generic_unlocked_ioctl(file, cmd, arg);
|
2009-07-21 17:13:58 +00:00
|
|
|
}
|
|
|
|
#endif /* CONFIG_COMPAT */
|
|
|
|
|
2008-03-20 23:30:15 +00:00
|
|
|
int
|
|
|
|
__ddi_create_minor_node(dev_info_t *di, char *name, int spec_type,
|
|
|
|
minor_t minor_num, char *node_type,
|
2008-11-13 21:43:30 +00:00
|
|
|
int flags, struct module *mod)
|
2008-03-20 23:30:15 +00:00
|
|
|
{
|
|
|
|
struct cdev *cdev;
|
|
|
|
struct cb_ops *cb_ops;
|
|
|
|
struct file_operations *fops;
|
|
|
|
int rc;
|
2010-07-20 18:55:37 +00:00
|
|
|
SENTRY;
|
2008-03-20 23:30:15 +00:00
|
|
|
|
2008-04-21 17:29:47 +00:00
|
|
|
ASSERT(spec_type == S_IFCHR);
|
|
|
|
ASSERT(minor_num < di->di_minors);
|
|
|
|
ASSERT(!strcmp(node_type, DDI_PSEUDO));
|
2008-03-20 23:30:15 +00:00
|
|
|
|
2008-04-01 03:24:17 +00:00
|
|
|
fops = kzalloc(sizeof(struct file_operations), GFP_KERNEL);
|
2008-03-20 23:30:15 +00:00
|
|
|
if (fops == NULL)
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(DDI_FAILURE);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
cdev = cdev_alloc();
|
|
|
|
if (cdev == NULL) {
|
|
|
|
kfree(fops);
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(DDI_FAILURE);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
cdev->ops = fops;
|
|
|
|
|
|
|
|
mutex_enter(&di->di_lock);
|
|
|
|
cb_ops = di->di_ops->devo_cb_ops;
|
2008-04-21 17:29:47 +00:00
|
|
|
ASSERT(cb_ops);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
/* Setup the fops to cb_ops mapping */
|
|
|
|
fops->owner = mod;
|
2009-07-21 17:13:58 +00:00
|
|
|
if (cb_ops->cb_ioctl) {
|
2010-11-10 20:58:07 +00:00
|
|
|
fops->unlocked_ioctl = mod_generic_unlocked_ioctl;
|
2009-07-21 17:13:58 +00:00
|
|
|
#ifdef CONFIG_COMPAT
|
|
|
|
fops->compat_ioctl = mod_generic_compat_ioctl;
|
|
|
|
#endif
|
|
|
|
}
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
#if 0
|
|
|
|
if (cb_ops->cb_open)
|
|
|
|
fops->open = mod_generic_open;
|
|
|
|
|
|
|
|
if (cb_ops->cb_close)
|
|
|
|
fops->release = mod_generic_close;
|
|
|
|
|
|
|
|
if (cb_ops->cb_read)
|
|
|
|
fops->read = mod_generic_read;
|
|
|
|
|
|
|
|
if (cb_ops->cb_write)
|
|
|
|
fops->write = mod_generic_write;
|
|
|
|
#endif
|
|
|
|
/* XXX: Currently unsupported operations */
|
2008-04-21 17:29:47 +00:00
|
|
|
ASSERT(cb_ops->cb_open == NULL);
|
|
|
|
ASSERT(cb_ops->cb_close == NULL);
|
|
|
|
ASSERT(cb_ops->cb_read == NULL);
|
|
|
|
ASSERT(cb_ops->cb_write == NULL);
|
|
|
|
ASSERT(cb_ops->cb_strategy == NULL);
|
|
|
|
ASSERT(cb_ops->cb_print == NULL);
|
|
|
|
ASSERT(cb_ops->cb_dump == NULL);
|
|
|
|
ASSERT(cb_ops->cb_devmap == NULL);
|
|
|
|
ASSERT(cb_ops->cb_mmap == NULL);
|
|
|
|
ASSERT(cb_ops->cb_segmap == NULL);
|
|
|
|
ASSERT(cb_ops->cb_chpoll == NULL);
|
|
|
|
ASSERT(cb_ops->cb_prop_op == NULL);
|
|
|
|
ASSERT(cb_ops->cb_str == NULL);
|
|
|
|
ASSERT(cb_ops->cb_aread == NULL);
|
|
|
|
ASSERT(cb_ops->cb_awrite == NULL);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
2009-02-04 23:15:41 +00:00
|
|
|
snprintf(di->di_name, DDI_MAX_NAME_LEN-1, "/dev/%s", name);
|
2008-11-13 21:43:30 +00:00
|
|
|
di->di_cdev = cdev;
|
|
|
|
di->di_flags = flags;
|
2008-03-20 23:30:15 +00:00
|
|
|
di->di_minor = minor_num;
|
2008-11-13 21:43:30 +00:00
|
|
|
di->di_dev = MKDEV(di->di_major, di->di_minor);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
rc = cdev_add(cdev, di->di_dev, 1);
|
|
|
|
if (rc) {
|
2010-07-20 18:55:37 +00:00
|
|
|
SERROR("Error adding cdev, %d\n", rc);
|
2008-03-20 23:30:15 +00:00
|
|
|
kfree(fops);
|
|
|
|
cdev_del(cdev);
|
|
|
|
mutex_exit(&di->di_lock);
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(DDI_FAILURE);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
spin_lock(&dev_info_lock);
|
|
|
|
list_add(&di->di_list, &dev_info_list);
|
|
|
|
spin_unlock(&dev_info_lock);
|
|
|
|
|
|
|
|
mutex_exit(&di->di_lock);
|
|
|
|
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(DDI_SUCCESS);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
2008-11-13 21:43:30 +00:00
|
|
|
EXPORT_SYMBOL(__ddi_create_minor_node);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
static void
|
2008-03-21 19:16:25 +00:00
|
|
|
__ddi_remove_minor_node_locked(dev_info_t *di, char *name)
|
2008-03-20 23:30:15 +00:00
|
|
|
{
|
2008-03-21 19:16:25 +00:00
|
|
|
if (di->di_cdev) {
|
|
|
|
cdev_del(di->di_cdev);
|
|
|
|
di->di_cdev = NULL;
|
|
|
|
}
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
spin_lock(&dev_info_lock);
|
2008-03-21 19:16:25 +00:00
|
|
|
list_del_init(&di->di_list);
|
2008-03-20 23:30:15 +00:00
|
|
|
spin_unlock(&dev_info_lock);
|
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
__ddi_remove_minor_node(dev_info_t *di, char *name)
|
|
|
|
{
|
2010-07-20 18:55:37 +00:00
|
|
|
SENTRY;
|
2008-03-20 23:30:15 +00:00
|
|
|
mutex_enter(&di->di_lock);
|
2008-03-21 19:16:25 +00:00
|
|
|
__ddi_remove_minor_node_locked(di, name);
|
2008-03-20 23:30:15 +00:00
|
|
|
mutex_exit(&di->di_lock);
|
2010-07-20 18:55:37 +00:00
|
|
|
SEXIT;
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
2008-11-13 21:43:30 +00:00
|
|
|
EXPORT_SYMBOL(__ddi_remove_minor_node);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
2009-01-09 18:49:47 +00:00
|
|
|
int
|
|
|
|
ddi_quiesce_not_needed(dev_info_t *dip)
|
|
|
|
{
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(DDI_SUCCESS);
|
2009-01-09 18:49:47 +00:00
|
|
|
}
|
2009-01-09 22:30:30 +00:00
|
|
|
EXPORT_SYMBOL(ddi_quiesce_not_needed);
|
2009-01-09 18:49:47 +00:00
|
|
|
|
2008-03-20 23:30:15 +00:00
|
|
|
#if 0
|
|
|
|
static int
|
|
|
|
mod_generic_open(struct inode *, struct file *)
|
|
|
|
{
|
2008-11-13 21:43:30 +00:00
|
|
|
open(dev_t *devp, int flags, int otyp, cred_t *credp);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
mod_generic_close(struct inode *, struct file *)
|
|
|
|
{
|
2008-11-13 21:43:30 +00:00
|
|
|
close(dev_t dev, int flags, int otyp, cred_t *credp);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
mod_generic_read(struct file *, char __user *, size_t, loff_t *)
|
|
|
|
{
|
|
|
|
read(dev_t dev, struct uio *uiop, cred_t *credp);
|
|
|
|
}
|
|
|
|
|
|
|
|
static ssize_t
|
|
|
|
mod_generic_write(struct file *, const char __user *, size_t, loff_t *)
|
|
|
|
{
|
|
|
|
write(dev_t dev, struct uio *uiop, cred_t *credp);
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
static struct dev_info *
|
|
|
|
dev_info_alloc(major_t major, minor_t minors, struct dev_ops *ops) {
|
|
|
|
struct dev_info *di;
|
|
|
|
|
|
|
|
di = kmalloc(sizeof(struct dev_info), GFP_KERNEL);
|
|
|
|
if (di == NULL)
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
mutex_init(&di->di_lock, NULL, MUTEX_DEFAULT, NULL);
|
|
|
|
INIT_LIST_HEAD(&di->di_list);
|
|
|
|
di->di_ops = ops;
|
|
|
|
di->di_class = NULL;
|
|
|
|
di->di_cdev = NULL;
|
|
|
|
di->di_major = major;
|
|
|
|
di->di_minor = 0;
|
|
|
|
di->di_minors = minors;
|
|
|
|
di->di_dev = 0;
|
|
|
|
|
|
|
|
return di;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
dev_info_free(struct dev_info *di)
|
|
|
|
{
|
|
|
|
mutex_enter(&di->di_lock);
|
2008-03-21 19:16:25 +00:00
|
|
|
__ddi_remove_minor_node_locked(di, NULL);
|
2008-03-20 23:30:15 +00:00
|
|
|
mutex_exit(&di->di_lock);
|
|
|
|
mutex_destroy(&di->di_lock);
|
|
|
|
kfree(di);
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
__mod_install(struct modlinkage *modlp)
|
|
|
|
{
|
|
|
|
struct modldrv *drv = modlp->ml_modldrv;
|
|
|
|
struct dev_info *di;
|
|
|
|
int rc;
|
2010-07-20 18:55:37 +00:00
|
|
|
SENTRY;
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
di = dev_info_alloc(modlp->ml_major, modlp->ml_minors,
|
|
|
|
drv->drv_dev_ops);
|
|
|
|
if (di == NULL)
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(ENOMEM);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
/* XXX: Really we need to be calling devo_probe if it's available
|
|
|
|
* and then calling devo_attach for each device discovered. However
|
|
|
|
* for now we just call it once and let the app sort it out.
|
|
|
|
*/
|
|
|
|
rc = drv->drv_dev_ops->devo_attach(di, DDI_ATTACH);
|
|
|
|
if (rc != DDI_SUCCESS) {
|
|
|
|
dev_info_free(di);
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(rc);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
drv->drv_dev_info = di;
|
|
|
|
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(DDI_SUCCESS);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__mod_install);
|
|
|
|
|
2009-02-04 23:15:41 +00:00
|
|
|
int
|
|
|
|
__mod_mknod(char *name, char *type, int major, int minor)
|
|
|
|
{
|
|
|
|
char cmd[] = "/bin/mknod";
|
|
|
|
char major_str[8];
|
|
|
|
char minor_str[8];
|
|
|
|
char *argv[] = { cmd,
|
|
|
|
name,
|
|
|
|
type,
|
|
|
|
major_str,
|
|
|
|
minor_str,
|
|
|
|
NULL };
|
|
|
|
char *envp[] = { "HOME=/",
|
|
|
|
"TERM=linux",
|
|
|
|
"PATH=/sbin:/usr/sbin:/bin:/usr/bin",
|
|
|
|
NULL };
|
|
|
|
|
|
|
|
snprintf(major_str, 8, "%d", major);
|
|
|
|
snprintf(minor_str, 8, "%d", minor);
|
|
|
|
|
|
|
|
return call_usermodehelper(cmd, argv, envp, 1);
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__mod_mknod);
|
|
|
|
|
2008-03-20 23:30:15 +00:00
|
|
|
int
|
|
|
|
__mod_remove(struct modlinkage *modlp)
|
|
|
|
{
|
|
|
|
struct modldrv *drv = modlp->ml_modldrv;
|
|
|
|
struct dev_info *di = drv->drv_dev_info;
|
|
|
|
int rc;
|
2010-07-20 18:55:37 +00:00
|
|
|
SENTRY;
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
rc = drv->drv_dev_ops->devo_detach(di, DDI_DETACH);
|
|
|
|
if (rc != DDI_SUCCESS)
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(rc);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
dev_info_free(di);
|
|
|
|
drv->drv_dev_info = NULL;
|
|
|
|
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(DDI_SUCCESS);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(__mod_remove);
|
|
|
|
|
|
|
|
int
|
|
|
|
ldi_ident_from_mod(struct modlinkage *modlp, ldi_ident_t *lip)
|
|
|
|
{
|
|
|
|
ldi_ident_t li;
|
2010-07-20 18:55:37 +00:00
|
|
|
SENTRY;
|
2008-03-20 23:30:15 +00:00
|
|
|
|
2008-04-21 17:29:47 +00:00
|
|
|
ASSERT(modlp);
|
|
|
|
ASSERT(lip);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
li = kmalloc(sizeof(struct ldi_ident), GFP_KERNEL);
|
|
|
|
if (li == NULL)
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(ENOMEM);
|
2008-03-20 23:30:15 +00:00
|
|
|
|
|
|
|
li->li_dev = MKDEV(modlp->ml_major, 0);
|
|
|
|
*lip = li;
|
|
|
|
|
2010-07-20 18:55:37 +00:00
|
|
|
SRETURN(0);
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ldi_ident_from_mod);
|
|
|
|
|
|
|
|
void
|
|
|
|
ldi_ident_release(ldi_ident_t lip)
|
|
|
|
{
|
2010-07-20 18:55:37 +00:00
|
|
|
SENTRY;
|
2008-04-21 17:29:47 +00:00
|
|
|
ASSERT(lip);
|
2008-03-20 23:30:15 +00:00
|
|
|
kfree(lip);
|
2010-07-20 18:55:37 +00:00
|
|
|
SEXIT;
|
2008-03-20 23:30:15 +00:00
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ldi_ident_release);
|