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
/* * 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);