Fixup ioctls
[binject.git] / main.c
diff --git a/main.c b/main.c
index 84f312f8c820748e534ea614b22ad42d3a6e3b75..296cbfe1aac8dc61a535545f41d3acd39d054901 100644 (file)
--- a/main.c
+++ b/main.c
@@ -28,18 +28,23 @@ static int b_major;
 
 #define B_MAX_DEVS     64
 
+struct b_dev_cpu {
+       spinlock_t lock;
+       struct list_head done_list;
+};
+
 struct b_dev {
        struct list_head device_list;
-       struct list_head done_list;
+       struct list_head reaped_done;
+       spinlock_t done_lock;
        atomic_t in_flight;
-       unsigned int done_cmds;
        wait_queue_head_t wq_done;
        struct block_device *bdev;
-       spinlock_t lock;
        atomic_t ref;
        struct file *file;
        struct device *dev;
        int minor;
+       struct b_dev_cpu __percpu *cpu_queue;
        struct rcu_head rcu_free;
 };
 
@@ -133,7 +138,10 @@ static void b_dev_remove_lookup(struct b_dev *bd)
 
 static void bd_rcu_free(struct rcu_head *head)
 {
-       kfree(container_of(head, struct b_dev, rcu_free));
+       struct b_dev *bd = container_of(head, struct b_dev, rcu_free);
+
+       free_percpu(bd->cpu_queue);
+       kfree(bd);
 }
 
 static void b_dev_put(struct b_dev *bd)
@@ -160,8 +168,6 @@ static struct b_cmd *get_free_command(struct b_dev *bd)
 
        bc = kmem_cache_alloc(b_slab, GFP_KERNEL);
        if (bc) {
-               memset(bc, 0, sizeof(*bc));
-               INIT_LIST_HEAD(&bc->list);
                bc->bd = bd;
                return bc;
        }
