#include <linux/compat.h>
#include <linux/chio.h> /* here are all the ioctls */
#include <linux/mutex.h>
+#include <linux/idr.h>
+#include <linux/smp_lock.h>
#include <scsi/scsi.h>
#include <scsi/scsi_cmnd.h>
#define CH_DT_MAX 16
#define CH_TYPES 8
+#define CH_MAX_DEVS 128
MODULE_DESCRIPTION("device driver for scsi media changer devices");
MODULE_AUTHOR("Gerd Knorr <kraxel@bytesex.org>");
MODULE_LICENSE("GPL");
MODULE_ALIAS_CHARDEV_MAJOR(SCSI_CHANGER_MAJOR);
+MODULE_ALIAS_SCSI_DEVICE(TYPE_MEDIUM_CHANGER);
static int init = 1;
module_param(init, int, 0444);
#define MAX_RETRIES 1
-static int ch_probe(struct device *);
-static int ch_remove(struct device *);
-static int ch_open(struct inode * inode, struct file * filp);
-static int ch_release(struct inode * inode, struct file * filp);
-static int ch_ioctl(struct inode * inode, struct file * filp,
- unsigned int cmd, unsigned long arg);
-#ifdef CONFIG_COMPAT
-static long ch_ioctl_compat(struct file * filp,
- unsigned int cmd, unsigned long arg);
-#endif
-
static struct class * ch_sysfs_class;
typedef struct {
struct mutex lock;
} scsi_changer;
-static LIST_HEAD(ch_devlist);
-static DEFINE_SPINLOCK(ch_devlist_lock);
-static int ch_devcount;
-
-static struct scsi_driver ch_template =
-{
- .owner = THIS_MODULE,
- .gendrv = {
- .name = "ch",
- .probe = ch_probe,
- .remove = ch_remove,
- },
-};
-
-static const struct file_operations changer_fops =
-{
- .owner = THIS_MODULE,
- .open = ch_open,
- .release = ch_release,
- .ioctl = ch_ioctl,
-#ifdef CONFIG_COMPAT
- .compat_ioctl = ch_ioctl_compat,
-#endif
-};
+static DEFINE_IDR(ch_index_idr);
+static DEFINE_SPINLOCK(ch_index_lock);
static const struct {
unsigned char sense;
unsigned char asc;
unsigned char ascq;
int errno;
-} err[] = {
+} ch_err[] = {
/* Just filled in what looks right. Hav'nt checked any standard paper for
these errno assignments, so they may be wrong... */
{
/* Check to see if additional sense information is available */
if (scsi_sense_valid(sshdr) &&
sshdr->asc != 0) {
- for (i = 0; err[i].errno != 0; i++) {
- if (err[i].sense == sshdr->sense_key &&
- err[i].asc == sshdr->asc &&
- err[i].ascq == sshdr->ascq) {
- errno = -err[i].errno;
+ for (i = 0; ch_err[i].errno != 0; i++) {
+ if (ch_err[i].sense == sshdr->sense_key &&
+ ch_err[i].asc == sshdr->asc &&
+ ch_err[i].ascq == sshdr->ascq) {
+ errno = -ch_err[i].errno;
break;
}
}
{
int errno, retries = 0, timeout, result;
struct scsi_sense_hdr sshdr;
-
+
timeout = (cmd[0] == INITIALIZE_ELEMENT_STATUS)
? timeout_init : timeout_move;
result = scsi_execute_req(ch->device, cmd, direction, buffer,
buflength, &sshdr, timeout * HZ,
- MAX_RETRIES);
+ MAX_RETRIES, NULL);
dprintk("result: 0x%x\n",result);
if (driver_byte(result) & DRIVER_SENSE) {
ch_elem_to_typecode(scsi_changer *ch, u_int elem)
{
int i;
-
+
for (i = 0; i < CH_TYPES; i++) {
if (elem >= ch->firsts[i] &&
elem < ch->firsts[i] +
u_char cmd[12];
u_char *buffer;
int result;
-
+
buffer = kmalloc(512, GFP_KERNEL | GFP_DMA);
if(!buffer)
return -ENOMEM;
-
+
retry:
memset(cmd,0,sizeof(cmd));
cmd[0] = READ_ELEMENT_STATUS;
- cmd[1] = (ch->device->lun << 5) |
+ cmd[1] = (ch->device->lun << 5) |
(ch->voltags ? 0x10 : 0) |
ch_elem_to_typecode(ch,elem);
cmd[2] = (elem >> 8) & 0xff;
return result;
}
-static int
+static int
ch_init_elem(scsi_changer *ch)
{
int err;
int result,id,lun,i;
u_int elem;
- buffer = kmalloc(512, GFP_KERNEL | GFP_DMA);
+ buffer = kzalloc(512, GFP_KERNEL | GFP_DMA);
if (!buffer)
return -ENOMEM;
- memset(buffer,0,512);
-
+
memset(cmd,0,sizeof(cmd));
cmd[0] = MODE_SENSE;
cmd[1] = ch->device->lun << 5;
} else {
vprintk("reading element address assigment page failed!\n");
}
-
+
/* vendor specific element types */
for (i = 0; i < 4; i++) {
if (0 == vendor_counts[i])
/* look up the devices of the data transfer elements */
ch->dt = kmalloc(ch->counts[CHET_DT]*sizeof(struct scsi_device),
GFP_KERNEL);
+
+ if (!ch->dt) {
+ kfree(buffer);
+ return -ENOMEM;
+ }
+
for (elem = 0; elem < ch->counts[CHET_DT]; elem++) {
id = -1;
lun = 0;
ch_position(scsi_changer *ch, u_int trans, u_int elem, int rotate)
{
u_char cmd[10];
-
+
dprintk("position: 0x%x\n",elem);
if (0 == trans)
trans = ch->firsts[CHET_MT];
ch_move(scsi_changer *ch, u_int trans, u_int src, u_int dest, int rotate)
{
u_char cmd[12];
-
+
dprintk("move: 0x%x => 0x%x\n",src,dest);
if (0 == trans)
trans = ch->firsts[CHET_MT];
u_int dest1, u_int dest2, int rotate1, int rotate2)
{
u_char cmd[12];
-
+
dprintk("exchange: 0x%x => 0x%x => 0x%x\n",
src,dest1,dest2);
if (0 == trans)
cmd[8] = (dest2 >> 8) & 0xff;
cmd[9] = dest2 & 0xff;
cmd[10] = (rotate1 ? 1 : 0) | (rotate2 ? 2 : 0);
-
+
return ch_do_scsi(ch, cmd, NULL,0, DMA_NONE);
}
u_char *buffer;
int result;
- buffer = kmalloc(512, GFP_KERNEL);
+ buffer = kzalloc(512, GFP_KERNEL);
if (!buffer)
return -ENOMEM;
- memset(buffer,0,512);
dprintk("%s %s voltag: 0x%x => \"%s\"\n",
clear ? "clear" : "set",
elem, tag);
memset(cmd,0,sizeof(cmd));
cmd[0] = SEND_VOLUME_TAG;
- cmd[1] = (ch->device->lun << 5) |
+ cmd[1] = (ch->device->lun << 5) |
ch_elem_to_typecode(ch,elem);
cmd[2] = (elem >> 8) & 0xff;
cmd[3] = elem & 0xff;
cmd[5] = clear
? (alternate ? 0x0d : 0x0c)
: (alternate ? 0x0b : 0x0a);
-
+
cmd[9] = 255;
memcpy(buffer,tag,32);
int retval = 0;
u_char data[16];
unsigned int i;
-
+
mutex_lock(&ch->lock);
for (i = 0; i < ch->counts[type]; i++) {
if (0 != ch_read_element_status
static int
ch_open(struct inode *inode, struct file *file)
{
- scsi_changer *tmp, *ch;
+ scsi_changer *ch;
int minor = iminor(inode);
- spin_lock(&ch_devlist_lock);
- ch = NULL;
- list_for_each_entry(tmp,&ch_devlist,list) {
- if (tmp->minor == minor)
- ch = tmp;
- }
+ lock_kernel();
+ spin_lock(&ch_index_lock);
+ ch = idr_find(&ch_index_idr, minor);
+
if (NULL == ch || scsi_device_get(ch->device)) {
- spin_unlock(&ch_devlist_lock);
+ spin_unlock(&ch_index_lock);
+ unlock_kernel();
return -ENXIO;
}
- spin_unlock(&ch_devlist_lock);
+ spin_unlock(&ch_index_lock);
file->private_data = ch;
+ unlock_kernel();
return 0;
}
return 0;
}
-static int ch_ioctl(struct inode * inode, struct file * file,
+static long ch_ioctl(struct file *file,
unsigned int cmd, unsigned long arg)
{
scsi_changer *ch = file->private_data;
int retval;
void __user *argp = (void __user *)arg;
-
+
switch (cmd) {
case CHIOGPARAMS:
{
struct changer_params params;
-
+
params.cp_curpicker = 0;
params.cp_npickers = ch->counts[CHET_MT];
params.cp_nslots = ch->counts[CHET_ST];
params.cp_nportals = ch->counts[CHET_IE];
params.cp_ndrives = ch->counts[CHET_DT];
-
+
if (copy_to_user(argp, ¶ms, sizeof(params)))
return -EFAULT;
return 0;
return -EFAULT;
return 0;
}
-
+
case CHIOPOSITION:
{
struct changer_position pos;
-
+
if (copy_from_user(&pos, argp, sizeof (pos)))
return -EFAULT;
mutex_unlock(&ch->lock);
return retval;
}
-
+
case CHIOMOVE:
{
struct changer_move mv;
dprintk("CHIOMOVE: invalid parameter\n");
return -EBADSLT;
}
-
+
mutex_lock(&ch->lock);
retval = ch_move(ch,0,
ch->firsts[mv.cm_fromtype] + mv.cm_fromunit,
case CHIOEXCHANGE:
{
struct changer_exchange mv;
-
+
if (copy_from_user(&mv, argp, sizeof (mv)))
return -EFAULT;
dprintk("CHIOEXCHANGE: invalid parameter\n");
return -EBADSLT;
}
-
+
mutex_lock(&ch->lock);
retval = ch_exchange
(ch,0,
case CHIOGSTATUS:
{
struct changer_element_status ces;
-
+
if (copy_from_user(&ces, argp, sizeof (ces)))
return -EFAULT;
if (ces.ces_type < 0 || ces.ces_type >= CH_TYPES)
case CHIOGELEM:
{
struct changer_get_element cge;
- u_char cmd[12];
- u_char *buffer;
+ u_char ch_cmd[12];
+ u_char *buffer;
unsigned int elem;
int result,i;
-
+
if (copy_from_user(&cge, argp, sizeof (cge)))
return -EFAULT;
if (0 != ch_checkrange(ch, cge.cge_type, cge.cge_unit))
return -EINVAL;
elem = ch->firsts[cge.cge_type] + cge.cge_unit;
-
+
buffer = kmalloc(512, GFP_KERNEL | GFP_DMA);
if (!buffer)
return -ENOMEM;
mutex_lock(&ch->lock);
-
+
voltag_retry:
- memset(cmd,0,sizeof(cmd));
- cmd[0] = READ_ELEMENT_STATUS;
- cmd[1] = (ch->device->lun << 5) |
+ memset(ch_cmd, 0, sizeof(ch_cmd));
+ ch_cmd[0] = READ_ELEMENT_STATUS;
+ ch_cmd[1] = (ch->device->lun << 5) |
(ch->voltags ? 0x10 : 0) |
ch_elem_to_typecode(ch,elem);
- cmd[2] = (elem >> 8) & 0xff;
- cmd[3] = elem & 0xff;
- cmd[5] = 1;
- cmd[9] = 255;
-
- if (0 == (result = ch_do_scsi(ch, cmd, buffer, 256, DMA_FROM_DEVICE))) {
+ ch_cmd[2] = (elem >> 8) & 0xff;
+ ch_cmd[3] = elem & 0xff;
+ ch_cmd[5] = 1;
+ ch_cmd[9] = 255;
+
+ result = ch_do_scsi(ch, ch_cmd, buffer, 256, DMA_FROM_DEVICE);
+ if (!result) {
cge.cge_status = buffer[18];
cge.cge_flags = 0;
if (buffer[18] & CESTATUS_EXCEPT) {
}
kfree(buffer);
mutex_unlock(&ch->lock);
-
+
if (copy_to_user(argp, &cge, sizeof (cge)))
return -EFAULT;
return result;
mutex_unlock(&ch->lock);
return retval;
}
-
+
case CHIOSVOLTAG:
{
struct changer_set_voltag csv;
unsigned int cmd, unsigned long arg)
{
scsi_changer *ch = file->private_data;
-
+
switch (cmd) {
case CHIOGPARAMS:
case CHIOGVPARAMS:
case CHIOINITELEM:
case CHIOSVOLTAG:
/* compatible */
- return ch_ioctl(NULL /* inode, unused */,
- file, cmd, arg);
+ return ch_ioctl(file, cmd, arg);
case CHIOGSTATUS32:
{
struct changer_element_status32 ces32;
unsigned char __user *data;
-
+
if (copy_from_user(&ces32, (void __user *)arg, sizeof (ces32)))
return -EFAULT;
if (ces32.ces_type < 0 || ces32.ces_type >= CH_TYPES)
static int ch_probe(struct device *dev)
{
struct scsi_device *sd = to_scsi_device(dev);
+ struct device *class_dev;
+ int minor, ret = -ENOMEM;
scsi_changer *ch;
-
+
if (sd->type != TYPE_MEDIUM_CHANGER)
return -ENODEV;
-
- ch = kmalloc(sizeof(*ch), GFP_KERNEL);
+
+ ch = kzalloc(sizeof(*ch), GFP_KERNEL);
if (NULL == ch)
return -ENOMEM;
- memset(ch,0,sizeof(*ch));
- ch->minor = ch_devcount;
+ if (!idr_pre_get(&ch_index_idr, GFP_KERNEL))
+ goto free_ch;
+
+ spin_lock(&ch_index_lock);
+ ret = idr_get_new(&ch_index_idr, ch, &minor);
+ spin_unlock(&ch_index_lock);
+
+ if (ret)
+ goto free_ch;
+
+ if (minor > CH_MAX_DEVS) {
+ ret = -ENODEV;
+ goto remove_idr;
+ }
+
+ ch->minor = minor;
sprintf(ch->name,"ch%d",ch->minor);
+
+ class_dev = device_create(ch_sysfs_class, dev,
+ MKDEV(SCSI_CHANGER_MAJOR, ch->minor), ch,
+ "s%s", ch->name);
+ if (IS_ERR(class_dev)) {
+ printk(KERN_WARNING "ch%d: device_create failed\n",
+ ch->minor);
+ ret = PTR_ERR(class_dev);
+ goto remove_idr;
+ }
+
mutex_init(&ch->lock);
ch->device = sd;
ch_readconfig(ch);
if (init)
ch_init_elem(ch);
- class_device_create(ch_sysfs_class, NULL,
- MKDEV(SCSI_CHANGER_MAJOR,ch->minor),
- dev, "s%s", ch->name);
-
+ dev_set_drvdata(dev, ch);
sdev_printk(KERN_INFO, sd, "Attached scsi changer %s\n", ch->name);
-
- spin_lock(&ch_devlist_lock);
- list_add_tail(&ch->list,&ch_devlist);
- ch_devcount++;
- spin_unlock(&ch_devlist_lock);
+
return 0;
+remove_idr:
+ idr_remove(&ch_index_idr, minor);
+free_ch:
+ kfree(ch);
+ return ret;
}
static int ch_remove(struct device *dev)
{
- struct scsi_device *sd = to_scsi_device(dev);
- scsi_changer *tmp, *ch;
+ scsi_changer *ch = dev_get_drvdata(dev);
- spin_lock(&ch_devlist_lock);
- ch = NULL;
- list_for_each_entry(tmp,&ch_devlist,list) {
- if (tmp->device == sd)
- ch = tmp;
- }
- BUG_ON(NULL == ch);
- list_del(&ch->list);
- spin_unlock(&ch_devlist_lock);
+ spin_lock(&ch_index_lock);
+ idr_remove(&ch_index_idr, ch->minor);
+ spin_unlock(&ch_index_lock);
- class_device_destroy(ch_sysfs_class,
- MKDEV(SCSI_CHANGER_MAJOR,ch->minor));
+ device_destroy(ch_sysfs_class, MKDEV(SCSI_CHANGER_MAJOR,ch->minor));
kfree(ch->dt);
kfree(ch);
- ch_devcount--;
return 0;
}
+static struct scsi_driver ch_template = {
+ .owner = THIS_MODULE,
+ .gendrv = {
+ .name = "ch",
+ .probe = ch_probe,
+ .remove = ch_remove,
+ },
+};
+
+static const struct file_operations changer_fops = {
+ .owner = THIS_MODULE,
+ .open = ch_open,
+ .release = ch_release,
+ .unlocked_ioctl = ch_ioctl,
+#ifdef CONFIG_COMPAT
+ .compat_ioctl = ch_ioctl_compat,
+#endif
+};
+
static int __init init_ch_module(void)
{
int rc;
-
+
printk(KERN_INFO "SCSI Media Changer driver v" VERSION " \n");
ch_sysfs_class = class_create(THIS_MODULE, "scsi_changer");
if (IS_ERR(ch_sysfs_class)) {
return rc;
}
-static void __exit exit_ch_module(void)
+static void __exit exit_ch_module(void)
{
scsi_unregister_driver(&ch_template.gendrv);
unregister_chrdev(SCSI_CHANGER_MAJOR, "ch");
class_destroy(ch_sysfs_class);
+ idr_destroy(&ch_index_idr);
}
module_init(init_ch_module);