From: Peter Osterlund <petero2@telia.com>

Christoph Hellwig <hch@infradead.org> writes:
>
> It's still messing with the elevator setting directly which is a no-go.
> That's not the packet-writing drivers fault but needs solving first.

That can actually be avoided by letting the packet driver itself keep
track of how many unfinished bios there are in the CD request queue.
This is straightforward to implement.  The only small complication is
that incoming read requests need to be cloned so that the packet
driver can use a private bi_end_io function.

Signed-off-by: Peter Osterlund <petero2@telia.com>
Signed-off-by: Andrew Morton <akpm@osdl.org>
---

 25-akpm/drivers/block/pktcdvd.c |  100 ++++++++++++++++++++++------------------
 25-akpm/include/linux/pktcdvd.h |    9 +++
 2 files changed, 65 insertions(+), 44 deletions(-)

diff -puN drivers/block/pktcdvd.c~packet-writing-avoid-bio-hackery drivers/block/pktcdvd.c
--- 25/drivers/block/pktcdvd.c~packet-writing-avoid-bio-hackery	Wed Aug 18 17:55:47 2004
+++ 25-akpm/drivers/block/pktcdvd.c	Wed Aug 18 17:55:47 2004
@@ -71,34 +71,17 @@ static struct pktcdvd_device *pkt_devs[M
 static struct proc_dir_entry *pkt_proc;
 static int pkt_major;
 static struct semaphore ctl_mutex;	/* Serialize open/close/setup/teardown */
+static mempool_t *psd_pool;
 
 
-static struct pktcdvd_device *pkt_find_dev(request_queue_t *q)
+static void pkt_bio_finished(struct pktcdvd_device *pd)
 {
-	int i;
-
-	for (i = 0; i < MAX_WRITERS; i++) {
-		struct pktcdvd_device *pd = pkt_devs[i];
-		if (pd && bdev_get_queue(pd->bdev) == q)
-			return pd;
-	}
-
-	return NULL;
-}
-
-static void pkt_lowlevel_elv_completed_req_fn(request_queue_t *q, struct request *req)
-{
-	struct pktcdvd_device *pd = pkt_find_dev(q);
-	BUG_ON(!pd);
-
-	if (elv_queue_empty(q)) {
+	BUG_ON(atomic_read(&pd->cdrw.pending_bios) <= 0);
+	if (atomic_dec_and_test(&pd->cdrw.pending_bios)) {
 		VPRINTK("pktcdvd: queue empty\n");
 		atomic_set(&pd->iosched.attention, 1);
 		wake_up(&pd->wqueue);
 	}
-
-	if (pd->cdrw.elv_completed_req_fn)
-		pd->cdrw.elv_completed_req_fn(q, req);
 }
 
 static void pkt_bio_init(struct bio *bio)
@@ -561,7 +544,7 @@ static void pkt_iosched_process_queue(st
 
 		if (pd->iosched.writing) {
 			if (high_prio_read || (!writes_queued && reads_queued)) {
-				if (!elv_queue_empty(q)) {
+				if (atomic_read(&pd->cdrw.pending_bios) > 0) {
 					VPRINTK("pktcdvd: write, waiting\n");
 					break;
 				}
@@ -570,7 +553,7 @@ static void pkt_iosched_process_queue(st
 			}
 		} else {
 			if (!reads_queued && writes_queued) {
-				if (!elv_queue_empty(q)) {
+				if (atomic_read(&pd->cdrw.pending_bios) > 0) {
 					VPRINTK("pktcdvd: read, waiting\n");
 					break;
 				}
@@ -607,6 +590,7 @@ static void pkt_iosched_process_queue(st
 			}
 		}
 
+		atomic_inc(&pd->cdrw.pending_bios);
 		generic_make_request(bio);
 	}
 }
@@ -714,6 +698,7 @@ static int pkt_end_io_read(struct bio *b
 		atomic_inc(&pkt->run_sm);
 		wake_up(&pd->wqueue);
 	}
+	pkt_bio_finished(pd);
 
 	return 0;
 }
@@ -731,6 +716,7 @@ static int pkt_end_io_packet_write(struc
 
 	pd->stats.pkt_ended++;
 
+	pkt_bio_finished(pd);
 	atomic_dec(&pkt->io_wait);
 	atomic_inc(&pkt->run_sm);
 	wake_up(&pd->wqueue);
@@ -1996,14 +1982,10 @@ static int pkt_open_dev(struct pktcdvd_d
 			goto out_putdev;
 		}
 	}
-	spin_lock_irq(q->queue_lock);
-	pd->cdrw.elv_completed_req_fn = q->elevator.elevator_completed_req_fn;
-	q->elevator.elevator_completed_req_fn = pkt_lowlevel_elv_completed_req_fn;
-	spin_unlock_irq(q->queue_lock);
 
 	if (write) {
 		if ((ret = pkt_open_write(pd)))
-			goto restore_queue;
+			goto out_putdev;
 		/*
 		 * Some CDRW drives can not handle writes larger than one packet,
 		 * even if the size is a multiple of the packet size.
@@ -2018,17 +2000,13 @@ static int pkt_open_dev(struct pktcdvd_d
 	}
 
 	if ((ret = pkt_set_segment_merging(pd, q)))
-		goto restore_queue;
+		goto out_putdev;
 
 	if (write)
 		printk("pktcdvd: %lukB available on disc\n", lba << 1);
 
 	return 0;
 
-restore_queue:
-	spin_lock_irq(q->queue_lock);
-	q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
-	spin_unlock_irq(q->queue_lock);
 out_putdev:
 	blkdev_put(pd->bdev);
 out:
@@ -2041,18 +2019,12 @@ out:
  */
 static void pkt_release_dev(struct pktcdvd_device *pd, int flush)
 {
-	request_queue_t *q;
-
 	if (flush && pkt_flush_cache(pd))
 		DPRINTK("pktcdvd: %s not flushing cache\n", pd->name);
 
 	pkt_lock_door(pd, 0);
 
-	q = bdev_get_queue(pd->bdev);
 	pkt_set_speed(pd, MAX_SPEED, MAX_SPEED);
-	spin_lock_irq(q->queue_lock);
-	q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
-	spin_unlock_irq(q->queue_lock);
 	blkdev_put(pd->bdev);
 }
 
@@ -2118,6 +2090,32 @@ static int pkt_close(struct inode *inode
 	return ret;
 }
 
+
+static void *psd_pool_alloc(int gfp_mask, void *data)
+{
+	return kmalloc(sizeof(struct packet_stacked_data), gfp_mask);
+}
+
+static void psd_pool_free(void *ptr, void *data)
+{
+	kfree(ptr);
+}
+
+static int pkt_end_io_read_cloned(struct bio *bio, unsigned int bytes_done, int err)
+{
+	struct packet_stacked_data *psd = bio->bi_private;
+	struct pktcdvd_device *pd = psd->pd;
+
+	if (bio->bi_size)
+		return 1;
+
+	bio_put(bio);
+	bio_endio(psd->bio, psd->bio->bi_size, err);
+	mempool_free(psd, psd_pool);
+	pkt_bio_finished(pd);
+	return 0;
+}
+
 static int pkt_make_request(request_queue_t *q, struct bio *bio)
 {
 	struct pktcdvd_device *pd;
@@ -2134,12 +2132,19 @@ static int pkt_make_request(request_queu
 	}
 
 	/*
-	 * quick remap a READ
+	 * Clone READ bios so we can have our own bi_end_io callback.
 	 */
 	if (bio_data_dir(bio) == READ) {
-		bio->bi_bdev = pd->bdev;
+		struct bio *cloned_bio = bio_clone(bio, GFP_NOIO);
+		struct packet_stacked_data *psd = mempool_alloc(psd_pool, GFP_NOIO);
+
+		psd->pd = pd;
+		psd->bio = bio;
+		cloned_bio->bi_bdev = pd->bdev;
+		cloned_bio->bi_private = psd;
+		cloned_bio->bi_end_io = pkt_end_io_read_cloned;
 		pd->stats.secs_r += bio->bi_size >> 9;
-		pkt_queue_bio(pd, bio, 1);
+		pkt_queue_bio(pd, cloned_bio, 1);
 		return 0;
 	}
 
@@ -2323,6 +2328,7 @@ static int pkt_seq_show(struct seq_file 
 
 	seq_printf(m, "\nQueue state:\n");
 	seq_printf(m, "\tbios queued:\t\t%d\n", pd->bio_queue_size);
+	seq_printf(m, "\tbios pending:\t\t%d\n", atomic_read(&pd->cdrw.pending_bios));
 	seq_printf(m, "\tcurrent sector:\t\t0x%llx\n", (unsigned long long)pd->current_sector);
 
 	pkt_count_states(pd, states);
@@ -2391,6 +2397,7 @@ static int pkt_new_dev(struct pktcdvd_de
 
 	pkt_init_queue(pd);
 
+	atomic_set(&pd->cdrw.pending_bios, 0);
 	pd->cdrw.thread = kthread_run(kcdrwd, pd, "%s", pd->name);
 	if (IS_ERR(pd->cdrw.thread)) {
 		printk("pktcdvd: can't start kernel thread\n");
@@ -2658,10 +2665,14 @@ int pkt_init(void)
 {
 	int ret;
 
+	psd_pool = mempool_create(PSD_POOL_SIZE, psd_pool_alloc, psd_pool_free, NULL);
+	if (!psd_pool)
+		return -ENOMEM;
+
 	ret = register_blkdev(pkt_major, "pktcdvd");
 	if (ret < 0) {
 		printk("pktcdvd: Unable to register block device\n");
-		return ret;
+		goto out2;
 	}
 	if (!pkt_major)
 		pkt_major = ret;
@@ -2681,6 +2692,8 @@ int pkt_init(void)
 
 out:
 	unregister_blkdev(pkt_major, "pktcdvd");
+out2:
+	mempool_destroy(psd_pool);
 	return ret;
 }
 
@@ -2689,6 +2702,7 @@ void pkt_exit(void)
 	remove_proc_entry("pktcdvd", proc_root_driver);
 	misc_deregister(&pkt_misc);
 	unregister_blkdev(pkt_major, "pktcdvd");
+	mempool_destroy(psd_pool);
 }
 
 MODULE_DESCRIPTION("Packet writing layer for CD/DVD drives");
diff -puN include/linux/pktcdvd.h~packet-writing-avoid-bio-hackery include/linux/pktcdvd.h
--- 25/include/linux/pktcdvd.h~packet-writing-avoid-bio-hackery	Wed Aug 18 17:55:47 2004
+++ 25-akpm/include/linux/pktcdvd.h	Wed Aug 18 17:55:47 2004
@@ -141,7 +141,7 @@ struct packet_cdrw
 	struct list_head	pkt_active_list;
 	spinlock_t		active_list_lock; /* Serialize access to pkt_active_list */
 	struct task_struct	*thread;
-	elevator_completed_req_fn *elv_completed_req_fn;
+	atomic_t		pending_bios;
 };
 
 /*
@@ -231,6 +231,13 @@ struct pkt_rb_node {
 	struct bio		*bio;
 };
 
+struct packet_stacked_data
+{
+	struct bio		*bio;		/* Original read request bio */
+	struct pktcdvd_device	*pd;
+};
+#define PSD_POOL_SIZE		64
+
 struct pktcdvd_device
 {
 	struct block_device	*bdev;		/* dev attached */
_