@@ -172,15 +178,50 @@ static struct b_cmd *get_free_command(struct b_dev *bd)
 static struct b_cmd *get_completed_command(struct b_dev *bd)
 {
        struct b_cmd *bc = NULL;
+       int cpu, spliced = 0;
 
-       spin_lock_irq(&bd->lock);
-       if (!list_empty(&bd->done_list)) {
-               bc = list_entry(bd->done_list.next, struct b_cmd, list);
-               bd->done_cmds--;
-               list_del(&bc->list);
+       spin_lock(&bd->done_lock);
+       if (!list_empty(&bd->reaped_done)) {
+ret_one:
+               bc = list_entry(bd->reaped_done.next, struct b_cmd, list);
+               list_del_init(&bc->list);
        }
-       spin_unlock_irq(&bd->lock);
-       return bc;
+       spin_unlock(&bd->done_lock);
+
+       if (bc)
+               return bc;
+       
+       spin_lock(&bd->done_lock);
+       for_each_possible_cpu(cpu) {
+               struct b_dev_cpu *bdc = per_cpu_ptr(bd->cpu_queue, cpu);
+
+               spin_lock_irq(&bdc->lock);
+               if (!list_empty(&bdc->done_list)) {
+                       list_splice_init(&bdc->done_list, &bd->reaped_done);
+                       spliced++;
+               }
+               spin_unlock_irq(&bdc->lock);
+       }
+
+       if (spliced)
+               goto ret_one;
+
+       spin_unlock(&bd->done_lock);
+       return NULL;
+}
+
+static int bd_pending_done(struct b_dev *bd)
+{
+       int cpu;
+
+       for_each_possible_cpu(cpu) {
+               struct b_dev_cpu *bdc = per_cpu_ptr(bd->cpu_queue, cpu);
+
+               if (!list_empty_careful(&bdc->done_list))
+                       return 1;
+       }
+
+       return 0;
 }
 
 static struct b_cmd *get_done_command(struct b_dev *bd, int block)
@@ -196,7 +237,7 @@ static struct b_cmd *get_done_command(struct b_dev *bd, int block)
                if (!block)
                        break;
 
-               ret = wait_event_interruptible(bd->wq_done, bd->done_cmds);
+               ret = wait_event_interruptible(bd->wq_done, bd_pending_done(bd));
                if (ret) {
                        bc = ERR_PTR(-ERESTARTSYS);
                        break;
@@ -245,27 +286,30 @@ static void b_dev_complete_commands(struct b_dev *bd)
 
 static int b_dev_validate_command(struct b_user_cmd *buc)
 {
+       int i;
+
        if (!binject_buc_check_magic(buc))
                return -EINVAL;
 
-       switch (buc->type) {
-       case B_TYPE_WRITE:
-       case B_TYPE_READ:
-       case B_TYPE_DISCARD:
-       case B_TYPE_READVOID:
-       case B_TYPE_WRITEZERO:
-               if (buc->len)
-                       return 0;
-               return -EINVAL;
-       default:
-               return -EINVAL;
+       for (i = 0; i < B_TYPE_NR; i++) {
+               const struct uc_map *ucm = &uc_map[i];
+
+               if (ucm->type != buc->type)
+                       continue;
+               if (ucm->data_transfer && !buc->len)
+                       break;
+
+               return 0;
        }
+
+       return -EINVAL;
 }
 
 static void b_cmd_endio(struct bio *bio, int error)
 {
        struct b_cmd *bc = bio->bi_private;
        struct b_dev *bd = bc->bd;
+       struct b_dev_cpu *bdc;
        unsigned long flags;
        unsigned long now;
 
@@ -273,14 +317,18 @@ static void b_cmd_endio(struct bio *bio, int error)
        bc->cmd.nsec = now - bc->issue_time;
        bc->cmd.error = error;
 
-       spin_lock_irqsave(&bd->lock, flags);
-       list_add_tail(&bc->list, &bd->done_list);
-       bd->done_cmds++;
-       spin_unlock_irqrestore(&bd->lock, flags);
+       local_irq_save(flags);
+       bdc = per_cpu_ptr(bd->cpu_queue, smp_processor_id());
+
+       spin_lock(&bdc->lock);
+       list_add_tail(&bc->list, &bdc->done_list);
+       spin_unlock_irqrestore(&bdc->lock, flags);
 
        atomic_dec(&bd->in_flight);
 
-       wake_up(&bd->wq_done);
+       smp_mb();
+       if (waitqueue_active(&bd->wq_done))
+               wake_up(&bd->wq_done);
 }
 
 #if LINUX_VERSION_CODE <= KERNEL_VERSION(2, 6, 18)
@@ -412,6 +460,7 @@ static int b_dev_add_command(struct b_dev *bd, struct b_cmd *bc)
 
 static void b_dev_free_command(struct b_dev *bd, struct b_cmd *bc)
 {
+       BUG_ON(!list_empty(&bc->list));
        kmem_cache_free(b_slab, bc);
 }
 
@@ -425,10 +474,8 @@ static unsigned int b_dev_poll(struct file *file, poll_table *wait)
 
        poll_wait(file, &bd->wq_done, wait);
 
-       spin_lock_irq(&bd->lock);
-       if (!list_empty(&bd->done_list))
+       if (bd_pending_done(bd))
                mask |= POLLIN | POLLRDNORM;
-       spin_unlock_irq(&bd->lock);
 
        return mask;
 }
@@ -595,7 +642,7 @@ static int b_add_dev(struct b_ioctl_cmd *bic)
        struct inode *inode;
        struct file *file;
        struct b_dev *bd;
-       int ret;
+       int ret, cpu;
 
        file = fget(bic->fd);
        if (!file)
@@ -621,9 +668,24 @@ static int b_add_dev(struct b_ioctl_cmd *bic)
                goto out_put;
        }
 
+       bd->cpu_queue = alloc_percpu(struct b_dev_cpu);
+       if (!bd->cpu_queue) {
+               kfree(bd);
+               ret = -ENOMEM;
+               goto out_put;
+       }
+
+       for_each_possible_cpu(cpu) {
+               struct b_dev_cpu *bdc;
+
+               bdc = per_cpu_ptr(bd->cpu_queue, cpu);
+               INIT_LIST_HEAD(&bdc->done_list);
+               spin_lock_init(&bdc->lock);
+       }
+
        atomic_set(&bd->ref, 1);
-       spin_lock_init(&bd->lock);
-       INIT_LIST_HEAD(&bd->done_list);
+       spin_lock_init(&bd->done_lock);
+       INIT_LIST_HEAD(&bd->reaped_done);
        init_waitqueue_head(&bd->wq_done);
        bd->file = file;
        bd->bdev = inode->i_bdev;;
@@ -656,6 +718,7 @@ out_idr:
        idr_remove(&b_minor_idr, bd->minor);
 out_unlock:
        spin_unlock(&b_dev_lock);
+       free_percpu(bd->cpu_queue);
        kfree(bd);
 out_put:
        fput(file);
@@ -673,21 +736,21 @@ static long b_misc_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
                return -EFAULT;
 
        switch (cmd) {
-       case 0:
+       case B_IOCTL_ADD:
                ret = b_add_dev(&bic);
                if (!ret && copy_to_user(uarg, &bic, sizeof(bic))) {
                        b_del_dev(&bic);
                        ret = -EFAULT;
                }
                break;
-       case 1:
+       case B_IOCTL_DEL:
                ret =  b_del_dev(&bic);
                break;
        default:
                break;
        }
 
-       return -ENOTTY;
+       return ret;
 }
 
 static const struct file_operations b_misc_fops = {
@@ -709,11 +772,31 @@ static void __exit b_exit(void)
        misc_deregister(&b_misc_dev);
 }
 
+static void __b_cmd_init_once(struct b_cmd *bc)
+{
+       INIT_LIST_HEAD(&bc->list);
+}
+
+#ifdef KCOMPAT_OLD_SLAB
+static void b_cmd_init_once(void *data, kmem_cache_t *slab, unsigned long flags)
+{
+       if ((flags & (SLAB_CTOR_VERIFY|SLAB_CTOR_CONSTRUCTOR)) ==
+           SLAB_CTOR_CONSTRUCTOR)
+               __b_cmd_init_once(data);
+}
+#else
+static void b_cmd_init_once(void *data)
+{
+       __b_cmd_init_once(data);
+}
+#endif
+
 static int __init b_init(void)
 {
        int ret;
 
-       b_slab = binject_create_slab("binject", sizeof(struct b_cmd));
+       b_slab = binject_create_slab("binject", sizeof(struct b_cmd),
+                                       SLAB_HWCACHE_ALIGN, b_cmd_init_once);
        if (!b_slab) {
                printk(KERN_ERR "binject: failed to create cmd slab\n");
                return -ENOMEM;