From: Henrik Stokseth <henrik@hw0.org>
To: Linux SCSI ML <linux-scsi@vger.kernel.org>
Subject: need help to merge (small) virtual scsi driver upstream.
Date: Mon, 19 Jan 2009 20:04:50 +0100 [thread overview]
Message-ID: <4974CED2.9000900@hw0.org> (raw)
[-- Attachment #1: Type: text/plain, Size: 821 bytes --]
Hi! I'm one of the developers on the CDEmu project.
We want to get a driver merged into vanilla linux kernel. CDEmu is a
software suite that allows people to take a CD/DVD-image and mount it on
the system in the form of a virtual disc. Major parts of this software
is based on user-space components, but we have a kernel module that's
responsible for acting like a SCSI HBA. Mainly all
this HBA does is to send requests to a userspace daemon for processing
and then fetch the result and send it back to the SCSI subsystem.
The project's homepage is here: http://cdemu.sourceforge.net
I went over the code to make sure it follows the coding style that's
used. Is there anything else I need to do? I'm a programmer but
kernel code is something that I seldom touch.
Any help is appreciated.
Sincerely,
Henrik Stokseth
[-- Attachment #2: v-hba.c --]
[-- Type: text/x-csrc, Size: 25431 bytes --]
/*
* vhba.c
*
* Copyright (C) 2007 Chia-I Wu <b90201047 AT ntu DOT edu DOT tw>
*
* This program 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.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT 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 with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
#include <linux/init.h>
#include <linux/module.h>
#include <linux/highmem.h>
#include <linux/fs.h>
#include <linux/sched.h>
#include <linux/platform_device.h>
#include <linux/miscdevice.h>
#include <linux/poll.h>
#include <asm/uaccess.h>
#include <scsi/scsi.h>
#include <scsi/scsi_host.h>
#include <scsi/scsi_cmnd.h>
#include <scsi/scsi_device.h>
#include <kernel.api.h>
MODULE_AUTHOR("Chia-I Wu");
MODULE_VERSION(VHBA_VERSION);
MODULE_DESCRIPTION("Virtual SCSI HBA");
MODULE_LICENSE("GPL");
#ifdef DEBUG
#define DPRINTK(fmt, args...) printk(KERN_DEBUG "%s: " fmt, __FUNCTION__, ## args)
#else
#define DPRINTK(fmt, args...)
#endif
#define scmd_dbg(scmd, fmt, a...) \
dev_dbg(&(scmd)->device->sdev_gendev, fmt, ##a)
#define scmd_warn(scmd, fmt, a...) \
dev_warn(&(scmd)->device->sdev_gendev, fmt, ##a)
#define VHBA_MAX_SECTORS_PER_IO 128
#define VHBA_MAX_ID 32
#define VHBA_CAN_QUEUE 32
#define VHBA_INVALID_ID VHBA_MAX_ID
#define DATA_TO_DEVICE(dir) ((dir) == DMA_TO_DEVICE || (dir) == DMA_BIDIRECTIONAL)
#define DATA_FROM_DEVICE(dir) ((dir) == DMA_FROM_DEVICE || (dir) == DMA_BIDIRECTIONAL)
enum vhba_req_state {
VHBA_REQ_FREE,
VHBA_REQ_PENDING,
VHBA_REQ_READING,
VHBA_REQ_SENT,
VHBA_REQ_WRITING,
};
struct vhba_command {
struct scsi_cmnd *cmd;
int status;
struct list_head entry;
};
struct vhba_device {
uint id;
spinlock_t cmd_lock;
struct list_head cmd_list;
wait_queue_head_t cmd_wq;
atomic_t refcnt;
};
struct vhba_host {
struct Scsi_Host *shost;
spinlock_t cmd_lock;
int cmd_next;
struct vhba_command commands[VHBA_CAN_QUEUE];
spinlock_t dev_lock;
struct vhba_device *devices[VHBA_MAX_ID];
int num_devices;
DECLARE_BITMAP(chgmap, VHBA_MAX_ID);
struct work_struct scan_devices;
};
#define MAX_COMMAND_SIZE 16
struct vhba_request {
__u32 tag;
__u32 lun;
__u8 cdb[MAX_COMMAND_SIZE];
__u8 cdb_len;
__u32 data_len;
};
struct vhba_response {
__u32 tag;
__u32 status;
__u32 data_len;
};
static struct vhba_command *vhba_alloc_command(void);
static void vhba_free_command(struct vhba_command *vcmd);
static struct platform_device vhba_platform_device;
static struct vhba_device *vhba_device_alloc(void)
{
struct vhba_device *vdev;
vdev = kzalloc(sizeof(struct vhba_device), GFP_KERNEL);
if (!vdev)
return NULL;
vdev->id = VHBA_INVALID_ID;
spin_lock_init(&vdev->cmd_lock);
INIT_LIST_HEAD(&vdev->cmd_list);
init_waitqueue_head(&vdev->cmd_wq);
atomic_set(&vdev->refcnt, 1);
return vdev;
}
static void vhba_device_put(struct vhba_device *vdev)
{
if (atomic_dec_and_test(&vdev->refcnt))
kfree(vdev);
}
static struct vhba_device *vhba_device_get(struct vhba_device *vdev)
{
atomic_inc(&vdev->refcnt);
return vdev;
}
static int vhba_device_queue(struct vhba_device *vdev, struct scsi_cmnd *cmd)
{
struct vhba_command *vcmd;
unsigned long flags;
vcmd = vhba_alloc_command();
if (!vcmd)
return SCSI_MLQUEUE_HOST_BUSY;
vcmd->cmd = cmd;
spin_lock_irqsave(&vdev->cmd_lock, flags);
list_add_tail(&vcmd->entry, &vdev->cmd_list);
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
wake_up_interruptible(&vdev->cmd_wq);
return 0;
}
static int vhba_device_dequeue(struct vhba_device *vdev, struct scsi_cmnd *cmd)
{
struct vhba_command *vcmd;
int retval;
unsigned long flags;
spin_lock_irqsave(&vdev->cmd_lock, flags);
list_for_each_entry(vcmd, &vdev->cmd_list, entry) {
if (vcmd->cmd == cmd) {
list_del_init(&vcmd->entry);
break;
}
}
/* command not found */
if (&vcmd->entry == &vdev->cmd_list) {
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
return SUCCESS;
}
while (vcmd->status == VHBA_REQ_READING || vcmd->status == VHBA_REQ_WRITING) {
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
scmd_dbg(cmd, "wait for I/O before aborting\n");
schedule_timeout(1);
spin_lock_irqsave(&vdev->cmd_lock, flags);
}
retval = (vcmd->status == VHBA_REQ_SENT) ? FAILED : SUCCESS;
vhba_free_command(vcmd);
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
return retval;
}
static void vhba_scan_devices(struct work_struct *work)
{
struct vhba_host *vhost = container_of(work, struct vhba_host, scan_devices);
int id;
unsigned long flags;
/* no need to lock here; it'll be scheduled and run again if some device missed */
while ((id = find_first_bit(vhost->chgmap, vhost->shost->max_id)) < vhost->shost->max_id) {
int remove;
spin_lock_irqsave(&vhost->dev_lock, flags);
clear_bit(id, vhost->chgmap);
remove = !(vhost->devices[id]);
spin_unlock_irqrestore(&vhost->dev_lock, flags);
dev_dbg(&vhost->shost->shost_gendev, "try to %s target 0:%d:0\n", (remove) ? "remove" : "add", id);
if (remove) {
struct scsi_device *sdev;
sdev = scsi_device_lookup(vhost->shost, 0, id, 0);
if (sdev) {
scsi_remove_device(sdev);
scsi_device_put(sdev);
}
} else {
scsi_add_device(vhost->shost, 0, id, 0);
}
}
}
static int vhba_add_device(struct vhba_device *vdev)
{
struct vhba_host *vhost;
int i;
unsigned long flags;
vhost = platform_get_drvdata(&vhba_platform_device);
vhba_device_get(vdev);
spin_lock_irqsave(&vhost->dev_lock, flags);
if (vhost->num_devices >= vhost->shost->max_id) {
spin_unlock_irqrestore(&vhost->dev_lock, flags);
vhba_device_put(vdev);
return -EBUSY;
}
for (i = 0; i < vhost->shost->max_id; i++) {
if (vhost->devices[i] == NULL) {
vdev->id = i;
vhost->devices[i] = vdev;
vhost->num_devices++;
set_bit(vdev->id, vhost->chgmap);
break;
}
}
spin_unlock_irqrestore(&vhost->dev_lock, flags);
schedule_work(&vhost->scan_devices);
return 0;
}
static int vhba_remove_device(struct vhba_device *vdev)
{
struct vhba_host *vhost;
unsigned long flags;
vhost = platform_get_drvdata(&vhba_platform_device);
spin_lock_irqsave(&vhost->dev_lock, flags);
set_bit(vdev->id, vhost->chgmap);
vhost->devices[vdev->id] = NULL;
vhost->num_devices--;
vdev->id = VHBA_INVALID_ID;
spin_unlock_irqrestore(&vhost->dev_lock, flags);
vhba_device_put(vdev);
schedule_work(&vhost->scan_devices);
return 0;
}
static struct vhba_device *vhba_lookup_device(int id)
{
struct vhba_host *vhost;
struct vhba_device *vdev = NULL;
unsigned long flags;
vhost = platform_get_drvdata(&vhba_platform_device);
if (likely(id < vhost->shost->max_id)) {
spin_lock_irqsave(&vhost->dev_lock, flags);
vdev = vhost->devices[id];
if (vdev)
vdev = vhba_device_get(vdev);
spin_unlock_irqrestore(&vhost->dev_lock, flags);
}
return vdev;
}
static struct vhba_command *vhba_alloc_command(void)
{
struct vhba_host *vhost;
struct vhba_command *vcmd;
unsigned long flags;
int i;
vhost = platform_get_drvdata(&vhba_platform_device);
spin_lock_irqsave(&vhost->cmd_lock, flags);
vcmd = vhost->commands + vhost->cmd_next++;
if (vcmd->status != VHBA_REQ_FREE) {
for (i = 0; i < vhost->shost->can_queue; i++) {
vcmd = vhost->commands + i;
if (vcmd->status == VHBA_REQ_FREE) {
vhost->cmd_next = i + 1;
break;
}
}
if (i == vhost->shost->can_queue)
vcmd = NULL;
}
if (vcmd)
vcmd->status = VHBA_REQ_PENDING;
vhost->cmd_next %= vhost->shost->can_queue;
spin_unlock_irqrestore(&vhost->cmd_lock, flags);
return vcmd;
}
static void vhba_free_command(struct vhba_command *vcmd)
{
struct vhba_host *vhost;
unsigned long flags;
vhost = platform_get_drvdata(&vhba_platform_device);
spin_lock_irqsave(&vhost->cmd_lock, flags);
vcmd->status = VHBA_REQ_FREE;
spin_unlock_irqrestore(&vhost->cmd_lock, flags);
}
static int vhba_queuecommand(struct scsi_cmnd *cmd, void (*done)(struct scsi_cmnd *))
{
struct vhba_device *vdev;
int retval;
scmd_dbg(cmd, "queue %lu\n", cmd->serial_number);
vdev = vhba_lookup_device(cmd->device->id);
if (!vdev) {
scmd_dbg(cmd, "no such device\n");
cmd->result = DID_NO_CONNECT << 16;
done(cmd);
return 0;
}
cmd->scsi_done = done;
retval = vhba_device_queue(vdev, cmd);
vhba_device_put(vdev);
return retval;
}
static int vhba_abort(struct scsi_cmnd *cmd)
{
struct vhba_device *vdev;
int retval = SUCCESS;
scmd_warn(cmd, "abort %lu\n", cmd->serial_number);
vdev = vhba_lookup_device(cmd->device->id);
if (vdev) {
retval = vhba_device_dequeue(vdev, cmd);
vhba_device_put(vdev);
} else {
cmd->result = DID_NO_CONNECT << 16;
}
return retval;
}
static struct scsi_host_template vhba_template = {
.module = THIS_MODULE,
.name = "vhba",
.proc_name = "vhba",
.queuecommand = vhba_queuecommand,
.eh_abort_handler = vhba_abort,
.can_queue = VHBA_CAN_QUEUE,
.this_id = -1,
.cmd_per_lun = 1,
.max_sectors = VHBA_MAX_SECTORS_PER_IO,
.sg_tablesize = 256,
};
static ssize_t do_request(struct scsi_cmnd *cmd, char __user *buf, size_t buf_len)
{
struct vhba_request vreq;
ssize_t ret;
scmd_dbg(cmd, "request %lu, cdb 0x%x, bufflen %d, use_sg %d\n",
cmd->serial_number, cmd->cmnd[0], scsi_bufflen(cmd), scsi_sg_count(cmd));
ret = sizeof(vreq);
if (DATA_TO_DEVICE(cmd->sc_data_direction))
ret += scsi_bufflen(cmd);
if (ret > buf_len) {
scmd_warn(cmd, "buffer too small (%zd < %zd) for a request\n", buf_len, ret);
return -EIO;
}
vreq.tag = cmd->serial_number;
vreq.lun = cmd->device->lun;
memcpy(vreq.cdb, cmd->cmnd, MAX_COMMAND_SIZE);
vreq.cdb_len = cmd->cmd_len;
vreq.data_len = scsi_bufflen(cmd);
if (copy_to_user(buf, &vreq, sizeof(vreq)))
return -EFAULT;
if (DATA_TO_DEVICE(cmd->sc_data_direction) && vreq.data_len) {
buf += sizeof(vreq);
/* XXX use_sg? */
if (copy_to_user(buf, scsi_sglist(cmd), vreq.data_len))
return -EFAULT;
}
return ret;
}
static ssize_t do_response(struct scsi_cmnd *cmd, const char __user *buf, size_t buf_len, struct vhba_response *res)
{
ssize_t ret = 0;
scmd_dbg(cmd, "response %lu, status %x, data len %d, use_sg %d\n",
cmd->serial_number, res->status, res->data_len, scsi_sg_count(cmd));
if (res->status) {
if (res->data_len > SCSI_SENSE_BUFFERSIZE) {
scmd_warn(cmd, "truncate sense (%d < %d)", SCSI_SENSE_BUFFERSIZE, res->data_len);
res->data_len = SCSI_SENSE_BUFFERSIZE;
}
if (copy_from_user(cmd->sense_buffer, buf, res->data_len))
return -EFAULT;
cmd->result = res->status;
ret += res->data_len;
} else if (DATA_FROM_DEVICE(cmd->sc_data_direction) && scsi_bufflen(cmd)) {
size_t to_read;
if (res->data_len > scsi_bufflen(cmd)) {
scmd_warn(cmd, "truncate data (%d < %d)\n", scsi_bufflen(cmd), res->data_len);
res->data_len = scsi_bufflen(cmd);
}
to_read = res->data_len;
if (scsi_sg_count(cmd)) {
unsigned char buf_stack[64];
unsigned char *kaddr, *uaddr, *kbuf;
struct scatterlist *sg = scsi_sglist(cmd);
int i;
uaddr = (unsigned char *) buf;
if (res->data_len > 64) {
kbuf = kmalloc(PAGE_SIZE, GFP_KERNEL);
} else {
kbuf = buf_stack;
}
for (i = 0; i < scsi_sg_count(cmd); i++) {
size_t len = (sg[i].length < to_read) ? sg[i].length : to_read;
if (copy_from_user(kbuf, uaddr, len)) {
if (kbuf != buf_stack)
kfree(kbuf);
return -EFAULT;
}
uaddr += len;
kaddr = kmap_atomic(sg_page(&sg[i]), KM_USER0);
memcpy(kaddr + sg[i].offset, kbuf, len);
kunmap_atomic(kaddr, KM_USER0);
to_read -= len;
if (to_read == 0)
break;
}
if (kbuf != buf_stack)
kfree(kbuf);
} else {
if (copy_from_user(scsi_sglist(cmd), buf, res->data_len))
return -EFAULT;
to_read -= res->data_len;
}
scsi_set_resid(cmd, to_read);
ret += res->data_len - to_read;
}
return ret;
}
static inline struct vhba_command *next_command(struct vhba_device *vdev)
{
struct vhba_command *vcmd;
list_for_each_entry(vcmd, &vdev->cmd_list, entry) {
if (vcmd->status == VHBA_REQ_PENDING)
break;
}
if (&vcmd->entry == &vdev->cmd_list)
vcmd = NULL;
return vcmd;
}
static inline struct vhba_command *match_command(struct vhba_device *vdev, u32 tag)
{
struct vhba_command *vcmd;
list_for_each_entry(vcmd, &vdev->cmd_list, entry) {
if (vcmd->cmd->serial_number == tag)
break;
}
if (&vcmd->entry == &vdev->cmd_list)
vcmd = NULL;
return vcmd;
}
static struct vhba_command *wait_command(struct vhba_device *vdev, unsigned long flags)
{
struct vhba_command *vcmd;
DEFINE_WAIT(wait);
while (!(vcmd = next_command(vdev))) {
if (signal_pending(current))
break;
prepare_to_wait(&vdev->cmd_wq, &wait, TASK_INTERRUPTIBLE);
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
schedule();
spin_lock_irqsave(&vdev->cmd_lock, flags);
}
finish_wait(&vdev->cmd_wq, &wait);
if (vcmd)
vcmd->status = VHBA_REQ_READING;
return vcmd;
}
static ssize_t vhba_ctl_read(struct file *file, char __user *buf, size_t buf_len, loff_t *offset)
{
struct vhba_device *vdev;
struct vhba_command *vcmd;
ssize_t ret;
unsigned long flags;
vdev = file->private_data;
spin_lock_irqsave(&vdev->cmd_lock, flags);
vcmd = wait_command(vdev, flags);
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
if (!vcmd)
return -ERESTARTSYS;
ret = do_request(vcmd->cmd, buf, buf_len);
spin_lock_irqsave(&vdev->cmd_lock, flags);
if (ret >= 0) {
vcmd->status = VHBA_REQ_SENT;
*offset += ret;
} else {
vcmd->status = VHBA_REQ_PENDING;
}
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
return ret;
}
static ssize_t vhba_ctl_write(struct file *file, const char __user *buf, size_t buf_len, loff_t *offset)
{
struct vhba_device *vdev;
struct vhba_command *vcmd;
struct vhba_response res;
ssize_t ret;
unsigned long flags;
if (buf_len < sizeof(res))
return -EIO;
if (copy_from_user(&res, buf, sizeof(res)))
return -EFAULT;
vdev = file->private_data;
spin_lock_irqsave(&vdev->cmd_lock, flags);
vcmd = match_command(vdev, res.tag);
if (!vcmd || vcmd->status != VHBA_REQ_SENT) {
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
DPRINTK("not expecting response\n");
return -EIO;
}
vcmd->status = VHBA_REQ_WRITING;
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
ret = do_response(vcmd->cmd, buf + sizeof(res), buf_len - sizeof(res), &res);
spin_lock_irqsave(&vdev->cmd_lock, flags);
if (ret >= 0) {
vcmd->cmd->scsi_done(vcmd->cmd);
ret += sizeof(res);
/* don't compete with vhba_device_dequeue */
if (!list_empty(&vcmd->entry)) {
list_del_init(&vcmd->entry);
vhba_free_command(vcmd);
}
} else {
vcmd->status = VHBA_REQ_SENT;
}
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
return ret;
}
static int vhba_ctl_ioctl (struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
{
struct vhba_device *vdev = file->private_data;
struct vhba_host *vhost;
struct scsi_device *sdev;
switch (cmd) {
case 0xBEEF001:
vhost = platform_get_drvdata(&vhba_platform_device);
sdev = scsi_device_lookup(vhost->shost, 0, vdev->id, 0);
if (sdev) {
int id[4] = {
sdev->host->host_no,
sdev->channel,
sdev->id,
sdev->lun
};
scsi_device_put(sdev);
if (copy_to_user((void *)arg, id, sizeof(id)))
return -EFAULT;
return 0;
} else {
return -ENODEV;
}
}
return -ENOTTY;
}
static unsigned int vhba_ctl_poll(struct file *file, poll_table *wait)
{
struct vhba_device *vdev = file->private_data;
unsigned int mask = 0;
unsigned long flags;
poll_wait(file, &vdev->cmd_wq, wait);
spin_lock_irqsave(&vdev->cmd_lock, flags);
if (next_command(vdev))
mask |= POLLIN | POLLRDNORM;
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
return mask;
}
static int vhba_ctl_open(struct inode *inode, struct file *file)
{
struct vhba_device *vdev;
int retval;
DPRINTK("open\n");
/* check if vhba is probed */
if (!platform_get_drvdata(&vhba_platform_device))
return -ENODEV;
vdev = vhba_device_alloc();
if (!vdev)
return -ENOMEM;
if (!(retval = vhba_add_device(vdev)))
file->private_data = vdev;
vhba_device_put(vdev);
return retval;
}
static int vhba_ctl_release(struct inode *inode, struct file *file)
{
struct vhba_device *vdev;
struct vhba_command *vcmd;
unsigned long flags;
DPRINTK("release\n");
vdev = file->private_data;
vhba_device_get(vdev);
vhba_remove_device(vdev);
spin_lock_irqsave(&vdev->cmd_lock, flags);
list_for_each_entry(vcmd, &vdev->cmd_list, entry) {
WARN_ON(vcmd->status == VHBA_REQ_READING || vcmd->status == VHBA_REQ_WRITING);
scmd_warn(vcmd->cmd, "device released with command %lu\n", vcmd->cmd->serial_number);
vcmd->cmd->result = DID_NO_CONNECT << 16;
vcmd->cmd->scsi_done(vcmd->cmd);
vhba_free_command(vcmd);
}
INIT_LIST_HEAD(&vdev->cmd_list);
spin_unlock_irqrestore(&vdev->cmd_lock, flags);
vhba_device_put(vdev);
return 0;
}
static struct file_operations vhba_ctl_fops = {
.owner = THIS_MODULE,
.open = vhba_ctl_open,
.release = vhba_ctl_release,
.read = vhba_ctl_read,
.write = vhba_ctl_write,
.poll = vhba_ctl_poll,
.ioctl = vhba_ctl_ioctl,
};
static struct miscdevice vhba_miscdev = {
.minor = MISC_DYNAMIC_MINOR,
.name = "vhba_ctl",
.fops = &vhba_ctl_fops,
};
static int vhba_probe(struct platform_device *pdev)
{
struct Scsi_Host *shost;
struct vhba_host *vhost;
int i;
shost = scsi_host_alloc(&vhba_template, sizeof(struct vhba_host));
if (!shost)
return -ENOMEM;
shost->max_id = VHBA_MAX_ID;
/* we don't support lun > 0 */
shost->max_lun = 1;
vhost = (struct vhba_host *) shost->hostdata;
memset(vhost, 0, sizeof(*vhost));
vhost->shost = shost;
vhost->num_devices = 0;
spin_lock_init(&vhost->dev_lock);
spin_lock_init(&vhost->cmd_lock);
INIT_WORK(&vhost->scan_devices, vhba_scan_devices);
vhost->cmd_next = 0;
for (i = 0; i < vhost->shost->can_queue; i++)
vhost->commands[i].status = VHBA_REQ_FREE;
platform_set_drvdata(pdev, vhost);
if (scsi_add_host(shost, &pdev->dev)) {
scsi_host_put(shost);
return -ENOMEM;
}
return 0;
}
static int vhba_remove(struct platform_device *pdev)
{
struct vhba_host *vhost;
struct Scsi_Host *shost;
vhost = platform_get_drvdata(pdev);
shost = vhost->shost;
scsi_remove_host(shost);
scsi_host_put(shost);
return 0;
}
static void vhba_release(struct device * dev)
{
return;
}
static struct platform_device vhba_platform_device = {
.name = "vhba",
.id = -1,
.dev = {
.release = vhba_release,
},
};
static struct platform_driver vhba_platform_driver = {
.driver = {
.owner = THIS_MODULE,
.name = "vhba",
},
.probe = vhba_probe,
.remove = vhba_remove,
};
static int __init vhba_init(void)
{
int ret;
ret = platform_device_register(&vhba_platform_device);
if (ret < 0)
return ret;
ret = platform_driver_register(&vhba_platform_driver);
if (ret < 0) {
platform_device_unregister(&vhba_platform_device);
return ret;
}
ret = misc_register(&vhba_miscdev);
if (ret < 0) {
platform_driver_unregister(&vhba_platform_driver);
platform_device_unregister(&vhba_platform_device);
return ret;
}
return 0;
}
static void __exit vhba_exit(void)
{
misc_deregister(&vhba_miscdev);
platform_driver_unregister(&vhba_platform_driver);
platform_device_unregister(&vhba_platform_device);
}
module_init(vhba_init);
module_exit(vhba_exit);
next reply other threads:[~2009-01-19 19:04 UTC|newest]
Thread overview: 6+ messages / expand[flat|nested] mbox.gz Atom feed top
2009-01-19 19:04 Henrik Stokseth [this message]
2009-01-19 19:29 ` need help to merge (small) virtual scsi driver upstream Bart Van Assche
2009-01-21 6:12 ` FUJITA Tomonori
2009-01-21 7:45 ` Henrik Stokseth
2009-01-21 23:09 ` FUJITA Tomonori
2009-01-23 20:23 ` Vladislav Bolkhovitin
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=4974CED2.9000900@hw0.org \
--to=henrik@hw0.org \
--cc=linux-scsi@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.