linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH] CDRW packet writing support for 2.6.7-bk13
@ 2004-07-01 13:34 Peter Osterlund
  2004-07-02 21:52 ` Peter Osterlund
                   ` (3 more replies)
  0 siblings, 4 replies; 27+ messages in thread
From: Peter Osterlund @ 2004-07-01 13:34 UTC (permalink / raw)
  To: linux-kernel; +Cc: Jens Axboe, Andrew Morton

This patch implements CDRW packet writing as a kernel block device.
Usage instructions are in the packet-writing.txt file.

A hint: If you don't want to wait for a complete disc format, you can
format just a part of the disc. For example:

        cdrwtool -d /dev/hdc -m 10240

This will format 10240 blocks, ie 20MB.


Signed-off-by: Peter Osterlund <petero2@telia.com>

---

 linux-petero/Documentation/cdrom/00-INDEX           |    2 
 linux-petero/Documentation/cdrom/packet-writing.txt |   22 
 linux-petero/drivers/block/Kconfig                  |   32 
 linux-petero/drivers/block/Makefile                 |    1 
 linux-petero/drivers/block/pktcdvd.c                | 2654 ++++++++++++++++++++
 linux-petero/drivers/cdrom/Makefile                 |    1 
 linux-petero/drivers/ide/ide-cd.c                   |    6 
 linux-petero/drivers/scsi/sr.c                      |    6 
 linux-petero/include/linux/cdrom.h                  |    1 
 linux-petero/include/linux/major.h                  |    2 
 linux-petero/include/linux/pktcdvd.h                |  261 +
 11 files changed, 2983 insertions(+), 5 deletions(-)

diff -puN Documentation/cdrom/00-INDEX~packet-2.6.7 Documentation/cdrom/00-INDEX
--- linux/Documentation/cdrom/00-INDEX~packet-2.6.7	2004-07-01 13:23:15.000000000 +0200
+++ linux-petero/Documentation/cdrom/00-INDEX	2004-07-01 13:23:15.000000000 +0200
@@ -22,6 +22,8 @@ mcdx
 	- info on improved Mitsumi CD-ROM driver.
 optcd
 	- info on the Optics Storage 8000 AT CD-ROM driver
+packet-writing.txt
+	- Info on the CDRW packet writing module
 sbpcd
 	- info on the SoundBlaster/Panasonic CD-ROM interface driver.
 sjcd
diff -puN /dev/null Documentation/cdrom/packet-writing.txt
--- /dev/null	2004-02-23 22:02:56.000000000 +0100
+++ linux-petero/Documentation/cdrom/packet-writing.txt	2004-07-01 13:23:15.000000000 +0200
@@ -0,0 +1,22 @@
+Getting started quick
+---------------------
+
+- Select packet support in the block device section and UDF support in
+  the file system section.
+
+- Compile and install kernel and modules, reboot.
+
+- You need the udftools package (pktsetup, mkudffs, cdrwtool).
+  Download from http://sourceforge.net/projects/linux-udf/
+
+- Grab a new CD-RW disc and format it (assuming CD-RW is hdc, substitute
+  as appropriate):
+	# cdrwtool -d /dev/hdc -q
+
+- Make sure that /dev/pktcdvd0 exists (mknod /dev/pktcdvd0 b 97 0)
+
+- Setup your writer
+	# pktsetup /dev/pktcdvd0 /dev/hdc
+
+- Now you can mount /dev/pktcdvd0 and copy files to it. Enjoy!
+	# mount /dev/pktcdvd0 /cdrom -t udf -o rw,noatime
diff -puN drivers/block/Kconfig~packet-2.6.7 drivers/block/Kconfig
--- linux/drivers/block/Kconfig~packet-2.6.7	2004-07-01 13:23:15.000000000 +0200
+++ linux-petero/drivers/block/Kconfig	2004-07-01 15:05:02.773199032 +0200
@@ -340,6 +340,38 @@ config LBD
 	  your machine, or if you want to have a raid or loopback device
 	  bigger than 2TB.  Otherwise say N.
 
+config CDROM_PKTCDVD
+	tristate "Packet writing on CD/DVD media"
+	help
+	  If you have a CDROM drive that supports packet writing, say Y to
+	  include preliminary support. It should work with any MMC/Mt Fuji
+	  compliant ATAPI or SCSI drive, which is just about any newer CD
+	  writer.
+
+	  Currently only writing to CD-RW discs is possible.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pktcdvd.
+
+config CDROM_PKTCDVD_BUFFERS
+	int "Free buffers for data gathering"
+	depends on CDROM_PKTCDVD
+	default "8"
+	help
+	  This controls the maximum number of active concurrent packets. More
+	  concurrent packets can increase write performance, but also require
+	  more memory. Each concurrent packet will require approximately 64Kb
+	  of non-swappable kernel memory, memory which will be allocated at
+	  pktsetup time.
+
+config CDROM_PKTCDVD_WCACHE
+	bool "Enable write caching"
+	depends on CDROM_PKTCDVD
+	help
+	  If enabled, write caching will be set for the CD-R/W device. For now
+	  this option is dangerous unless the CD-RW media is known good, as we
+	  don't do deferred write error handling yet.
+
 source "drivers/s390/block/Kconfig"
 
 endmenu
diff -puN drivers/block/Makefile~packet-2.6.7 drivers/block/Makefile
--- linux/drivers/block/Makefile~packet-2.6.7	2004-07-01 13:23:15.000000000 +0200
+++ linux-petero/drivers/block/Makefile	2004-07-01 13:23:15.000000000 +0200
@@ -35,6 +35,7 @@ obj-$(CONFIG_BLK_DEV_XD)	+= xd.o
 obj-$(CONFIG_BLK_CPQ_DA)	+= cpqarray.o
 obj-$(CONFIG_BLK_CPQ_CISS_DA)  += cciss.o
 obj-$(CONFIG_BLK_DEV_DAC960)	+= DAC960.o
+obj-$(CONFIG_CDROM_PKTCDVD)	+= pktcdvd.o
 
 obj-$(CONFIG_BLK_DEV_UMEM)	+= umem.o
 obj-$(CONFIG_BLK_DEV_NBD)	+= nbd.o
diff -puN /dev/null drivers/block/pktcdvd.c
--- /dev/null	2004-02-23 22:02:56.000000000 +0100
+++ linux-petero/drivers/block/pktcdvd.c	2004-07-01 15:09:19.447178656 +0200
@@ -0,0 +1,2654 @@
+/*
+ * Copyright (C) 2000 Jens Axboe <axboe@suse.de>
+ * Copyright (C) 2001-2004 Peter Osterlund <petero2@telia.com>
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License.  See linux/COPYING for more information.
+ *
+ * Packet writing layer for ATAPI and SCSI CD-R, CD-RW, DVD-R, and
+ * DVD-RW devices (aka an exercise in block layer masturbation)
+ *
+ *
+ * TODO: (circa order of when I will fix it)
+ * - Only able to write on CD-RW media right now.
+ * - check host application code on media and set it in write page
+ * - Generic interface for UDF to submit large packets for variable length
+ *   packet writing
+ * - interface for UDF <-> packet to negotiate a new location when a write
+ *   fails.
+ * - handle OPC, especially for -RW media
+ *
+ * Theory of operation:
+ *
+ * We use a custom make_request_fn function that forwards reads directly to
+ * the underlying CD device. Write requests are either attached directly to
+ * a live packet_data object, or simply stored sequentially in a list for
+ * later processing by the kcdrwd kernel thread. This driver doesn't use
+ * any elevator functionally as defined by the elevator_s struct, but the
+ * underlying CD device uses a standard elevator.
+ *
+ * This strategy makes it possible to do very late merging of IO requests.
+ * A new bio sent to pkt_make_request can be merged with a live packet_data
+ * object even if the object is in the data gathering state.
+ *
+ *************************************************************************/
+
+#define VERSION_CODE	"v0.1.6a 2004-07-01 Jens Axboe (axboe@suse.de) and petero2@telia.com"
+
+#include <linux/pktcdvd.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/file.h>
+#include <linux/proc_fs.h>
+#include <linux/buffer_head.h>
+#include <linux/devfs_fs_kernel.h>
+#include <linux/suspend.h>
+#include <scsi/scsi_cmnd.h>
+
+#include <asm/uaccess.h>
+
+#define SCSI_IOCTL_SEND_COMMAND	1
+
+#define ZONE(sector, pd) (((sector) + (pd)->offset) & ~((pd)->settings.size - 1))
+
+static struct pktcdvd_device *pkt_devs;
+static struct proc_dir_entry *pkt_proc;
+static struct gendisk *disks[MAX_WRITERS];
+
+static struct pktcdvd_device *pkt_find_dev(request_queue_t *q)
+{
+	int i;
+
+	for (i = 0; i < MAX_WRITERS; i++)
+		if (pkt_devs[i].bdev && bdev_get_queue(pkt_devs[i].bdev) == q)
+			return &pkt_devs[i];
+
+	return NULL;
+}
+
+/*
+ * The underlying block device is not allowed to merge write requests. Some
+ * CDRW drives can not handle writes larger than one packet, even if the size
+ * is a multiple of the packet size.
+ */
+static int pkt_lowlevel_elv_merge_fn(request_queue_t *q, struct request **req, struct bio *bio)
+{
+	struct pktcdvd_device *pd = pkt_find_dev(q);
+	BUG_ON(!pd);
+
+	if (bio_data_dir(bio) == WRITE)
+		return ELEVATOR_NO_MERGE;
+
+	if (pd->cdrw.elv_merge_fn)
+		return pd->cdrw.elv_merge_fn(q, req, bio);
+
+	return ELEVATOR_NO_MERGE;
+}
+
+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)) {
+		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 int pkt_lowlevel_merge_requests_fn(request_queue_t *q, struct request *rq, struct request *next)
+{
+	struct pktcdvd_device *pd = pkt_find_dev(q);
+	BUG_ON(!pd);
+
+	if (rq_data_dir(rq) == WRITE)
+		return 0;
+
+	return pd->cdrw.merge_requests_fn(q, rq, next);
+}
+
+static void pkt_bio_init(struct bio *bio)
+{
+	bio->bi_next = NULL;
+	bio->bi_flags = 1 << BIO_UPTODATE;
+	bio->bi_rw = 0;
+	bio->bi_vcnt = 0;
+	bio->bi_idx = 0;
+	bio->bi_phys_segments = 0;
+	bio->bi_hw_segments = 0;
+	bio->bi_size = 0;
+	bio->bi_max_vecs = 0;
+	bio->bi_end_io = NULL;
+	atomic_set(&bio->bi_cnt, 1);
+	bio->bi_private = NULL;
+}
+
+static void pkt_bio_destructor(struct bio *bio)
+{
+	kfree(bio->bi_io_vec);
+	kfree(bio);
+}
+
+static struct bio *pkt_bio_alloc(int nr_iovecs)
+{
+	struct bio_vec *bvl = NULL;
+	struct bio *bio;
+
+	bio = kmalloc(sizeof(struct bio), GFP_KERNEL);
+	if (!bio)
+		goto no_bio;
+	pkt_bio_init(bio);
+
+	bvl = kmalloc(nr_iovecs * sizeof(struct bio_vec), GFP_KERNEL);
+	if (!bvl)
+		goto no_bvl;
+	memset(bvl, 0, nr_iovecs * sizeof(struct bio_vec));
+
+	bio->bi_max_vecs = nr_iovecs;
+	bio->bi_io_vec = bvl;
+	bio->bi_destructor = pkt_bio_destructor;
+
+	return bio;
+
+ no_bvl:
+	kfree(bio);
+ no_bio:
+	return NULL;
+}
+
+/*
+ * Allocate a packet_data struct
+ */
+static struct packet_data *pkt_alloc_packet_data(void)
+{
+	int i;
+	struct packet_data *pkt;
+
+	pkt = kmalloc(sizeof(struct packet_data), GFP_KERNEL);
+	if (!pkt)
+		goto no_pkt;
+	memset(pkt, 0, sizeof(struct packet_data));
+
+	pkt->w_bio = pkt_bio_alloc(PACKET_MAX_SIZE);
+	if (!pkt->w_bio)
+		goto no_bio;
+
+	for (i = 0; i < PAGES_PER_PACKET; i++) {
+		pkt->pages[i] = alloc_page(GFP_KERNEL);
+		if (!pkt->pages[i])
+			goto no_page;
+	}
+	for (i = 0; i < PAGES_PER_PACKET; i++)
+		clear_page(page_address(pkt->pages[i]));
+
+	spin_lock_init(&pkt->lock);
+
+	for (i = 0; i < PACKET_MAX_SIZE; i++) {
+		struct bio *bio = pkt_bio_alloc(1);
+		if (!bio)
+			goto no_rd_bio;
+		pkt->r_bios[i] = bio;
+	}
+
+	return pkt;
+
+no_rd_bio:
+	for (i = 0; i < PACKET_MAX_SIZE; i++) {
+		struct bio *bio = pkt->r_bios[i];
+		if (bio)
+			bio_put(bio);
+	}
+
+no_page:
+	for (i = 0; i < PAGES_PER_PACKET; i++)
+		if (pkt->pages[i])
+			__free_page(pkt->pages[i]);
+	bio_put(pkt->w_bio);
+no_bio:
+	kfree(pkt);
+no_pkt:
+	return NULL;
+}
+
+/*
+ * Free a packet_data struct
+ */
+static void pkt_free_packet_data(struct packet_data *pkt)
+{
+	int i;
+
+	for (i = 0; i < PACKET_MAX_SIZE; i++) {
+		struct bio *bio = pkt->r_bios[i];
+		if (bio)
+			bio_put(bio);
+	}
+	for (i = 0; i < PAGES_PER_PACKET; i++)
+		__free_page(pkt->pages[i]);
+	bio_put(pkt->w_bio);
+	kfree(pkt);
+}
+
+static void pkt_shrink_pktlist(struct pktcdvd_device *pd)
+{
+	struct packet_data *pkt, *next;
+
+	BUG_ON(!list_empty(&pd->cdrw.pkt_active_list));
+
+	list_for_each_entry_safe(pkt, next, &pd->cdrw.pkt_free_list, list) {
+		pkt_free_packet_data(pkt);
+	}
+}
+
+static int pkt_grow_pktlist(struct pktcdvd_device *pd, int nr_packets)
+{
+	struct packet_data *pkt;
+
+	INIT_LIST_HEAD(&pd->cdrw.pkt_free_list);
+	INIT_LIST_HEAD(&pd->cdrw.pkt_active_list);
+	spin_lock_init(&pd->cdrw.active_list_lock);
+	while (nr_packets > 0) {
+		pkt = pkt_alloc_packet_data();
+		if (!pkt) {
+			pkt_shrink_pktlist(pd);
+			return 0;
+		}
+		pkt->id = nr_packets;
+		pkt->pd = pd;
+		list_add(&pkt->list, &pd->cdrw.pkt_free_list);
+		nr_packets--;
+	}
+	return 1;
+}
+
+/*
+ * Add a bio to a single linked list defined by its head and tail pointers.
+ */
+static inline void pkt_add_list_last(struct bio *bio, struct bio **list_head, struct bio **list_tail)
+{
+	bio->bi_next = NULL;
+	if (*list_tail) {
+		BUG_ON((*list_head) == NULL);
+		(*list_tail)->bi_next = bio;
+		(*list_tail) = bio;
+	} else {
+		BUG_ON((*list_head) != NULL);
+		(*list_head) = bio;
+		(*list_tail) = bio;
+	}
+}
+
+/*
+ * Remove and return the first bio from a single linked list defined by its
+ * head and tail pointers.
+ */
+static inline struct bio *pkt_get_list_first(struct bio **list_head, struct bio **list_tail)
+{
+	struct bio *bio;
+
+	if (*list_head == NULL)
+		return NULL;
+
+	bio = *list_head;
+	*list_head = bio->bi_next;
+	if (*list_head == NULL)
+		*list_tail = NULL;
+
+	bio->bi_next = NULL;
+	return bio;
+}
+
+/*
+ * Send a packet_command to the underlying block device and
+ * wait for completion.
+ */
+static int pkt_generic_packet(struct pktcdvd_device *pd, struct packet_command *cgc)
+{
+	char sense[SCSI_SENSE_BUFFERSIZE];
+	request_queue_t *q;
+	struct request *rq;
+	DECLARE_COMPLETION(wait);
+	int err = 0;
+
+	if (!pd->bdev) {
+		printk("pkt_generic_packet: no bdev\n");
+		return -ENXIO;
+	}
+
+	q = bdev_get_queue(pd->bdev);
+
+	rq = blk_get_request(q, (cgc->data_direction == CGC_DATA_WRITE) ? WRITE : READ,
+			     __GFP_WAIT);
+	rq->errors = 0;
+	rq->rq_disk = pd->bdev->bd_disk;
+	rq->bio = NULL;
+	rq->buffer = NULL;
+	rq->timeout = 60*HZ;
+	rq->data = cgc->buffer;
+	rq->data_len = cgc->buflen;
+	rq->sense = sense;
+	memset(sense, 0, sizeof(sense));
+	rq->sense_len = 0;
+	rq->flags |= REQ_BLOCK_PC | REQ_HARDBARRIER;
+	if (cgc->quiet)
+		rq->flags |= REQ_QUIET;
+	memcpy(rq->cmd, cgc->cmd, CDROM_PACKET_SIZE);
+	if (sizeof(rq->cmd) > CDROM_PACKET_SIZE)
+		memset(rq->cmd + CDROM_PACKET_SIZE, 0, sizeof(rq->cmd) - CDROM_PACKET_SIZE);
+
+	rq->ref_count++;
+	rq->flags |= REQ_NOMERGE;
+	rq->waiting = &wait;
+	elv_add_request(q, rq, ELEVATOR_INSERT_BACK, 1);
+	generic_unplug_device(q);
+	wait_for_completion(&wait);
+
+	if (rq->errors)
+		err = -EIO;
+
+	blk_put_request(rq);
+	return err;
+}
+
+/*
+ * A generic sense dump / resolve mechanism should be implemented across
+ * all ATAPI + SCSI devices.
+ */
+static void pkt_dump_sense(struct packet_command *cgc)
+{
+	static char *info[9] = { "No sense", "Recovered error", "Not ready",
+				 "Medium error", "Hardware error", "Illegal request",
+				 "Unit attention", "Data protect", "Blank check" };
+	int i;
+	struct request_sense *sense = cgc->sense;
+
+	printk("pktcdvd:");
+	for (i = 0; i < CDROM_PACKET_SIZE; i++)
+		printk(" %02x", cgc->cmd[i]);
+	printk(" - ");
+
+	if (sense == NULL) {
+		printk("no sense\n");
+		return;
+	}
+
+	printk("sense %02x.%02x.%02x", sense->sense_key, sense->asc, sense->ascq);
+
+	if (sense->sense_key > 8) {
+		printk(" (INVALID)\n");
+		return;
+	}
+
+	printk(" (%s)\n", info[sense->sense_key]);
+}
+
+/*
+ * flush the drive cache to media
+ */
+static int pkt_flush_cache(struct pktcdvd_device *pd)
+{
+	struct packet_command cgc;
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_FLUSH_CACHE;
+	cgc.quiet = 1;
+
+	/*
+	 * the IMMED bit -- we default to not setting it, although that
+	 * would allow a much faster close, this is safer
+	 */
+#if 0
+	cgc.cmd[1] = 1 << 1;
+#endif
+	return pkt_generic_packet(pd, &cgc);
+}
+
+/*
+ * speed is given as the normal factor, e.g. 4 for 4x
+ */
+static int pkt_set_speed(struct pktcdvd_device *pd, unsigned write_speed, unsigned read_speed)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	int ret;
+
+	write_speed = write_speed * 177; /* should be 176.4, but CD-RWs rounds down */
+	write_speed = min_t(unsigned, write_speed, 0xffff);
+	read_speed = read_speed * 177;
+	read_speed = min_t(unsigned, read_speed, 0xffff);
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.sense = &sense;
+	cgc.cmd[0] = GPCMD_SET_SPEED;
+	cgc.cmd[2] = (read_speed >> 8) & 0xff;
+	cgc.cmd[3] = read_speed & 0xff;
+	cgc.cmd[4] = (write_speed >> 8) & 0xff;
+	cgc.cmd[5] = write_speed & 0xff;
+
+	if ((ret = pkt_generic_packet(pd, &cgc)))
+		pkt_dump_sense(&cgc);
+
+	return ret;
+}
+
+/*
+ * Queue a bio for processing by the low-level CD device. Must be called
+ * from process context.
+ */
+static void pkt_queue_bio(struct pktcdvd_device *pd, struct bio *bio, int high_prio_read)
+{
+	spin_lock(&pd->iosched.lock);
+	if (bio_data_dir(bio) == READ) {
+		pkt_add_list_last(bio, &pd->iosched.read_queue,
+				  &pd->iosched.read_queue_tail);
+		if (high_prio_read)
+			pd->iosched.high_prio_read = 1;
+	} else {
+		pkt_add_list_last(bio, &pd->iosched.write_queue,
+				  &pd->iosched.write_queue_tail);
+	}
+	spin_unlock(&pd->iosched.lock);
+
+	atomic_set(&pd->iosched.attention, 1);
+	wake_up(&pd->wqueue);
+}
+
+/*
+ * Process the queued read/write requests. This function handles special
+ * requirements for CDRW drives:
+ * - A cache flush command must be inserted before a read request if the
+ *   previous request was a write.
+ * - Switching between reading and writing is slow, so don't it more often
+ *   than necessary.
+ * - Set the read speed according to current usage pattern. When only reading
+ *   from the device, it's best to use the highest possible read speed, but
+ *   when switching often between reading and writing, it's better to have the
+ *   same read and write speeds.
+ * - Reads originating from user space should have higher priority than reads
+ *   originating from pkt_gather_data, because some process is usually waiting
+ *   on reads of the first kind.
+ */
+static void pkt_iosched_process_queue(struct pktcdvd_device *pd)
+{
+	request_queue_t *q;
+
+	if (atomic_read(&pd->iosched.attention) == 0)
+		return;
+	atomic_set(&pd->iosched.attention, 0);
+
+	if (!pd->bdev)
+		return;
+	q = bdev_get_queue(pd->bdev);
+
+	for (;;) {
+		struct bio *bio;
+		int reads_queued, writes_queued, high_prio_read;
+
+		spin_lock(&pd->iosched.lock);
+		reads_queued = (pd->iosched.read_queue != NULL);
+		writes_queued = (pd->iosched.write_queue != NULL);
+		if (!reads_queued)
+			pd->iosched.high_prio_read = 0;
+		high_prio_read = pd->iosched.high_prio_read;
+		spin_unlock(&pd->iosched.lock);
+
+		if (!reads_queued && !writes_queued)
+			break;
+
+		if (pd->iosched.writing) {
+			if (high_prio_read || (!writes_queued && reads_queued)) {
+				if (!elv_queue_empty(q)) {
+					VPRINTK("pktcdvd: write, waiting\n");
+					break;
+				}
+				pkt_flush_cache(pd);
+				pd->iosched.writing = 0;
+			}
+		} else {
+			if (!reads_queued && writes_queued) {
+				if (!elv_queue_empty(q)) {
+					VPRINTK("pktcdvd: read, waiting\n");
+					break;
+				}
+				pd->iosched.writing = 1;
+			}
+		}
+
+		spin_lock(&pd->iosched.lock);
+		if (pd->iosched.writing) {
+			bio = pkt_get_list_first(&pd->iosched.write_queue,
+						 &pd->iosched.write_queue_tail);
+		} else {
+			bio = pkt_get_list_first(&pd->iosched.read_queue,
+						 &pd->iosched.read_queue_tail);
+		}
+		spin_unlock(&pd->iosched.lock);
+
+		if (!bio)
+			continue;
+
+		if (bio_data_dir(bio) == READ)
+			pd->iosched.successive_reads += bio->bi_size >> 10;
+		else
+			pd->iosched.successive_reads = 0;
+		if (pd->iosched.successive_reads >= HI_SPEED_SWITCH) {
+			if (pd->read_speed == pd->write_speed) {
+				pd->read_speed = 0xff;
+				pkt_set_speed(pd, pd->write_speed, pd->read_speed);
+			}
+		} else {
+			if (pd->read_speed != pd->write_speed) {
+				pd->read_speed = pd->write_speed;
+				pkt_set_speed(pd, pd->write_speed, pd->read_speed);
+			}
+		}
+
+		generic_make_request(bio);
+	}
+}
+
+/*
+ * Special care is needed if the underlying block device has a small
+ * max_phys_segments value.
+ */
+static int pkt_set_segment_merging(struct pktcdvd_device *pd, request_queue_t *q)
+{
+	if ((pd->settings.size << 9) / CD_FRAMESIZE <= q->max_phys_segments) {
+		/*
+		 * The cdrom device can handle one segment/frame
+		 */
+		clear_bit(PACKET_MERGE_SEGS, &pd->flags);
+		return 0;
+	} else if ((pd->settings.size << 9) / PAGE_SIZE <= q->max_phys_segments) {
+		/*
+		 * We can handle this case at the expense of some extra memory
+		 * copies during write operations
+		 */
+		set_bit(PACKET_MERGE_SEGS, &pd->flags);
+		return 0;
+	} else {
+		printk("pktcdvd: cdrom max_phys_segments too small\n");
+		return -EIO;
+	}
+}
+
+/*
+ * Copy CD_FRAMESIZE bytes from src_bio into a destination page
+ */
+static void pkt_copy_bio_data(struct bio *src_bio, int seg, int offs,
+			      struct page *dst_page, int dst_offs)
+{
+	unsigned int copy_size = CD_FRAMESIZE;
+
+	while (copy_size > 0) {
+		struct bio_vec *src_bvl = bio_iovec_idx(src_bio, seg);
+		void *vfrom = kmap_atomic(src_bvl->bv_page, KM_USER0) +
+			src_bvl->bv_offset + offs;
+		void *vto = page_address(dst_page) + dst_offs;
+		int len = min_t(int, copy_size, src_bvl->bv_len - offs);
+
+		BUG_ON(len < 0);
+		memcpy(vto, vfrom, len);
+		kunmap_atomic(src_bvl->bv_page, KM_USER0);
+
+		seg++;
+		offs = 0;
+		dst_offs += len;
+		copy_size -= len;
+	}
+}
+
+/*
+ * Copy all data for this packet to pkt->pages[], so that
+ * a) The number of required segments for the write bio is minimized, which
+ *    is necessary for some scsi controllers.
+ * b) The data can be used as cache to avoid read requests if we receive a
+ *    new write request for the same zone.
+ */
+static void pkt_make_local_copy(struct packet_data *pkt, struct page **pages, int *offsets)
+{
+	int f, p, offs;
+
+	/* Copy all data to pkt->pages[] */
+	p = 0;
+	offs = 0;
+	for (f = 0; f < pkt->frames; f++) {
+		if (pages[f] != pkt->pages[p]) {
+			void *vfrom = kmap_atomic(pages[f], KM_USER0) + offsets[f];
+			void *vto = page_address(pkt->pages[p]) + offs;
+			memcpy(vto, vfrom, CD_FRAMESIZE);
+			kunmap_atomic(pages[f], KM_USER0);
+			pages[f] = pkt->pages[p];
+			offsets[f] = offs;
+		} else {
+			BUG_ON(offsets[f] != offs);
+		}
+		offs += CD_FRAMESIZE;
+		if (offs >= PAGE_SIZE) {
+			BUG_ON(offs > PAGE_SIZE);
+			offs = 0;
+			p++;
+		}
+	}
+}
+
+static int pkt_end_io_read(struct bio *bio, unsigned int bytes_done, int err)
+{
+	struct packet_data *pkt = bio->bi_private;
+	struct pktcdvd_device *pd = pkt->pd;
+	BUG_ON(!pd);
+
+	if (bio->bi_size)
+		return 1;
+
+	VPRINTK("pkt_end_io_read: bio=%p sec0=%llx sec=%llx err=%d\n", bio,
+		(unsigned long long)pkt->sector, (unsigned long long)bio->bi_sector, err);
+
+	if (err)
+		atomic_inc(&pkt->io_errors);
+	if (atomic_dec_and_test(&pkt->io_wait)) {
+		atomic_inc(&pkt->run_sm);
+		wake_up(&pd->wqueue);
+	}
+
+	return 0;
+}
+
+static int pkt_end_io_packet_write(struct bio *bio, unsigned int bytes_done, int err)
+{
+	struct packet_data *pkt = bio->bi_private;
+	struct pktcdvd_device *pd = pkt->pd;
+	BUG_ON(!pd);
+
+	if (bio->bi_size)
+		return 1;
+
+	VPRINTK("pkt_end_io_packet_write: id=%d, err=%d\n", pkt->id, err);
+
+	pd->stats.pkt_ended++;
+
+	atomic_dec(&pkt->io_wait);
+	atomic_inc(&pkt->run_sm);
+	wake_up(&pd->wqueue);
+	return 0;
+}
+
+/*
+ * Schedule reads for the holes in a packet
+ */
+static void pkt_gather_data(struct pktcdvd_device *pd, struct packet_data *pkt)
+{
+	int frames_read = 0;
+	struct bio *bio;
+	int f;
+	char written[PACKET_MAX_SIZE];
+
+	BUG_ON(!pkt->orig_bios);
+
+	atomic_set(&pkt->io_wait, 0);
+	atomic_set(&pkt->io_errors, 0);
+
+	if (pkt->cache_valid) {
+		VPRINTK("pkt_gather_data: zone %llx cached\n",
+			(unsigned long long)pkt->sector);
+		goto out_account;
+	}
+
+	/*
+	 * Figure out which frames we need to read before we can write.
+	 */
+	memset(written, 0, sizeof(written));
+	spin_lock(&pkt->lock);
+	for (bio = pkt->orig_bios; bio; bio = bio->bi_next) {
+		int first_frame = (bio->bi_sector - pkt->sector) / (CD_FRAMESIZE >> 9);
+		int num_frames = bio->bi_size / CD_FRAMESIZE;
+		BUG_ON(first_frame < 0);
+		BUG_ON(first_frame + num_frames > pkt->frames);
+		for (f = first_frame; f < first_frame + num_frames; f++)
+			written[f] = 1;
+	}
+	spin_unlock(&pkt->lock);
+
+	/*
+	 * Schedule reads for missing parts of the packet.
+	 */
+	for (f = 0; f < pkt->frames; f++) {
+		int p, offset;
+		if (written[f])
+			continue;
+		bio = pkt->r_bios[f];
+		pkt_bio_init(bio);
+		bio->bi_max_vecs = 1;
+		bio->bi_sector = pkt->sector + f * (CD_FRAMESIZE >> 9);
+		bio->bi_bdev = pd->bdev;
+		bio->bi_end_io = pkt_end_io_read;
+		bio->bi_private = pkt;
+
+		p = (f * CD_FRAMESIZE) / PAGE_SIZE;
+		offset = (f * CD_FRAMESIZE) % PAGE_SIZE;
+		VPRINTK("pkt_gather_data: Adding frame %d, page:%p offs:%d\n",
+			f, pkt->pages[p], offset);
+		if (!bio_add_page(bio, pkt->pages[p], CD_FRAMESIZE, offset))
+			BUG();
+
+		atomic_inc(&pkt->io_wait);
+		bio->bi_rw = READ;
+		pkt_queue_bio(pd, bio, 0);
+		frames_read++;
+	}
+
+out_account:
+	VPRINTK("pkt_gather_data: need %d frames for zone %llx\n",
+		frames_read, (unsigned long long)pkt->sector);
+	pd->stats.pkt_started++;
+	pd->stats.secs_rg += frames_read * (CD_FRAMESIZE >> 9);
+	pd->stats.secs_w += pd->settings.size;
+}
+
+/*
+ * Find a packet matching zone, or the least recently used packet if
+ * there is no match.
+ */
+static struct packet_data *pkt_get_packet_data(struct pktcdvd_device *pd, int zone)
+{
+	struct packet_data *pkt;
+
+	list_for_each_entry(pkt, &pd->cdrw.pkt_free_list, list) {
+		if (pkt->sector == zone || pkt->list.next == &pd->cdrw.pkt_free_list) {
+			list_del_init(&pkt->list);
+			if (pkt->sector != zone)
+				pkt->cache_valid = 0;
+			break;
+		}
+	}
+	return pkt;
+}
+
+static void pkt_put_packet_data(struct pktcdvd_device *pd, struct packet_data *pkt)
+{
+	if (pkt->cache_valid) {
+		list_add(&pkt->list, &pd->cdrw.pkt_free_list);
+	} else {
+		list_add_tail(&pkt->list, &pd->cdrw.pkt_free_list);
+	}
+}
+
+/*
+ * recover a failed write, query for relocation if possible
+ *
+ * returns 1 if recovery is possible, or 0 if not
+ *
+ */
+static int pkt_start_recovery(struct packet_data *pkt)
+{
+	/*
+	 * FIXME. We need help from the file system to implement
+	 * recovery handling.
+	 */
+	return 0;
+#if 0
+	struct request *rq = pkt->rq;
+	struct pktcdvd_device *pd = rq->rq_disk->private_data;
+	struct block_device *pkt_bdev;
+	struct super_block *sb = NULL;
+	unsigned long old_block, new_block;
+	sector_t new_sector;
+
+	pkt_bdev = bdget(kdev_t_to_nr(pd->pkt_dev));
+	if (pkt_bdev) {
+		sb = get_super(pkt_bdev);
+		bdput(pkt_bdev);
+	}
+
+	if (!sb)
+		return 0;
+
+	if (!sb->s_op || !sb->s_op->relocate_blocks)
+		goto out;
+
+	old_block = pkt->sector / (CD_FRAMESIZE >> 9);
+	if (sb->s_op->relocate_blocks(sb, old_block, &new_block))
+		goto out;
+
+	new_sector = new_block * (CD_FRAMESIZE >> 9);
+	pkt->sector = new_sector;
+
+	pkt->bio->bi_sector = new_sector;
+	pkt->bio->bi_next = NULL;
+	pkt->bio->bi_flags = 1 << BIO_UPTODATE;
+	pkt->bio->bi_idx = 0;
+
+	BUG_ON(pkt->bio->bi_rw != (1 << BIO_RW));
+	BUG_ON(pkt->bio->bi_vcnt != pkt->frames);
+	BUG_ON(pkt->bio->bi_size != pkt->frames * CD_FRAMESIZE);
+	BUG_ON(pkt->bio->bi_end_io != pkt_end_io_packet_write);
+	BUG_ON(pkt->bio->bi_private != pkt);
+
+	drop_super(sb);
+	return 1;
+
+out:
+	drop_super(sb);
+	return 0;
+#endif
+}
+
+static inline void pkt_set_state(struct packet_data *pkt, enum packet_data_state state)
+{
+#if PACKET_DEBUG > 1
+	static const char *state_name[] = {
+		"IDLE", "WAITING", "READ_WAIT", "WRITE_WAIT", "RECOVERY", "FINISHED"
+	};
+	enum packet_data_state old_state = pkt->state;
+	VPRINTK("pkt %2d : s=%6llx %s -> %s\n", pkt->id, (unsigned long long)pkt->sector,
+		state_name[old_state], state_name[state]);
+#endif
+	pkt->state = state;
+}
+
+/*
+ * Scan the work queue to see if we can start a new packet.
+ * returns non-zero if any work was done.
+ */
+static int pkt_handle_queue(struct pktcdvd_device *pd)
+{
+	struct packet_data *pkt, *p;
+	struct bio *bio, *prev, *next;
+	sector_t zone = 0; /* Suppress gcc warning */
+
+	VPRINTK("handle_queue\n");
+
+	atomic_set(&pd->scan_queue, 0);
+
+	if (list_empty(&pd->cdrw.pkt_free_list)) {
+		VPRINTK("handle_queue: no pkt\n");
+		return 0;
+	}
+
+	/*
+	 * Try to find a zone we are not already working on.
+	 */
+	spin_lock(&pd->lock);
+	for (bio = pd->bio_queue; bio; bio = bio->bi_next) {
+		zone = ZONE(bio->bi_sector, pd);
+		list_for_each_entry(p, &pd->cdrw.pkt_active_list, list) {
+			if (p->sector == zone)
+				goto try_next_bio;
+		}
+		break;
+try_next_bio: ;
+	}
+	spin_unlock(&pd->lock);
+	if (!bio) {
+		VPRINTK("handle_queue: no bio\n");
+		return 0;
+	}
+
+	pkt = pkt_get_packet_data(pd, zone);
+	BUG_ON(!pkt);
+
+	pkt->sector = zone;
+	pkt->frames = pd->settings.size >> 2;
+	BUG_ON(pkt->frames > PACKET_MAX_SIZE);
+	pkt->write_size = 0;
+
+	/*
+	 * Scan work queue for bios in the same zone and link them
+	 * to this packet.
+	 */
+	spin_lock(&pd->lock);
+	prev = NULL;
+	VPRINTK("pkt_handle_queue: looking for zone %llx\n", (unsigned long long)zone);
+	bio = pd->bio_queue;
+	while (bio) {
+		VPRINTK("pkt_handle_queue: found zone=%llx\n",
+			(unsigned long long)ZONE(bio->bi_sector, pd));
+		if (ZONE(bio->bi_sector, pd) == zone) {
+			if (prev) {
+				prev->bi_next = bio->bi_next;
+			} else {
+				pd->bio_queue = bio->bi_next;
+			}
+			if (bio == pd->bio_queue_tail)
+				pd->bio_queue_tail = prev;
+			next = bio->bi_next;
+			spin_lock(&pkt->lock);
+			pkt_add_list_last(bio, &pkt->orig_bios,
+					  &pkt->orig_bios_tail);
+			pkt->write_size += bio->bi_size / CD_FRAMESIZE;
+			if (pkt->write_size >= pkt->frames) {
+				VPRINTK("pkt_handle_queue: pkt is full\n");
+				next = NULL; /* Stop searching if the packet is full */
+			}
+			spin_unlock(&pkt->lock);
+			bio = next;
+		} else {
+			prev = bio;
+			bio = bio->bi_next;
+		}
+	}
+	spin_unlock(&pd->lock);
+
+	pkt->sleep_time = max(PACKET_WAIT_TIME, 1);
+	pkt_set_state(pkt, PACKET_WAITING_STATE);
+	atomic_set(&pkt->run_sm, 1);
+
+	spin_lock(&pd->cdrw.active_list_lock);
+	list_add(&pkt->list, &pd->cdrw.pkt_active_list);
+	spin_unlock(&pd->cdrw.active_list_lock);
+
+	return 1;
+}
+
+/*
+ * Assemble a bio to write one packet and queue the bio for processing
+ * by the underlying block device.
+ */
+static void pkt_start_write(struct pktcdvd_device *pd, struct packet_data *pkt)
+{
+	struct bio *bio;
+	struct page *pages[PACKET_MAX_SIZE];
+	int offsets[PACKET_MAX_SIZE];
+	int f;
+	int frames_write;
+
+	for (f = 0; f < pkt->frames; f++) {
+		pages[f] = pkt->pages[(f * CD_FRAMESIZE) / PAGE_SIZE];
+		offsets[f] = (f * CD_FRAMESIZE) % PAGE_SIZE;
+	}
+
+	/*
+	 * Fill-in pages[] and offsets[] with data from orig_bios.
+	 */
+	frames_write = 0;
+	spin_lock(&pkt->lock);
+	for (bio = pkt->orig_bios; bio; bio = bio->bi_next) {
+		int segment = bio->bi_idx;
+		int src_offs = 0;
+		int first_frame = (bio->bi_sector - pkt->sector) / (CD_FRAMESIZE >> 9);
+		int num_frames = bio->bi_size / CD_FRAMESIZE;
+		BUG_ON(first_frame < 0);
+		BUG_ON(first_frame + num_frames > pkt->frames);
+		for (f = first_frame; f < first_frame + num_frames; f++) {
+			struct bio_vec *src_bvl = bio_iovec_idx(bio, segment);
+
+			while (src_offs >= src_bvl->bv_len) {
+				src_offs -= src_bvl->bv_len;
+				segment++;
+				BUG_ON(segment >= bio->bi_vcnt);
+				src_bvl = bio_iovec_idx(bio, segment);
+			}
+
+			if (src_bvl->bv_len - src_offs >= CD_FRAMESIZE) {
+				pages[f] = src_bvl->bv_page;
+				offsets[f] = src_bvl->bv_offset + src_offs;
+			} else {
+				pkt_copy_bio_data(bio, segment, src_offs,
+						  pages[f], offsets[f]);
+			}
+			src_offs += CD_FRAMESIZE;
+			frames_write++;
+		}
+	}
+	pkt_set_state(pkt, PACKET_WRITE_WAIT_STATE);
+	spin_unlock(&pkt->lock);
+
+	VPRINTK("pkt_start_write: Writing %d frames for zone %llx\n",
+		frames_write, (unsigned long long)pkt->sector);
+	BUG_ON(frames_write != pkt->write_size);
+
+	if (test_bit(PACKET_MERGE_SEGS, &pd->flags) || (pkt->write_size < pkt->frames)) {
+		pkt_make_local_copy(pkt, pages, offsets);
+		pkt->cache_valid = 1;
+	} else {
+		pkt->cache_valid = 0;
+	}
+
+	/* Start the write request */
+	pkt_bio_init(pkt->w_bio);
+	pkt->w_bio->bi_max_vecs = PACKET_MAX_SIZE;
+	pkt->w_bio->bi_sector = pkt->sector;
+	pkt->w_bio->bi_bdev = pd->bdev;
+	pkt->w_bio->bi_end_io = pkt_end_io_packet_write;
+	pkt->w_bio->bi_private = pkt;
+	for (f = 0; f < pkt->frames; f++) {
+		if ((f + 1 < pkt->frames) && (pages[f + 1] == pages[f]) &&
+		    (offsets[f + 1] = offsets[f] + CD_FRAMESIZE)) {
+			if (!bio_add_page(pkt->w_bio, pages[f], CD_FRAMESIZE * 2, offsets[f]))
+				BUG();
+			f++;
+		} else {
+			if (!bio_add_page(pkt->w_bio, pages[f], CD_FRAMESIZE, offsets[f]))
+				BUG();
+		}
+	}
+	VPRINTK("pktcdvd: vcnt=%d\n", pkt->w_bio->bi_vcnt);
+
+	atomic_set(&pkt->io_wait, 1);
+	pkt->w_bio->bi_rw = WRITE;
+	pkt_queue_bio(pd, pkt->w_bio, 0);
+}
+
+static void pkt_finish_packet(struct packet_data *pkt, int uptodate)
+{
+	struct bio *bio, *next;
+
+	if (!uptodate)
+		pkt->cache_valid = 0;
+
+	/* Finish all bios corresponding to this packet */
+	bio = pkt->orig_bios;
+	while (bio) {
+		next = bio->bi_next;
+		bio->bi_next = NULL;
+		bio_endio(bio, bio->bi_size, uptodate ? 0 : -EIO);
+		bio = next;
+	}
+	pkt->orig_bios = pkt->orig_bios_tail = NULL;
+}
+
+static void pkt_run_state_machine(struct pktcdvd_device *pd, struct packet_data *pkt)
+{
+	int uptodate;
+
+	VPRINTK("run_state_machine: pkt %d\n", pkt->id);
+
+	for (;;) {
+		switch (pkt->state) {
+		case PACKET_WAITING_STATE:
+			if ((pkt->write_size < pkt->frames) && (pkt->sleep_time > 0))
+				return;
+
+			pkt->sleep_time = 0;
+			pkt_gather_data(pd, pkt);
+			pkt_set_state(pkt, PACKET_READ_WAIT_STATE);
+			break;
+
+		case PACKET_READ_WAIT_STATE:
+			if (atomic_read(&pkt->io_wait) > 0)
+				return;
+
+			if (atomic_read(&pkt->io_errors) > 0) {
+				pkt_set_state(pkt, PACKET_RECOVERY_STATE);
+			} else {
+				pkt_start_write(pd, pkt);
+			}
+			break;
+
+		case PACKET_WRITE_WAIT_STATE:
+			if (atomic_read(&pkt->io_wait) > 0)
+				return;
+
+			if (test_bit(BIO_UPTODATE, &pkt->w_bio->bi_flags)) {
+				pkt_set_state(pkt, PACKET_FINISHED_STATE);
+			} else {
+				pkt_set_state(pkt, PACKET_RECOVERY_STATE);
+			}
+			break;
+
+		case PACKET_RECOVERY_STATE:
+			if (pkt_start_recovery(pkt)) {
+				pkt_start_write(pd, pkt);
+			} else {
+				VPRINTK("No recovery possible\n");
+				pkt_set_state(pkt, PACKET_FINISHED_STATE);
+			}
+			break;
+
+		case PACKET_FINISHED_STATE:
+			uptodate = test_bit(BIO_UPTODATE, &pkt->w_bio->bi_flags);
+			pkt_finish_packet(pkt, uptodate);
+			return;
+
+		default:
+			BUG();
+			break;
+		}
+	}
+}
+
+static void pkt_handle_packets(struct pktcdvd_device *pd)
+{
+	struct packet_data *pkt, *next;
+
+	VPRINTK("pkt_handle_packets\n");
+
+	/*
+	 * Run state machine for active packets
+	 */
+	list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+		if (atomic_read(&pkt->run_sm) > 0) {
+			atomic_set(&pkt->run_sm, 0);
+			pkt_run_state_machine(pd, pkt);
+		}
+	}
+
+	/*
+	 * Move no longer active packets to the free list
+	 */
+	spin_lock(&pd->cdrw.active_list_lock);
+	list_for_each_entry_safe(pkt, next, &pd->cdrw.pkt_active_list, list) {
+		if (pkt->state == PACKET_FINISHED_STATE) {
+			list_del(&pkt->list);
+			pkt_put_packet_data(pd, pkt);
+			pkt_set_state(pkt, PACKET_IDLE_STATE);
+			atomic_set(&pd->scan_queue, 1);
+		}
+	}
+	spin_unlock(&pd->cdrw.active_list_lock);
+}
+
+static void pkt_count_states(struct pktcdvd_device *pd, int *states)
+{
+	struct packet_data *pkt;
+	int i;
+
+	for (i = 0; i <= PACKET_NUM_STATES; i++)
+		states[i] = 0;
+
+	spin_lock(&pd->cdrw.active_list_lock);
+	list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+		states[pkt->state]++;
+	}
+	spin_unlock(&pd->cdrw.active_list_lock);
+}
+
+/*
+ * kcdrwd is woken up when writes have been queued for one of our
+ * registered devices
+ */
+static int kcdrwd(void *foobar)
+{
+	struct pktcdvd_device *pd = foobar;
+	struct packet_data *pkt;
+	long min_sleep_time, residue;
+
+	/*
+	 * exit_files, mm (move to lazy-tlb, so context switches are come
+	 * extremely cheap) etc
+	 */
+	daemonize(pd->name);
+
+	set_user_nice(current, -20);
+	sprintf(current->comm, pd->name);
+
+	siginitsetinv(&current->blocked, sigmask(SIGKILL));
+	flush_signals(current);
+
+	for (;;) {
+		DECLARE_WAITQUEUE(wait, current);
+
+		/*
+		 * Wait until there is something to do
+		 */
+		add_wait_queue(&pd->wqueue, &wait);
+		for (;;) {
+			set_current_state(TASK_INTERRUPTIBLE);
+
+			/* Check if we need to run pkt_handle_queue */
+			if (atomic_read(&pd->scan_queue) > 0)
+				goto work_to_do;
+
+			/* Check if we need to run the state machine for some packet */
+			list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+				if (atomic_read(&pkt->run_sm) > 0)
+					goto work_to_do;
+			}
+
+			/* Check if we need to process the iosched queues */
+			if (atomic_read(&pd->iosched.attention) != 0)
+				goto work_to_do;
+
+			/* Otherwise, go to sleep */
+			if (PACKET_DEBUG > 1) {
+				int states[PACKET_NUM_STATES];
+				pkt_count_states(pd, states);
+				VPRINTK("kcdrwd: i:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n",
+					states[0], states[1], states[2], states[3],
+					states[4], states[5]);
+			}
+
+			min_sleep_time = MAX_SCHEDULE_TIMEOUT;
+			list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+				if (pkt->sleep_time && pkt->sleep_time < min_sleep_time)
+					min_sleep_time = pkt->sleep_time;
+			}
+
+			if (pd->bdev) {
+				request_queue_t *q;
+				q = bdev_get_queue(pd->bdev);
+				generic_unplug_device(q);
+			}
+
+			VPRINTK("kcdrwd: sleeping\n");
+			residue = schedule_timeout(min_sleep_time);
+			VPRINTK("kcdrwd: wake up\n");
+
+			/* make swsusp happy with our thread */
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
+
+			list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+				if (!pkt->sleep_time)
+					continue;
+				pkt->sleep_time -= min_sleep_time - residue;
+				if (pkt->sleep_time <= 0) {
+					pkt->sleep_time = 0;
+					atomic_inc(&pkt->run_sm);
+				}
+			}
+
+			if (signal_pending(current)) {
+				flush_signals(current);
+			}
+			if (pd->cdrw.time_to_die)
+				break;
+		}
+work_to_do:
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&pd->wqueue, &wait);
+
+		if (pd->cdrw.time_to_die)
+			break;
+
+		/*
+		 * if pkt_handle_queue returns true, we can queue
+		 * another request.
+		 */
+		while (pkt_handle_queue(pd))
+			;
+
+		/*
+		 * Handle packet state machine
+		 */
+		pkt_handle_packets(pd);
+
+		/*
+		 * Handle iosched queues
+		 */
+		pkt_iosched_process_queue(pd);
+	}
+
+	complete_and_exit(&pd->cdrw.thr_compl, 0);
+	return 0;
+}
+
+static void pkt_print_settings(struct pktcdvd_device *pd)
+{
+	printk("pktcdvd: %s packets, ", pd->settings.fp ? "Fixed" : "Variable");
+	printk("%u blocks, ", pd->settings.size >> 2);
+	printk("Mode-%c disc\n", pd->settings.block_mode == 8 ? '1' : '2');
+}
+
+static int pkt_mode_sense(struct pktcdvd_device *pd, struct packet_command *cgc,
+			  int page_code, int page_control)
+{
+	memset(cgc->cmd, 0, sizeof(cgc->cmd));
+
+	cgc->cmd[0] = GPCMD_MODE_SENSE_10;
+	cgc->cmd[2] = page_code | (page_control << 6);
+	cgc->cmd[7] = cgc->buflen >> 8;
+	cgc->cmd[8] = cgc->buflen & 0xff;
+	cgc->data_direction = CGC_DATA_READ;
+	return pkt_generic_packet(pd, cgc);
+}
+
+static int pkt_mode_select(struct pktcdvd_device *pd, struct packet_command *cgc)
+{
+	memset(cgc->cmd, 0, sizeof(cgc->cmd));
+	memset(cgc->buffer, 0, 2);
+	cgc->cmd[0] = GPCMD_MODE_SELECT_10;
+	cgc->cmd[1] = 0x10;		/* PF */
+	cgc->cmd[7] = cgc->buflen >> 8;
+	cgc->cmd[8] = cgc->buflen & 0xff;
+	cgc->data_direction = CGC_DATA_WRITE;
+	return pkt_generic_packet(pd, cgc);
+}
+
+static int pkt_get_disc_info(struct pktcdvd_device *pd, disc_information *di)
+{
+	struct packet_command cgc;
+	int ret;
+
+	/* set up command and get the disc info */
+	init_cdrom_command(&cgc, di, sizeof(*di), CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_DISC_INFO;
+	cgc.cmd[8] = cgc.buflen = 2;
+	cgc.quiet = 1;
+
+	if ((ret = pkt_generic_packet(pd, &cgc)))
+		return ret;
+
+	/* not all drives have the same disc_info length, so requeue
+	 * packet with the length the drive tells us it can supply
+	 */
+	cgc.buflen = be16_to_cpu(di->disc_information_length) +
+		     sizeof(di->disc_information_length);
+
+	if (cgc.buflen > sizeof(disc_information))
+		cgc.buflen = sizeof(disc_information);
+
+	cgc.cmd[8] = cgc.buflen;
+	return pkt_generic_packet(pd, &cgc);
+}
+
+static int pkt_get_track_info(struct pktcdvd_device *pd, __u16 track, __u8 type, track_information *ti)
+{
+	struct packet_command cgc;
+	int ret;
+
+	init_cdrom_command(&cgc, ti, 8, CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_TRACK_RZONE_INFO;
+	cgc.cmd[1] = type & 3;
+	cgc.cmd[4] = (track & 0xff00) >> 8;
+	cgc.cmd[5] = track & 0xff;
+	cgc.cmd[8] = 8;
+	cgc.quiet = 1;
+
+	if ((ret = pkt_generic_packet(pd, &cgc)))
+		return ret;
+
+	cgc.buflen = be16_to_cpu(ti->track_information_length) +
+		     sizeof(ti->track_information_length);
+
+	if (cgc.buflen > sizeof(track_information))
+		cgc.buflen = sizeof(track_information);
+
+	cgc.cmd[8] = cgc.buflen;
+	return pkt_generic_packet(pd, &cgc);
+}
+
+static int pkt_get_last_written(struct pktcdvd_device *pd, long *last_written)
+{
+	disc_information di;
+	track_information ti;
+	__u32 last_track;
+	int ret = -1;
+
+	if ((ret = pkt_get_disc_info(pd, &di)))
+		return ret;
+
+	last_track = (di.last_track_msb << 8) | di.last_track_lsb;
+	if ((ret = pkt_get_track_info(pd, last_track, 1, &ti)))
+		return ret;
+
+	/* if this track is blank, try the previous. */
+	if (ti.blank) {
+		last_track--;
+		if ((ret = pkt_get_track_info(pd, last_track, 1, &ti)))
+			return ret;
+	}
+
+	/* if last recorded field is valid, return it. */
+	if (ti.lra_v) {
+		*last_written = be32_to_cpu(ti.last_rec_address);
+	} else {
+		/* make it up instead */
+		*last_written = be32_to_cpu(ti.track_start) +
+				be32_to_cpu(ti.track_size);
+		if (ti.free_blocks)
+			*last_written -= (be32_to_cpu(ti.free_blocks) + 7);
+	}
+	return 0;
+}
+
+/*
+ * write mode select package based on pd->settings
+ */
+static int pkt_set_write_settings(struct pktcdvd_device *pd)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	write_param_page *wp;
+	char buffer[128];
+	int ret, size;
+
+	memset(buffer, 0, sizeof(buffer));
+	init_cdrom_command(&cgc, buffer, sizeof(*wp), CGC_DATA_READ);
+	cgc.sense = &sense;
+	if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WRITE_PARMS_PAGE, 0))) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+
+	size = 2 + ((buffer[0] << 8) | (buffer[1] & 0xff));
+	pd->mode_offset = (buffer[6] << 8) | (buffer[7] & 0xff);
+	if (size > sizeof(buffer))
+		size = sizeof(buffer);
+
+	/*
+	 * now get it all
+	 */
+	init_cdrom_command(&cgc, buffer, size, CGC_DATA_READ);
+	cgc.sense = &sense;
+	if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WRITE_PARMS_PAGE, 0))) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+
+	/*
+	 * write page is offset header + block descriptor length
+	 */
+	wp = (write_param_page *) &buffer[sizeof(struct mode_page_header) + pd->mode_offset];
+
+	wp->fp = pd->settings.fp;
+	wp->track_mode = pd->settings.track_mode;
+	wp->write_type = pd->settings.write_type;
+	wp->data_block_type = pd->settings.block_mode;
+
+	wp->multi_session = 0;
+
+#ifdef PACKET_USE_LS
+	wp->link_size = 7;
+	wp->ls_v = 1;
+#endif
+
+	if (wp->data_block_type == PACKET_BLOCK_MODE1) {
+		wp->session_format = 0;
+		wp->subhdr2 = 0x20;
+	} else if (wp->data_block_type == PACKET_BLOCK_MODE2) {
+		wp->session_format = 0x20;
+		wp->subhdr2 = 8;
+#if 0
+		wp->mcn[0] = 0x80;
+		memcpy(&wp->mcn[1], PACKET_MCN, sizeof(wp->mcn) - 1);
+#endif
+	} else {
+		/*
+		 * paranoia
+		 */
+		printk("pktcdvd: write mode wrong %d\n", wp->data_block_type);
+		return 1;
+	}
+	wp->packet_size = cpu_to_be32(pd->settings.size >> 2);
+
+	cgc.buflen = cgc.cmd[8] = size;
+	if ((ret = pkt_mode_select(pd, &cgc))) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+
+	pkt_print_settings(pd);
+	return 0;
+}
+
+/*
+ * 0 -- we can write to this track, 1 -- we can't
+ */
+static int pkt_good_track(track_information *ti)
+{
+	/*
+	 * only good for CD-RW at the moment, not DVD-RW
+	 */
+
+	/*
+	 * FIXME: only for FP
+	 */
+	if (ti->fp == 0)
+		return 0;
+
+	/*
+	 * "good" settings as per Mt Fuji.
+	 */
+	if (ti->rt == 0 && ti->blank == 0 && ti->packet == 1)
+		return 0;
+
+	if (ti->rt == 0 && ti->blank == 1 && ti->packet == 1)
+		return 0;
+
+	if (ti->rt == 1 && ti->blank == 0 && ti->packet == 1)
+		return 0;
+
+	printk("pktcdvd: bad state %d-%d-%d\n", ti->rt, ti->blank, ti->packet);
+	return 1;
+}
+
+/*
+ * 0 -- we can write to this disc, 1 -- we can't
+ */
+static int pkt_good_disc(struct pktcdvd_device *pd, disc_information *di)
+{
+	/*
+	 * for disc type 0xff we should probably reserve a new track.
+	 * but i'm not sure, should we leave this to user apps? probably.
+	 */
+	if (di->disc_type == 0xff) {
+		printk("pktcdvd: Unknown disc. No track?\n");
+		return 1;
+	}
+
+	if (di->disc_type != 0x20 && di->disc_type != 0) {
+		printk("pktcdvd: Wrong disc type (%x)\n", di->disc_type);
+		return 1;
+	}
+
+	if (di->erasable == 0) {
+		printk("pktcdvd: Disc not erasable\n");
+		return 1;
+	}
+
+	if (di->border_status == PACKET_SESSION_RESERVED) {
+		printk("pktcdvd: Can't write to last track (reserved)\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static int pkt_probe_settings(struct pktcdvd_device *pd)
+{
+	disc_information di;
+	track_information ti;
+	int ret, track;
+
+	memset(&di, 0, sizeof(disc_information));
+	memset(&ti, 0, sizeof(track_information));
+
+	if ((ret = pkt_get_disc_info(pd, &di))) {
+		printk("failed get_disc\n");
+		return ret;
+	}
+
+	if (pkt_good_disc(pd, &di))
+		return -ENXIO;
+
+	printk("pktcdvd: inserted media is CD-R%s\n", di.erasable ? "W" : "");
+	pd->type = di.erasable ? PACKET_CDRW : PACKET_CDR;
+
+	track = 1; /* (di.last_track_msb << 8) | di.last_track_lsb; */
+	if ((ret = pkt_get_track_info(pd, track, 1, &ti))) {
+		printk("pktcdvd: failed get_track\n");
+		return ret;
+	}
+
+	if (pkt_good_track(&ti)) {
+		printk("pktcdvd: can't write to this track\n");
+		return -ENXIO;
+	}
+
+	/*
+	 * we keep packet size in 512 byte units, makes it easier to
+	 * deal with request calculations.
+	 */
+	pd->settings.size = be32_to_cpu(ti.fixed_packet_size) << 2;
+	if (pd->settings.size == 0) {
+		printk("pktcdvd: detected zero packet size!\n");
+		pd->settings.size = 128;
+	}
+	pd->settings.fp = ti.fp;
+	pd->offset = (be32_to_cpu(ti.track_start) << 2) & (pd->settings.size - 1);
+
+	if (ti.nwa_v) {
+		pd->nwa = be32_to_cpu(ti.next_writable);
+		set_bit(PACKET_NWA_VALID, &pd->flags);
+	}
+
+	/*
+	 * in theory we could use lra on -RW media as well and just zero
+	 * blocks that haven't been written yet, but in practice that
+	 * is just a no-go. we'll use that for -R, naturally.
+	 */
+	if (ti.lra_v) {
+		pd->lra = be32_to_cpu(ti.last_rec_address);
+		set_bit(PACKET_LRA_VALID, &pd->flags);
+	} else {
+		pd->lra = 0xffffffff;
+		set_bit(PACKET_LRA_VALID, &pd->flags);
+	}
+
+	/*
+	 * fine for now
+	 */
+	pd->settings.link_loss = 7;
+	pd->settings.write_type = 0;	/* packet */
+	pd->settings.track_mode = ti.track_mode;
+
+	/*
+	 * mode1 or mode2 disc
+	 */
+	switch (ti.data_mode) {
+		case PACKET_MODE1:
+			pd->settings.block_mode = PACKET_BLOCK_MODE1;
+			break;
+		case PACKET_MODE2:
+			pd->settings.block_mode = PACKET_BLOCK_MODE2;
+			break;
+		default:
+			printk("pktcdvd: unknown data mode\n");
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * enable/disable write caching on drive
+ */
+static int pkt_write_caching(struct pktcdvd_device *pd, int set)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	unsigned char buf[64];
+	int ret;
+
+	memset(buf, 0, sizeof(buf));
+	init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ);
+	cgc.sense = &sense;
+	cgc.buflen = pd->mode_offset + 12;
+
+	/*
+	 * caching mode page might not be there, so quiet this command
+	 */
+	cgc.quiet = 1;
+
+	if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WCACHING_PAGE, 0)))
+		return ret;
+
+	buf[pd->mode_offset + 10] |= (!!set << 2);
+
+	cgc.buflen = cgc.cmd[8] = 2 + ((buf[0] << 8) | (buf[1] & 0xff));
+	ret = pkt_mode_select(pd, &cgc);
+	if (ret) {
+		printk("pktcdvd: write caching control failed\n");
+		pkt_dump_sense(&cgc);
+	} else if (!ret && set)
+		printk("pktcdvd: enabled write caching on %s\n", pd->name);
+	return ret;
+}
+
+static int pkt_lock_door(struct pktcdvd_device *pd, int lockflag)
+{
+	struct packet_command cgc;
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL;
+	cgc.cmd[4] = lockflag ? 1 : 0;
+	return pkt_generic_packet(pd, &cgc);
+}
+
+/*
+ * Returns drive maximum write speed
+ */
+static int pkt_get_max_speed(struct pktcdvd_device *pd, unsigned *write_speed)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	unsigned char buf[256+18];
+	unsigned char *cap_buf;
+	int ret, offset;
+
+	memset(buf, 0, sizeof(buf));
+	cap_buf = &buf[sizeof(struct mode_page_header) + pd->mode_offset];
+	init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_UNKNOWN);
+	cgc.sense = &sense;
+
+	ret = pkt_mode_sense(pd, &cgc, GPMODE_CAPABILITIES_PAGE, 0);
+	if (ret) {
+		cgc.buflen = pd->mode_offset + cap_buf[1] + 2 +
+			     sizeof(struct mode_page_header);
+		ret = pkt_mode_sense(pd, &cgc, GPMODE_CAPABILITIES_PAGE, 0);
+		if (ret) {
+			pkt_dump_sense(&cgc);
+			return ret;
+		}
+	}
+
+	offset = 20;			    /* Obsoleted field, used by older drives */
+	if (cap_buf[1] >= 28)
+		offset = 28;		    /* Current write speed selected */
+	if (cap_buf[1] >= 30) {
+		/* If the drive reports at least one "Logical Unit Write
+		 * Speed Performance Descriptor Block", use the information
+		 * in the first block. (contains the highest speed)
+		 */
+		int num_spdb = (cap_buf[30] << 8) + cap_buf[31];
+		if (num_spdb > 0)
+			offset = 34;
+	}
+
+	*write_speed = ((cap_buf[offset] << 8) | cap_buf[offset + 1]) / 0xb0;
+	return 0;
+}
+
+/* These tables from cdrecord - I don't have orange book */
+/* standard speed CD-RW (1-4x) */
+static char clv_to_speed[16] = {
+	/* 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 */
+	   0, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+/* high speed CD-RW (-10x) */
+static char hs_clv_to_speed[16] = {
+	/* 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 */
+	   0, 2, 4, 6, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+/* ultra high speed CD-RW */
+static char us_clv_to_speed[16] = {
+	/* 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 */
+	   0, 2, 4, 8, 0, 0,16, 0,24,32,40,48, 0, 0, 0, 0
+};
+
+/*
+ * reads the maximum media speed from ATIP
+ */
+static int pkt_media_speed(struct pktcdvd_device *pd, unsigned *speed)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	unsigned char buf[64];
+	unsigned int size, st, sp;
+	int ret;
+
+	init_cdrom_command(&cgc, buf, 2, CGC_DATA_READ);
+	cgc.sense = &sense;
+	cgc.cmd[0] = GPCMD_READ_TOC_PMA_ATIP;
+	cgc.cmd[1] = 2;
+	cgc.cmd[2] = 4; /* READ ATIP */
+	cgc.cmd[8] = 2;
+	ret = pkt_generic_packet(pd, &cgc);
+	if (ret) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+	size = ((unsigned int) buf[0]<<8) + buf[1] + 2;
+	if (size > sizeof(buf))
+		size = sizeof(buf);
+
+	init_cdrom_command(&cgc, buf, size, CGC_DATA_READ);
+	cgc.sense = &sense;
+	cgc.cmd[0] = GPCMD_READ_TOC_PMA_ATIP;
+	cgc.cmd[1] = 2;
+	cgc.cmd[2] = 4;
+	cgc.cmd[8] = size;
+	ret = pkt_generic_packet(pd, &cgc);
+	if (ret) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+
+	if (!buf[6] & 0x40) {
+		printk("pktcdvd: Disc type is not CD-RW\n");
+		return 1;
+	}
+	if (!buf[6] & 0x4) {
+		printk("pktcdvd: A1 values on media are not valid, maybe not CDRW?\n");
+		return 1;
+	}
+
+	st = (buf[6] >> 3) & 0x7; /* disc sub-type */
+
+	sp = buf[16] & 0xf; /* max speed from ATIP A1 field */
+
+	/* Info from cdrecord */
+	switch (st) {
+		case 0: /* standard speed */
+			*speed = clv_to_speed[sp];
+			break;
+		case 1: /* high speed */
+			*speed = hs_clv_to_speed[sp];
+			break;
+		case 2: /* ultra high speed */
+			*speed = us_clv_to_speed[sp];
+			break;
+		default:
+			printk("pktcdvd: Unknown disc sub-type %d\n",st);
+			return 1;
+	}
+	if (*speed) {
+		printk("pktcdvd: Max. media speed: %d\n",*speed);
+		return 0;
+	} else {
+		printk("pktcdvd: Unknown speed %d for sub-type %d\n",sp,st);
+		return 1;
+	}
+}
+
+static int pkt_perform_opc(struct pktcdvd_device *pd)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	int ret;
+
+	VPRINTK("pktcdvd: Performing OPC\n");
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.sense = &sense;
+	cgc.timeout = 60*HZ;
+	cgc.cmd[0] = GPCMD_SEND_OPC;
+	cgc.cmd[1] = 1;
+	if ((ret = pkt_generic_packet(pd, &cgc)))
+		pkt_dump_sense(&cgc);
+	return ret;
+}
+
+static int pkt_open_write(struct pktcdvd_device *pd)
+{
+	int ret;
+	unsigned int write_speed, media_write_speed, read_speed;
+
+	if ((ret = pkt_probe_settings(pd))) {
+		DPRINTK("pktcdvd: %s failed probe\n", pd->name);
+		return -EIO;
+	}
+
+	if ((ret = pkt_set_write_settings(pd))) {
+		DPRINTK("pktcdvd: %s failed saving write settings\n", pd->name);
+		return -EIO;
+	}
+
+	pkt_write_caching(pd, USE_WCACHING);
+
+	if ((ret = pkt_get_max_speed(pd, &write_speed)))
+		write_speed = 16;
+	if ((ret = pkt_media_speed(pd, &media_write_speed)))
+		media_write_speed = 16;
+	write_speed = min(write_speed, media_write_speed);
+	read_speed = write_speed;
+
+	if ((ret = pkt_set_speed(pd, write_speed, read_speed))) {
+		DPRINTK("pktcdvd: %s couldn't set write speed\n", pd->name);
+		return -EIO;
+	}
+	DPRINTK("pktcdvd: write speed %u\n", write_speed);
+	pd->write_speed = write_speed;
+	pd->read_speed = read_speed;
+
+	if ((ret = pkt_perform_opc(pd))) {
+		DPRINTK("pktcdvd: %s Optimum Power Calibration failed\n", pd->name);
+	}
+
+	return 0;
+}
+
+static int pkt_get_minor(struct pktcdvd_device *pd)
+{
+	int minor;
+	for (minor = 0; minor < MAX_WRITERS; minor++)
+		if (pd == &pkt_devs[minor])
+			break;
+	BUG_ON(minor == MAX_WRITERS);
+	return minor;
+}
+
+/*
+ * called at open time.
+ */
+static int pkt_open_dev(struct pktcdvd_device *pd, int write)
+{
+	int ret;
+	long lba;
+	request_queue_t *q;
+	int i;
+	char b[BDEVNAME_SIZE];
+
+	if (!pd->dev)
+		return -ENXIO;
+
+	pd->bdev = bdget(pd->dev);
+	if (!pd->bdev) {
+		printk("pktcdvd: can't find cdrom block device\n");
+		return -ENXIO;
+	}
+
+	if ((ret = blkdev_get(pd->bdev, FMODE_READ, 0))) {
+		pd->bdev = NULL;
+		return ret;
+	}
+
+	if ((ret = pkt_get_last_written(pd, &lba))) {
+		printk("pktcdvd: pkt_get_last_written failed\n");
+		return ret;
+	}
+
+	set_capacity(disks[pkt_get_minor(pd)], lba << 2);
+
+	/*
+	 * The underlying block device needs to have its merge logic
+	 * modified, so that it doesn't try to merge write requests.
+	 * First make sure the queue isn't already in use by another
+	 * pktcdvd_device.
+	 */
+	q = bdev_get_queue(pd->bdev);
+	for (i = 0; i < MAX_WRITERS; i++) {
+		if (pd == &pkt_devs[i])
+			continue;
+		if (pkt_devs[i].bdev && bdev_get_queue(pkt_devs[i].bdev) == q) {
+			printk("pktcdvd: %s request queue busy\n", bdevname(pd->bdev, b));
+			return -EBUSY;
+		}
+	}
+	spin_lock_irq(q->queue_lock);
+	pd->cdrw.elv_merge_fn = q->elevator.elevator_merge_fn;
+	pd->cdrw.elv_completed_req_fn = q->elevator.elevator_completed_req_fn;
+	pd->cdrw.merge_requests_fn = q->merge_requests_fn;
+	q->elevator.elevator_merge_fn = pkt_lowlevel_elv_merge_fn;
+	q->elevator.elevator_completed_req_fn = pkt_lowlevel_elv_completed_req_fn;
+	q->merge_requests_fn = pkt_lowlevel_merge_requests_fn;
+	spin_unlock_irq(q->queue_lock);
+
+	if (write) {
+		if ((ret = pkt_open_write(pd)))
+			goto restore_queue;
+		set_bit(PACKET_WRITABLE, &pd->flags);
+	} else {
+		pkt_set_speed(pd, 0xffff, 0xffff);
+		clear_bit(PACKET_WRITABLE, &pd->flags);
+	}
+
+	if ((ret = pkt_set_segment_merging(pd, q)))
+		goto restore_queue;
+
+	if (write)
+		printk("pktcdvd: %lukB available on disc\n", lba << 1);
+
+	return 0;
+
+restore_queue:
+	spin_lock_irq(q->queue_lock);
+	q->elevator.elevator_merge_fn = pd->cdrw.elv_merge_fn;
+	q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
+	q->merge_requests_fn = pd->cdrw.merge_requests_fn;
+	spin_unlock_irq(q->queue_lock);
+	return ret;
+}
+
+/*
+ * called when the device is closed. makes sure that the device flushes
+ * the internal cache before we close.
+ */
+static void pkt_release_dev(struct pktcdvd_device *pd, int flush)
+{
+	struct block_device *bdev;
+
+	atomic_dec(&pd->refcnt);
+	if (atomic_read(&pd->refcnt) > 0)
+		return;
+
+	bdev = bdget(pd->pkt_dev);
+	if (bdev) {
+		fsync_bdev(bdev);
+		bdput(bdev);
+	}
+
+	if (flush && pkt_flush_cache(pd))
+		DPRINTK("pktcdvd: %s not flushing cache\n", pd->name);
+
+	if (pd->bdev) {
+		request_queue_t *q = bdev_get_queue(pd->bdev);
+		pkt_set_speed(pd, 0xffff, 0xffff);
+		spin_lock_irq(q->queue_lock);
+		q->elevator.elevator_merge_fn = pd->cdrw.elv_merge_fn;
+		q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
+		q->merge_requests_fn = pd->cdrw.merge_requests_fn;
+		spin_unlock_irq(q->queue_lock);
+		blkdev_put(pd->bdev);
+		pd->bdev = NULL;
+	}
+}
+
+static int pkt_open(struct inode *inode, struct file *file)
+{
+	struct pktcdvd_device *pd = NULL;
+	struct block_device *pkt_bdev;
+	int ret;
+
+	VPRINTK("pktcdvd: entering open\n");
+
+	if (iminor(inode) >= MAX_WRITERS) {
+		printk("pktcdvd: max %d writers supported\n", MAX_WRITERS);
+		ret = -ENODEV;
+		goto out;
+	}
+
+	/*
+	 * either device is not configured, or pktsetup is old and doesn't
+	 * use O_CREAT to create device
+	 */
+	pd = &pkt_devs[iminor(inode)];
+	if (!pd->dev && !(file->f_flags & O_CREAT)) {
+		VPRINTK("pktcdvd: not configured and O_CREAT not set\n");
+		ret = -ENXIO;
+		goto out;
+	}
+
+	atomic_inc(&pd->refcnt);
+	if (atomic_read(&pd->refcnt) > 1) {
+		if (file->f_mode & FMODE_WRITE) {
+			VPRINTK("pktcdvd: busy open for write\n");
+			ret = -EBUSY;
+			goto out_dec;
+		}
+
+		/*
+		 * Not first open, everything is already set up
+		 */
+		return 0;
+	}
+
+	if (((file->f_flags & O_ACCMODE) != O_RDONLY) || !(file->f_flags & O_CREAT)) {
+		if (pkt_open_dev(pd, file->f_mode & FMODE_WRITE)) {
+			ret = -EIO;
+			goto out_dec;
+		}
+	}
+
+	/*
+	 * needed here as well, since ext2 (among others) may change
+	 * the blocksize at mount time
+	 */
+	pkt_bdev = bdget(inode->i_rdev);
+	if (pkt_bdev) {
+		set_blocksize(pkt_bdev, CD_FRAMESIZE);
+		bdput(pkt_bdev);
+	}
+	return 0;
+
+out_dec:
+	atomic_dec(&pd->refcnt);
+	if (atomic_read(&pd->refcnt) == 0) {
+		if (pd->bdev) {
+			blkdev_put(pd->bdev);
+			pd->bdev = NULL;
+		}
+	}
+out:
+	VPRINTK("pktcdvd: failed open (%d)\n", ret);
+	return ret;
+}
+
+static int pkt_close(struct inode *inode, struct file *file)
+{
+	struct pktcdvd_device *pd = &pkt_devs[iminor(inode)];
+	int ret = 0;
+
+	if (pd->dev) {
+		int flush = test_bit(PACKET_WRITABLE, &pd->flags);
+		pkt_release_dev(pd, flush);
+	}
+
+	return ret;
+}
+
+static int pkt_make_request(request_queue_t *q, struct bio *bio)
+{
+	struct pktcdvd_device *pd;
+	char b[BDEVNAME_SIZE];
+	sector_t zone;
+	struct packet_data *pkt;
+	int was_empty, blocked_bio;
+
+	pd = q->queuedata;
+	if (!pd) {
+		printk("pktcdvd: %s incorrect request queue\n", bdevname(bio->bi_bdev, b));
+		goto end_io;
+	}
+
+	if (!pd->dev) {
+		printk("pktcdvd: request received for non-active pd\n");
+		goto end_io;
+	}
+
+	/*
+	 * quick remap a READ
+	 */
+	if (bio_data_dir(bio) == READ) {
+		bio->bi_bdev = pd->bdev;
+		pd->stats.secs_r += bio->bi_size >> 9;
+		pkt_queue_bio(pd, bio, 1);
+		return 0;
+	}
+
+	if (!test_bit(PACKET_WRITABLE, &pd->flags)) {
+		printk("pktcdvd: WRITE for ro device %s (%llu)\n",
+			pd->name, (unsigned long long)bio->bi_sector);
+		goto end_io;
+	}
+
+	if (!bio->bi_size || (bio->bi_size % CD_FRAMESIZE)) {
+		printk("pktcdvd: wrong bio size\n");
+		goto end_io;
+	}
+
+	blk_queue_bounce(q, &bio);
+
+	zone = ZONE(bio->bi_sector, pd);
+	VPRINTK("pkt_make_request: start = %6llx stop = %6llx\n",
+		(unsigned long long)bio->bi_sector,
+		(unsigned long long)(bio->bi_sector + bio_sectors(bio)));
+
+	/* Check if we have to split the bio */
+	{
+		struct bio_pair *bp;
+		sector_t last_zone;
+		int first_sectors;
+
+		last_zone = ZONE(bio->bi_sector + bio_sectors(bio) - 1, pd);
+		if (last_zone != zone) {
+			BUG_ON(last_zone != zone + pd->settings.size);
+			first_sectors = last_zone - bio->bi_sector;
+			bp = bio_split(bio, bio_split_pool, first_sectors);
+			BUG_ON(!bp);
+			pkt_make_request(q, &bp->bio1);
+			pkt_make_request(q, &bp->bio2);
+			bio_pair_release(bp);
+			return 0;
+		}
+	}
+
+	/*
+	 * If we find a matching packet in state WAITING or READ_WAIT, we can
+	 * just append this bio to that packet.
+	 */
+	spin_lock(&pd->cdrw.active_list_lock);
+	blocked_bio = 0;
+	list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+		if (pkt->sector == zone) {
+			spin_lock(&pkt->lock);
+			if ((pkt->state == PACKET_WAITING_STATE) ||
+			    (pkt->state == PACKET_READ_WAIT_STATE)) {
+				pkt_add_list_last(bio, &pkt->orig_bios,
+						  &pkt->orig_bios_tail);
+				pkt->write_size += bio->bi_size / CD_FRAMESIZE;
+				if ((pkt->write_size >= pkt->frames) &&
+				    (pkt->state == PACKET_WAITING_STATE)) {
+					atomic_inc(&pkt->run_sm);
+					wake_up(&pd->wqueue);
+				}
+				spin_unlock(&pkt->lock);
+				spin_unlock(&pd->cdrw.active_list_lock);
+				return 0;
+			} else {
+				blocked_bio = 1;
+			}
+			spin_unlock(&pkt->lock);
+		}
+	}
+	spin_unlock(&pd->cdrw.active_list_lock);
+
+	/*
+	 * No matching packet found. Store the bio in the work queue.
+	 */
+	spin_lock(&pd->lock);
+	if (pd->bio_queue == NULL) {
+		was_empty = 1;
+		bio->bi_next = NULL;
+		pd->bio_queue = bio;
+		pd->bio_queue_tail = bio;
+	} else {
+		struct bio *bio2, *insert_after;
+		int distance, z, cnt;
+
+		was_empty = 0;
+		z = ZONE(pd->bio_queue_tail->bi_sector, pd);
+		distance = (zone >= z ? zone - z : INT_MAX);
+		insert_after = pd->bio_queue_tail;
+		if (distance > pd->settings.size) {
+			for (bio2 = pd->bio_queue, cnt = 0; bio2 && (cnt < 10000);
+			     bio2 = bio2->bi_next, cnt++) {
+				int d2;
+				z = ZONE(bio2->bi_sector, pd);
+				d2 = (zone >= z ? zone - z : INT_MAX);
+				if (d2 < distance) {
+					distance = d2;
+					insert_after = bio2;
+					if (distance == 0)
+						break;
+				}
+			}
+		}
+		bio->bi_next = insert_after->bi_next;
+		insert_after->bi_next = bio;
+		if (insert_after == pd->bio_queue_tail)
+			pd->bio_queue_tail = bio;
+	}
+	spin_unlock(&pd->lock);
+
+	/*
+	 * Wake up the worker thread.
+	 */
+	atomic_set(&pd->scan_queue, 1);
+	if (was_empty) {
+		/* This wake_up is required for correct operation */
+		wake_up(&pd->wqueue);
+	} else if (!list_empty(&pd->cdrw.pkt_free_list) && !blocked_bio) {
+		/*
+		 * This wake up is not required for correct operation,
+		 * but improves performance in some cases.
+		 */
+		wake_up(&pd->wqueue);
+	}
+	return 0;
+end_io:
+	bio_io_error(bio, bio->bi_size);
+	return 0;
+}
+
+
+
+static int pkt_merge_bvec(request_queue_t *q, struct bio *bio, struct bio_vec *bvec)
+{
+	struct pktcdvd_device *pd = &pkt_devs[MINOR(bio->bi_bdev->bd_dev)];
+	sector_t zone = ZONE(bio->bi_sector, pd);
+	int used = ((bio->bi_sector - zone) << 9) + bio->bi_size;
+	int remaining = (pd->settings.size << 9) - used;
+	int remaining2;
+
+	/*
+	 * A bio <= PAGE_SIZE must be allowed. If it crosses a packet
+	 * boundary, pkt_make_request() will split the bio.
+	 */
+	remaining2 = PAGE_SIZE - bio->bi_size;
+	remaining = max(remaining, remaining2);
+
+	BUG_ON(remaining < 0);
+	return remaining;
+}
+
+static void pkt_init_queue(struct pktcdvd_device *pd)
+{
+	request_queue_t *q = disks[pkt_get_minor(pd)]->queue;
+
+	blk_queue_make_request(q, pkt_make_request);
+	blk_queue_hardsect_size(q, CD_FRAMESIZE);
+	blk_queue_max_sectors(q, PACKET_MAX_SECTORS);
+	blk_queue_merge_bvec(q, pkt_merge_bvec);
+	q->queuedata = pd;
+}
+
+static int pkt_proc_device(struct pktcdvd_device *pd, char *buf)
+{
+	char *b = buf, *msg;
+	char bdev_buf[BDEVNAME_SIZE];
+	int states[PACKET_NUM_STATES];
+
+	b += sprintf(b, "\nWriter %s mapped to %s:\n", pd->name,
+		     __bdevname(pd->dev, bdev_buf));
+
+	b += sprintf(b, "\nSettings:\n");
+	b += sprintf(b, "\tpacket size:\t\t%dkB\n", pd->settings.size / 2);
+
+	if (pd->settings.write_type == 0)
+		msg = "Packet";
+	else
+		msg = "Unknown";
+	b += sprintf(b, "\twrite type:\t\t%s\n", msg);
+
+	b += sprintf(b, "\tpacket type:\t\t%s\n", pd->settings.fp ? "Fixed" : "Variable");
+	b += sprintf(b, "\tlink loss:\t\t%d\n", pd->settings.link_loss);
+
+	b += sprintf(b, "\ttrack mode:\t\t%d\n", pd->settings.track_mode);
+
+	if (pd->settings.block_mode == PACKET_BLOCK_MODE1)
+		msg = "Mode 1";
+	else if (pd->settings.block_mode == PACKET_BLOCK_MODE2)
+		msg = "Mode 2";
+	else
+		msg = "Unknown";
+	b += sprintf(b, "\tblock mode:\t\t%s\n", msg);
+
+	b += sprintf(b, "\nStatistics:\n");
+	b += sprintf(b, "\tpackets started:\t%lu\n", pd->stats.pkt_started);
+	b += sprintf(b, "\tpackets ended:\t\t%lu\n", pd->stats.pkt_ended);
+	b += sprintf(b, "\twritten:\t\t%lukB\n", pd->stats.secs_w >> 1);
+	b += sprintf(b, "\tread gather:\t\t%lukB\n", pd->stats.secs_rg >> 1);
+	b += sprintf(b, "\tread:\t\t\t%lukB\n", pd->stats.secs_r >> 1);
+
+	b += sprintf(b, "\nMisc:\n");
+	b += sprintf(b, "\treference count:\t%d\n", atomic_read(&pd->refcnt));
+	b += sprintf(b, "\tflags:\t\t\t0x%lx\n", pd->flags);
+	b += sprintf(b, "\tread speed:\t\t%ukB/s\n", pd->read_speed * 150);
+	b += sprintf(b, "\twrite speed:\t\t%ukB/s\n", pd->write_speed * 150);
+	b += sprintf(b, "\tstart offset:\t\t%lu\n", pd->offset);
+	b += sprintf(b, "\tmode page offset:\t%u\n", pd->mode_offset);
+
+	b += sprintf(b, "\nQueue state:\n");
+	b += sprintf(b, "\tbios queued:\t\t%s\n", pd->bio_queue ? "yes" : "no");
+
+	pkt_count_states(pd, states);
+	b += sprintf(b, "\tstate:\t\t\ti:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n",
+		     states[0], states[1], states[2], states[3], states[4], states[5]);
+
+	return b - buf;
+}
+
+static int pkt_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+	struct pktcdvd_device *pd = data;
+	char *buf = page;
+	int len;
+
+	len = pkt_proc_device(pd, buf);
+	buf += len;
+
+	if (len <= off + count)
+		*eof = 1;
+
+	*start = page + off;
+	len -= off;
+	if (len > count)
+		len = count;
+	if (len < 0)
+		len = 0;
+
+	return len;
+}
+
+extern struct block_device_operations pktcdvd_ops;
+
+static int pkt_new_dev(struct pktcdvd_device *pd, struct block_device *bdev)
+{
+	dev_t dev = bdev->bd_dev;
+	int minor;
+	int ret = 0;
+	char b[BDEVNAME_SIZE];
+
+	for (minor = 0; minor < MAX_WRITERS; minor++) {
+		if (pkt_devs[minor].dev == dev) {
+			printk("pktcdvd: %s already setup\n", bdevname(bdev, b));
+			return -EBUSY;
+		}
+	}
+
+	minor = pkt_get_minor(pd);
+
+	/* This is safe, since we have a reference from open(). */
+	__module_get(THIS_MODULE);
+
+	memset(pd, 0, sizeof(struct pktcdvd_device));
+
+	spin_lock_init(&pd->lock);
+	spin_lock_init(&pd->iosched.lock);
+	if (!pkt_grow_pktlist(pd, CONFIG_CDROM_PKTCDVD_BUFFERS)) {
+		printk("pktcdvd: not enough memory for buffers\n");
+		ret = -ENOMEM;
+		goto out_mem;
+	}
+
+	set_blocksize(bdev, CD_FRAMESIZE);
+	pd->dev = dev;
+	pd->bdev = NULL;
+	pd->pkt_dev = MKDEV(PACKET_MAJOR, minor);
+	sprintf(pd->name, "pktcdvd%d", minor);
+	atomic_set(&pd->refcnt, 0);
+	init_waitqueue_head(&pd->wqueue);
+	init_completion(&pd->cdrw.thr_compl);
+
+	pkt_init_queue(pd);
+
+	pd->cdrw.time_to_die = 0;
+	pd->cdrw.pid = kernel_thread(kcdrwd, pd, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
+	if (pd->cdrw.pid < 0) {
+		printk("pktcdvd: can't start kernel thread\n");
+		ret = -EBUSY;
+		goto out_thread;
+	}
+
+	create_proc_read_entry(pd->name, 0, pkt_proc, pkt_read_proc, pd);
+	DPRINTK("pktcdvd: writer %d mapped to %s\n", minor, bdevname(bdev, b));
+	return 0;
+
+out_thread:
+	pkt_shrink_pktlist(pd);
+	memset(pd, 0, sizeof(*pd));
+out_mem:
+	/* This is safe: open() is still holding a reference. */
+	module_put(THIS_MODULE);
+	return ret;
+}
+
+/*
+ * arg contains file descriptor of CD-ROM device.
+ */
+static int pkt_setup_dev(struct pktcdvd_device *pd, unsigned int arg)
+{
+	struct inode *inode;
+	struct file *file;
+	int ret;
+
+	if ((file = fget(arg)) == NULL) {
+		printk("pktcdvd: bad file descriptor passed\n");
+		return -EBADF;
+	}
+
+	ret = -EINVAL;
+	if ((inode = file->f_dentry->d_inode) == NULL) {
+		printk("pktcdvd: huh? file descriptor contains no inode?\n");
+		goto out;
+	}
+	ret = -ENOTBLK;
+	if (!S_ISBLK(inode->i_mode)) {
+		printk("pktcdvd: device is not a block device (duh)\n");
+		goto out;
+	}
+	ret = -EROFS;
+	if (IS_RDONLY(inode)) {
+		printk("pktcdvd: Can't write to read-only dev\n");
+		goto out;
+	}
+	if ((ret = pkt_new_dev(pd, inode->i_bdev)))
+		goto out;
+
+	atomic_inc(&pd->refcnt);
+
+out:
+	fput(file);
+	return ret;
+}
+
+static int pkt_remove_dev(struct pktcdvd_device *pd)
+{
+	struct block_device *bdev;
+	int ret;
+
+	if (pd->cdrw.pid >= 0) {
+		pd->cdrw.time_to_die = 1;
+		wmb();
+		ret = kill_proc(pd->cdrw.pid, SIGKILL, 1);
+		if (ret) {
+			printk("pkt_remove_dev: can't kill kernel thread\n");
+			return ret;
+		}
+		wait_for_completion(&pd->cdrw.thr_compl);
+	}
+
+	/*
+	 * will also invalidate buffers for CD-ROM
+	 */
+	bdev = bdget(pd->pkt_dev);
+	if (bdev) {
+		invalidate_bdev(bdev, 1);
+		bdput(bdev);
+	}
+
+	pkt_shrink_pktlist(pd);
+
+	remove_proc_entry(pd->name, pkt_proc);
+	DPRINTK("pktcdvd: writer %d unmapped\n", pkt_get_minor(pd));
+	memset(pd, 0, sizeof(struct pktcdvd_device));
+	/* This is safe: open() is still holding a reference. */
+	module_put(THIS_MODULE);
+	return 0;
+}
+
+static int pkt_media_changed(struct gendisk *disk)
+{
+	struct pktcdvd_device *pd = disk->private_data;
+	struct gendisk *attached_disk;
+
+	if (!pd)
+		return 0;
+	if (!pd->bdev)
+		return 0;
+	attached_disk = pd->bdev->bd_disk;
+	if (!attached_disk)
+		return 0;
+	return attached_disk->fops->media_changed(attached_disk);
+}
+
+static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct pktcdvd_device *pd = &pkt_devs[iminor(inode)];
+
+	VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode));
+
+	if ((cmd != PACKET_SETUP_DEV) && !pd->dev) {
+		DPRINTK("pktcdvd: dev not setup\n");
+		return -ENXIO;
+	}
+
+	switch (cmd) {
+	case PACKET_GET_STATS:
+		if (copy_to_user(&arg, &pd->stats, sizeof(struct packet_stats)))
+			return -EFAULT;
+		break;
+
+	case PACKET_SETUP_DEV:
+		if (pd->dev) {
+			printk("pktcdvd: dev already setup\n");
+			return -EBUSY;
+		}
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		return pkt_setup_dev(pd, arg);
+
+	case PACKET_TEARDOWN_DEV:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		if (atomic_read(&pd->refcnt) != 1)
+			return -EBUSY;
+		return pkt_remove_dev(pd);
+
+	case BLKROSET:
+		if (capable(CAP_SYS_ADMIN))
+			clear_bit(PACKET_WRITABLE, &pd->flags);
+	case BLKROGET:
+	case BLKSSZGET:
+	case BLKFLSBUF:
+		if (!pd->bdev)
+			return -ENXIO;
+		return -EINVAL;		    /* Handled by blkdev layer */
+
+	/*
+	 * forward selected CDROM ioctls to CD-ROM, for UDF
+	 */
+	case CDROMMULTISESSION:
+	case CDROMREADTOCENTRY:
+	case CDROM_LAST_WRITTEN:
+	case CDROM_SEND_PACKET:
+	case SCSI_IOCTL_SEND_COMMAND:
+		if (!pd->bdev)
+			return -ENXIO;
+		return ioctl_by_bdev(pd->bdev, cmd, arg);
+
+	case CDROMEJECT:
+		if (!pd->bdev)
+			return -ENXIO;
+		/*
+		 * The door gets locked when the device is opened, so we
+		 * have to unlock it or else the eject command fails.
+		 */
+		pkt_lock_door(pd, 0);
+		return ioctl_by_bdev(pd->bdev, cmd, arg);
+
+	default:
+		printk("pktcdvd: Unknown ioctl for %s (%x)\n", pd->name, cmd);
+		return -ENOTTY;
+	}
+
+	return 0;
+}
+
+static struct block_device_operations pktcdvd_ops = {
+	.owner =		THIS_MODULE,
+	.open =			pkt_open,
+	.release =		pkt_close,
+	.ioctl =		pkt_ioctl,
+	.media_changed =	pkt_media_changed,
+};
+
+int pkt_init(void)
+{
+	int i;
+
+	devfs_mk_dir("pktcdvd");
+	if (register_blkdev(PACKET_MAJOR, "pktcdvd")) {
+		printk("unable to register pktcdvd device\n");
+		return -EIO;
+	}
+
+	pkt_devs = kmalloc(MAX_WRITERS * sizeof(struct pktcdvd_device), GFP_KERNEL);
+	if (pkt_devs == NULL)
+		goto out_mem;
+	memset(pkt_devs, 0, MAX_WRITERS * sizeof(struct pktcdvd_device));
+
+	for (i = 0; i < MAX_WRITERS; i++) {
+		disks[i] = alloc_disk(1);
+		if (!disks[i])
+			goto out_mem2;
+	}
+
+	for (i = 0; i < MAX_WRITERS; i++) {
+		struct pktcdvd_device *pd = &pkt_devs[i];
+		struct gendisk *disk = disks[i];
+		disk->major = PACKET_MAJOR;
+		disk->first_minor = i;
+		disk->fops = &pktcdvd_ops;
+		disk->flags = GENHD_FL_REMOVABLE;
+		sprintf(disk->disk_name, "pktcdvd%d", i);
+		sprintf(disk->devfs_name, "pktcdvd/%d", i);
+		disk->private_data = pd;
+		disk->queue = blk_alloc_queue(GFP_KERNEL);
+		if (!disk->queue)
+			goto out_mem3;
+		add_disk(disk);
+	}
+
+	pkt_proc = proc_mkdir("pktcdvd", proc_root_driver);
+
+	DPRINTK("pktcdvd: %s\n", VERSION_CODE);
+	return 0;
+
+out_mem3:
+	while (i--)
+		blk_put_queue(disks[i]->queue);
+	i = MAX_WRITERS;
+out_mem2:
+	while (i--)
+		put_disk(disks[i]);
+	kfree(pkt_devs);
+out_mem:
+	printk("pktcdvd: out of memory\n");
+	devfs_remove("pktcdvd");
+	unregister_blkdev(PACKET_MAJOR, "pktcdvd");
+	return -ENOMEM;
+}
+
+void pkt_exit(void)
+{
+	int i;
+	for (i = 0; i < MAX_WRITERS; i++) {
+		del_gendisk(disks[i]);
+		blk_put_queue(disks[i]->queue);
+		put_disk(disks[i]);
+	}
+
+	devfs_remove("pktcdvd");
+	unregister_blkdev(PACKET_MAJOR, "pktcdvd");
+
+	remove_proc_entry("pktcdvd", proc_root_driver);
+	kfree(pkt_devs);
+}
+
+MODULE_DESCRIPTION("Packet writing layer for CD/DVD drives");
+MODULE_AUTHOR("Jens Axboe <axboe@suse.de>");
+MODULE_LICENSE("GPL");
+
+module_init(pkt_init);
+module_exit(pkt_exit);
+
+MODULE_ALIAS_BLOCKDEV_MAJOR(PACKET_MAJOR);
diff -puN drivers/cdrom/Makefile~packet-2.6.7 drivers/cdrom/Makefile
--- linux/drivers/cdrom/Makefile~packet-2.6.7	2004-07-01 13:23:15.000000000 +0200
+++ linux-petero/drivers/cdrom/Makefile	2004-07-01 13:23:15.000000000 +0200
@@ -8,6 +8,7 @@
 obj-$(CONFIG_BLK_DEV_IDECD)	+=              cdrom.o
 obj-$(CONFIG_BLK_DEV_SR)	+=              cdrom.o
 obj-$(CONFIG_PARIDE_PCD)	+=		cdrom.o
+obj-$(CONFIG_CDROM_PKTCDVD)	+=		cdrom.o
 
 obj-$(CONFIG_AZTCD)		+= aztcd.o
 obj-$(CONFIG_CDU31A)		+= cdu31a.o     cdrom.o
diff -puN drivers/ide/ide-cd.c~packet-2.6.7 drivers/ide/ide-cd.c
--- linux/drivers/ide/ide-cd.c~packet-2.6.7	2004-07-01 13:23:15.000000000 +0200
+++ linux-petero/drivers/ide/ide-cd.c	2004-07-01 15:05:03.298119232 +0200
@@ -2003,7 +2003,7 @@ ide_do_rw_cdrom (ide_drive_t *drive, str
 			}
 			CDROM_CONFIG_FLAGS(drive)->seeking = 0;
 		}
-		if (IDE_LARGE_SEEK(info->last_block, block, IDECD_SEEK_THRESHOLD) && drive->dsc_overlap) {
+		if ((rq_data_dir(rq) == READ) && IDE_LARGE_SEEK(info->last_block, block, IDECD_SEEK_THRESHOLD) && drive->dsc_overlap) {
 			action = cdrom_start_seek(drive, block);
 		} else {
 			if (rq_data_dir(rq) == READ)
@@ -2960,8 +2960,10 @@ int ide_cdrom_probe_capabilities (ide_dr
 		CDROM_CONFIG_FLAGS(drive)->no_eject = 0;
 	if (cap.cd_r_write)
 		CDROM_CONFIG_FLAGS(drive)->cd_r = 1;
-	if (cap.cd_rw_write)
+	if (cap.cd_rw_write) {
 		CDROM_CONFIG_FLAGS(drive)->cd_rw = 1;
+		CDROM_CONFIG_FLAGS(drive)->ram = 1;
+	}
 	if (cap.test_write)
 		CDROM_CONFIG_FLAGS(drive)->test_write = 1;
 	if (cap.dvd_ram_read || cap.dvd_r_read || cap.dvd_rom)
diff -puN drivers/scsi/sr.c~packet-2.6.7 drivers/scsi/sr.c
--- linux/drivers/scsi/sr.c~packet-2.6.7	2004-07-01 13:23:15.000000000 +0200
+++ linux-petero/drivers/scsi/sr.c	2004-07-01 15:05:03.274122880 +0200
@@ -880,10 +880,10 @@ static void get_capabilities(struct scsi
 		cd->cdi.mask |= CDC_CLOSE_TRAY; */
 
 	/*
-	 * if DVD-RAM of MRW-W, we are randomly writeable
+	 * if DVD-RAM, MRW-W or CD-RW, we are randomly writable
 	 */
-	if ((cd->cdi.mask & (CDC_DVD_RAM | CDC_MRW_W | CDC_RAM)) !=
-			(CDC_DVD_RAM | CDC_MRW_W | CDC_RAM)) {
+	if ((cd->cdi.mask & (CDC_DVD_RAM | CDC_MRW_W | CDC_RAM | CDC_CD_RW)) !=
+			(CDC_DVD_RAM | CDC_MRW_W | CDC_RAM | CDC_CD_RW)) {
 		cd->device->writeable = 1;
 		set_disk_ro(cd->disk, 0);
 	}
diff -puN include/linux/cdrom.h~packet-2.6.7 include/linux/cdrom.h
--- linux/include/linux/cdrom.h~packet-2.6.7	2004-07-01 13:23:15.000000000 +0200
+++ linux-petero/include/linux/cdrom.h	2004-07-01 15:05:03.299119080 +0200
@@ -498,6 +498,7 @@ struct cdrom_generic_command
 #define GPMODE_VENDOR_PAGE		0x00
 #define GPMODE_R_W_ERROR_PAGE		0x01
 #define GPMODE_WRITE_PARMS_PAGE		0x05
+#define GPMODE_WCACHING_PAGE		0x08
 #define GPMODE_AUDIO_CTL_PAGE		0x0e
 #define GPMODE_POWER_PAGE		0x1a
 #define GPMODE_FAULT_FAIL_PAGE		0x1c
diff -puN include/linux/major.h~packet-2.6.7 include/linux/major.h
--- linux/include/linux/major.h~packet-2.6.7	2004-07-01 13:23:15.000000000 +0200
+++ linux-petero/include/linux/major.h	2004-07-01 13:23:15.000000000 +0200
@@ -111,6 +111,8 @@
 
 #define MDISK_MAJOR		95
 
+#define PACKET_MAJOR		97
+
 #define UBD_MAJOR		98
 
 #define JSFD_MAJOR		99
diff -puN /dev/null include/linux/pktcdvd.h
--- /dev/null	2004-02-23 22:02:56.000000000 +0100
+++ linux-petero/include/linux/pktcdvd.h	2004-07-01 15:05:02.776198576 +0200
@@ -0,0 +1,261 @@
+/*
+ * Copyright (C) 2000 Jens Axboe <axboe@suse.de>
+ * Copyright (C) 2001-2004 Peter Osterlund <petero2@telia.com>
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License.  See linux/COPYING for more information.
+ *
+ * Packet writing layer for ATAPI and SCSI CD-R, CD-RW, DVD-R, and
+ * DVD-RW devices.
+ *
+ */
+#ifndef __PKTCDVD_H
+#define __PKTCDVD_H
+
+/*
+ * 1 for normal debug messages, 2 is very verbose. 0 to turn it off.
+ */
+#define PACKET_DEBUG		1
+
+#define	MAX_WRITERS		8
+
+/*
+ * How long we should hold a non-full packet before starting data gathering.
+ */
+#define PACKET_WAIT_TIME	(HZ * 5 / 1000)
+
+/*
+ * use drive write caching -- we need deferred error handling to be
+ * able to sucessfully recover with this option (drive will return good
+ * status as soon as the cdb is validated).
+ */
+#if defined(CONFIG_CDROM_PKTCDVD_WCACHE)
+#warning Enabling write caching, use at your own risk
+#define USE_WCACHING		1
+#else
+#define USE_WCACHING		0
+#endif
+
+/*
+ * No user-servicable parts beyond this point ->
+ */
+
+#if PACKET_DEBUG
+#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+#if PACKET_DEBUG > 1
+#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
+#else
+#define VPRINTK(fmt, args...)
+#endif
+
+/*
+ * device types
+ */
+#define PACKET_CDR		1
+#define	PACKET_CDRW		2
+#define PACKET_DVDR		3
+#define PACKET_DVDRW		4
+
+/*
+ * flags
+ */
+#define PACKET_WRITABLE		1	/* pd is writable */
+#define PACKET_NWA_VALID	2	/* next writable address valid */
+#define PACKET_LRA_VALID	3	/* last recorded address valid */
+#define PACKET_MERGE_SEGS	4	/* perform segment merging to keep */
+					/* underlying cdrom device happy */
+
+/*
+ * Disc status -- from READ_DISC_INFO
+ */
+#define PACKET_DISC_EMPTY	0
+#define PACKET_DISC_INCOMPLETE	1
+#define PACKET_DISC_COMPLETE	2
+#define PACKET_DISC_OTHER	3
+
+/*
+ * write type, and corresponding data block type
+ */
+#define PACKET_MODE1		1
+#define PACKET_MODE2		2
+#define PACKET_BLOCK_MODE1	8
+#define PACKET_BLOCK_MODE2	10
+
+/*
+ * Last session/border status
+ */
+#define PACKET_SESSION_EMPTY		0
+#define PACKET_SESSION_INCOMPLETE	1
+#define PACKET_SESSION_RESERVED		2
+#define PACKET_SESSION_COMPLETE		3
+
+#define PACKET_MCN			"4a656e734178626f65323030300000"
+
+#undef PACKET_USE_LS
+
+/*
+ * Very crude stats for now
+ */
+struct packet_stats
+{
+	unsigned long		pkt_started;
+	unsigned long		pkt_ended;
+	unsigned long		secs_w;
+	unsigned long		secs_rg;
+	unsigned long		secs_r;
+};
+
+/*
+ * packet ioctls
+ */
+#define PACKET_IOCTL_MAGIC	('X')
+#define PACKET_GET_STATS	_IOR(PACKET_IOCTL_MAGIC, 0, struct packet_stats)
+#define PACKET_SETUP_DEV	_IOW(PACKET_IOCTL_MAGIC, 1, unsigned int)
+#define PACKET_TEARDOWN_DEV	_IOW(PACKET_IOCTL_MAGIC, 2, unsigned int)
+
+#ifdef __KERNEL__
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/cdrom.h>
+
+struct packet_settings
+{
+	__u8			size;		/* packet size in (512 byte) sectors */
+	__u8			fp;		/* fixed packets */
+	__u8			link_loss;	/* the rest is specified
+						 * as per Mt Fuji */
+	__u8			write_type;
+	__u8			track_mode;
+	__u8			block_mode;
+};
+
+struct packet_cdrw
+{
+	struct list_head	pkt_free_list;
+	struct list_head	pkt_active_list;
+	spinlock_t		active_list_lock; /* Serialize access to pkt_active_list */
+	pid_t			pid;
+	int			time_to_die;
+	struct completion	thr_compl;
+	elevator_merge_fn	*elv_merge_fn;
+	elevator_completed_req_fn *elv_completed_req_fn;
+	merge_requests_fn	*merge_requests_fn;
+};
+
+/*
+ * Switch to high speed reading after reading this many kilobytes
+ * with no interspersed writes.
+ */
+#define HI_SPEED_SWITCH 512
+
+struct packet_iosched
+{
+	atomic_t		attention;	/* Set to non-zero when queue processing is needed */
+	int			writing;	/* Non-zero when writing, zero when reading */
+	spinlock_t		lock;		/* Protecting read/write queue manipulations */
+	struct bio		*read_queue;
+	struct bio		*read_queue_tail;
+	struct bio		*write_queue;
+	struct bio		*write_queue_tail;
+	int			high_prio_read;	/* An important read request has been queued */
+	int			successive_reads;
+};
+
+/*
+ * 32 buffers of 2048 bytes
+ */
+#define PACKET_MAX_SIZE		32
+#define PAGES_PER_PACKET	(PACKET_MAX_SIZE * CD_FRAMESIZE / PAGE_SIZE)
+#define PACKET_MAX_SECTORS	(PACKET_MAX_SIZE * CD_FRAMESIZE >> 9)
+
+enum packet_data_state {
+	PACKET_IDLE_STATE,			/* Not used at the moment */
+	PACKET_WAITING_STATE,			/* Waiting for more bios to arrive, so */
+						/* we don't have to do as much */
+						/* data gathering */
+	PACKET_READ_WAIT_STATE,			/* Waiting for reads to fill in holes */
+	PACKET_WRITE_WAIT_STATE,		/* Waiting for the write to complete */
+	PACKET_RECOVERY_STATE,			/* Recover after read/write errors */
+	PACKET_FINISHED_STATE,			/* After write has finished */
+
+	PACKET_NUM_STATES			/* Number of possible states */
+};
+
+/*
+ * Information needed for writing a single packet
+ */
+struct pktcdvd_device;
+
+struct packet_data
+{
+	struct list_head	list;
+
+	spinlock_t		lock;		/* Lock protecting state transitions and */
+						/* orig_bios list */
+
+	struct bio		*orig_bios;	/* Original bios passed to pkt_make_request */
+	struct bio		*orig_bios_tail;/* that will be handled by this packet */
+	int			write_size;	/* Total size of all bios in the orig_bios */
+						/* list, measured in number of frames */
+
+	struct bio		*w_bio;		/* The bio we will send to the real CD */
+						/* device once we have all data for the */
+						/* packet we are going to write */
+	sector_t		sector;		/* First sector in this packet */
+	int			frames;		/* Number of frames in this packet */
+
+	enum packet_data_state	state;		/* Current state */
+	atomic_t		run_sm;		/* Incremented whenever the state */
+						/* machine needs to be run */
+	long			sleep_time;	/* Set this to non-zero to make the state */
+						/* machine run after this many jiffies. */
+
+	atomic_t		io_wait;	/* Number of pending IO operations */
+	atomic_t		io_errors;	/* Number of read/write errors during IO */
+
+	struct bio		*r_bios[PACKET_MAX_SIZE]; /* bios to use during data gathering */
+	struct page		*pages[PAGES_PER_PACKET];
+
+	int			cache_valid;	/* If non-zero, the data for the zone defined */
+						/* by the sector variable is completely cached */
+						/* in the pages[] vector. */
+
+	int			id;		/* ID number for debugging */
+	struct pktcdvd_device	*pd;
+};
+
+struct pktcdvd_device
+{
+	struct block_device	*bdev;		/* dev attached */
+	dev_t			dev;		/* dev attached */
+	dev_t			pkt_dev;	/* our dev */
+	char			name[20];
+	struct packet_settings	settings;
+	struct packet_stats	stats;
+	atomic_t		refcnt;
+	__u8			write_speed;	/* current write speed */
+	__u8			read_speed;	/* current read speed */
+	unsigned long		offset;		/* start offset */
+	__u8			mode_offset;	/* 0 / 8 */
+	__u8			type;
+	unsigned long		flags;
+	__u32			nwa;		/* next writable address */
+	__u32			lra;		/* last recorded address */
+	struct packet_cdrw	cdrw;
+	wait_queue_head_t	wqueue;
+
+	spinlock_t		lock;		/* Serialize access to bio_queue */
+	struct bio		*bio_queue;	/* Work queue of bios we need to handle */
+	struct bio		*bio_queue_tail;
+	atomic_t		scan_queue;	/* Set to non-zero when pkt_handle_queue */
+						/* needs to be run. */
+	struct packet_iosched   iosched;
+};
+
+#endif /* __KERNEL__ */
+
+#endif /* __PKTCDVD_H */
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-01 13:34 [PATCH] CDRW packet writing support for 2.6.7-bk13 Peter Osterlund
@ 2004-07-02 21:52 ` Peter Osterlund
  2004-07-02 22:08   ` Andrew Morton
  2004-07-04 12:30 ` [PATCH] Fix race in pktcdvd kernel thread handling Peter Osterlund
                   ` (2 subsequent siblings)
  3 siblings, 1 reply; 27+ messages in thread
From: Peter Osterlund @ 2004-07-02 21:52 UTC (permalink / raw)
  To: viro; +Cc: linux-kernel, Jens Axboe, Andrew Morton

Peter Osterlund <petero2@telia.com> writes:

> This patch implements CDRW packet writing as a kernel block device.
> Usage instructions are in the packet-writing.txt file.
...
> +static int pkt_proc_device(struct pktcdvd_device *pd, char *buf)
> +{
> +	char *b = buf, *msg;
> +	char bdev_buf[BDEVNAME_SIZE];
> +	int states[PACKET_NUM_STATES];
> +
> +	b += sprintf(b, "\nWriter %s mapped to %s:\n", pd->name,
> +		     __bdevname(pd->dev, bdev_buf));

This code leaks a module reference. The patch below fixes it. I'm not
sure it's correct, but do_open() also does module_put() after
put_disk().

Signed-off-by: Peter Osterlund <petero2@telia.com>

---

 linux-petero/fs/partitions/check.c |    1 +
 1 files changed, 1 insertion(+)

diff -puN fs/partitions/check.c~packet-refcnt fs/partitions/check.c
--- linux/fs/partitions/check.c~packet-refcnt	2004-07-02 23:18:22.000000000 +0200
+++ linux-petero/fs/partitions/check.c	2004-07-02 23:18:23.000000000 +0200
@@ -151,6 +151,7 @@ const char *__bdevname(dev_t dev, char *
 	if (disk) {
 		buffer = disk_name(disk, part, buffer);
 		put_disk(disk);
+		module_put(disk->fops->owner);
 	} else {
 		snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
 				MAJOR(dev), MINOR(dev));
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-02 21:52 ` Peter Osterlund
@ 2004-07-02 22:08   ` Andrew Morton
  2004-07-02 22:47     ` Greg KH
  0 siblings, 1 reply; 27+ messages in thread
From: Andrew Morton @ 2004-07-02 22:08 UTC (permalink / raw)
  To: Peter Osterlund; +Cc: viro, linux-kernel, axboe

Peter Osterlund <petero2@telia.com> wrote:
>
> This code leaks a module reference. The patch below fixes it. I'm not
> sure it's correct, but do_open() also does module_put() after
> put_disk().
> 
> Signed-off-by: Peter Osterlund <petero2@telia.com>
> 
> ---
> 
>  linux-petero/fs/partitions/check.c |    1 +
>  1 files changed, 1 insertion(+)
> 
> diff -puN fs/partitions/check.c~packet-refcnt fs/partitions/check.c
> --- linux/fs/partitions/check.c~packet-refcnt	2004-07-02 23:18:22.000000000 +0200
> +++ linux-petero/fs/partitions/check.c	2004-07-02 23:18:23.000000000 +0200
> @@ -151,6 +151,7 @@ const char *__bdevname(dev_t dev, char *
>  	if (disk) {
>  		buffer = disk_name(disk, part, buffer);
>  		put_disk(disk);
> +		module_put(disk->fops->owner);
>  	} else {
>  		snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
>  				MAJOR(dev), MINOR(dev));

Yes, there's certainly a leak there.  It's surprising that it hasn't been
noted before.

But:

const char *__bdevname(dev_t dev, char *buffer)
{
	struct gendisk *disk;
	int part;

	disk = get_gendisk(dev, &part);
	if (disk) {
		buffer = disk_name(disk, part, buffer);
		put_disk(disk);
	} else {
		snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
				MAJOR(dev), MINOR(dev));
	}

get_gendisk() did an internal module_get() in kobj_lookup().  It would seem
to be logical that the module_put() should be in kobject_put(), called from
put_disk().  But surely if we made that change 1000 things would explode.

Greg, Al?


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-02 22:08   ` Andrew Morton
@ 2004-07-02 22:47     ` Greg KH
  2004-07-02 22:59       ` Andrew Morton
  0 siblings, 1 reply; 27+ messages in thread
From: Greg KH @ 2004-07-02 22:47 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Peter Osterlund, viro, linux-kernel, axboe

On Fri, Jul 02, 2004 at 03:08:19PM -0700, Andrew Morton wrote:
> Peter Osterlund <petero2@telia.com> wrote:
> >
> > This code leaks a module reference. The patch below fixes it. I'm not
> > sure it's correct, but do_open() also does module_put() after
> > put_disk().
> > 
> > Signed-off-by: Peter Osterlund <petero2@telia.com>
> > 
> > ---
> > 
> >  linux-petero/fs/partitions/check.c |    1 +
> >  1 files changed, 1 insertion(+)
> > 
> > diff -puN fs/partitions/check.c~packet-refcnt fs/partitions/check.c
> > --- linux/fs/partitions/check.c~packet-refcnt	2004-07-02 23:18:22.000000000 +0200
> > +++ linux-petero/fs/partitions/check.c	2004-07-02 23:18:23.000000000 +0200
> > @@ -151,6 +151,7 @@ const char *__bdevname(dev_t dev, char *
> >  	if (disk) {
> >  		buffer = disk_name(disk, part, buffer);
> >  		put_disk(disk);
> > +		module_put(disk->fops->owner);
> >  	} else {
> >  		snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
> >  				MAJOR(dev), MINOR(dev));
> 
> Yes, there's certainly a leak there.  It's surprising that it hasn't been
> noted before.
> 
> But:
> 
> const char *__bdevname(dev_t dev, char *buffer)
> {
> 	struct gendisk *disk;
> 	int part;
> 
> 	disk = get_gendisk(dev, &part);
> 	if (disk) {
> 		buffer = disk_name(disk, part, buffer);
> 		put_disk(disk);
> 	} else {
> 		snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
> 				MAJOR(dev), MINOR(dev));
> 	}
> 
> get_gendisk() did an internal module_get() in kobj_lookup().

But if kobj_lookup() succeeds, module_put() is called before returning
(actually it's always called, right?) 

> It would seem to be logical that the module_put() should be in
> kobject_put(), called from put_disk().  But surely if we made that
> change 1000 things would explode.

I don't see how that would fix anything, as the module reference is not
held after kobj_lookup() returns.  Or am I missing something here?

thanks,

greg k-h

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-02 22:47     ` Greg KH
@ 2004-07-02 22:59       ` Andrew Morton
  2004-07-02 23:24         ` Peter Osterlund
  0 siblings, 1 reply; 27+ messages in thread
From: Andrew Morton @ 2004-07-02 22:59 UTC (permalink / raw)
  To: Greg KH; +Cc: petero2, viro, linux-kernel, axboe

Greg KH <greg@kroah.com> wrote:
>
> > const char *__bdevname(dev_t dev, char *buffer)
> > {
> > 	struct gendisk *disk;
> > 	int part;
> > 
> > 	disk = get_gendisk(dev, &part);
> > 	if (disk) {
> > 		buffer = disk_name(disk, part, buffer);
> > 		put_disk(disk);
> > 	} else {
> > 		snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
> > 				MAJOR(dev), MINOR(dev));
> > 	}
> > 
> > get_gendisk() did an internal module_get() in kobj_lookup().
> 
> But if kobj_lookup() succeeds, module_put() is called before returning
> (actually it's always called, right?) 

Oop, sorry, yes.  Peter, are you sure this is where the leak is coming from?

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-02 22:59       ` Andrew Morton
@ 2004-07-02 23:24         ` Peter Osterlund
  2004-07-02 23:51           ` Andrew Morton
  0 siblings, 1 reply; 27+ messages in thread
From: Peter Osterlund @ 2004-07-02 23:24 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Greg KH, viro, linux-kernel, axboe

Andrew Morton <akpm@osdl.org> writes:

> Greg KH <greg@kroah.com> wrote:
> >
> > > const char *__bdevname(dev_t dev, char *buffer)
> > > {
> > > 	struct gendisk *disk;
> > > 	int part;
> > > 
> > > 	disk = get_gendisk(dev, &part);
> > > 	if (disk) {
> > > 		buffer = disk_name(disk, part, buffer);
> > > 		put_disk(disk);
> > > 	} else {
> > > 		snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
> > > 				MAJOR(dev), MINOR(dev));
> > > 	}
> > > 
> > > get_gendisk() did an internal module_get() in kobj_lookup().
> > 
> > But if kobj_lookup() succeeds, module_put() is called before returning
> > (actually it's always called, right?) 

But kobj_lookup() also calls probe(), which equals ata_probe() in the
IDE case, and ata_probe() calls get_disk() which calls
try_module_get() to acquire another reference to the module. I guess
all probe() functions behave the same way, as I've seen the same
problem with the sr_mod module.

> Oop, sorry, yes.  Peter, are you sure this is where the leak is coming from?

I'm sure that the module reference count as reported by lsmod
increases each time I access /proc/driver/pktcdvd/pktcdvd0. I can make
this problem go away either by patching __bdevname() or by deleting
the call to __bdevname() in pktcdvd.c.

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-02 23:24         ` Peter Osterlund
@ 2004-07-02 23:51           ` Andrew Morton
  2004-07-04 11:57             ` Peter Osterlund
  0 siblings, 1 reply; 27+ messages in thread
From: Andrew Morton @ 2004-07-02 23:51 UTC (permalink / raw)
  To: Peter Osterlund; +Cc: greg, viro, linux-kernel, axboe

Peter Osterlund <petero2@telia.com> wrote:
>
> > Oop, sorry, yes.  Peter, are you sure this is where the leak is coming from?
> 
> I'm sure that the module reference count as reported by lsmod
> increases each time I access /proc/driver/pktcdvd/pktcdvd0. I can make
> this problem go away either by patching __bdevname() or by deleting
> the call to __bdevname() in pktcdvd.c.

Can't you use bdevname(pd->bdev) in there?

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-02 23:51           ` Andrew Morton
@ 2004-07-04 11:57             ` Peter Osterlund
  2004-07-04 20:58               ` Andrew Morton
  0 siblings, 1 reply; 27+ messages in thread
From: Peter Osterlund @ 2004-07-04 11:57 UTC (permalink / raw)
  To: Andrew Morton; +Cc: greg, viro, linux-kernel, axboe

Andrew Morton <akpm@osdl.org> writes:

> Peter Osterlund <petero2@telia.com> wrote:
> >
> > > Oop, sorry, yes.  Peter, are you sure this is where the leak is coming from?
> > 
> > I'm sure that the module reference count as reported by lsmod
> > increases each time I access /proc/driver/pktcdvd/pktcdvd0. I can make
> > this problem go away either by patching __bdevname() or by deleting
> > the call to __bdevname() in pktcdvd.c.
> 
> Can't you use bdevname(pd->bdev) in there?

Not without more changes, because pd->bdev is only non-NULL when the
device is actually opened by someone. When the device is setup (ie
mapped to a real device) but not opened, pd->bdev (a struct
block_device *) is NULL and pd->dev (a dev_t) is the only thing that
keeps track of the mapping between the packet device and the real
device.

Maybe that's a bad design though. Perhaps pd->dev can be eliminated
altogether.

But anyway, if __bdevname() leaks a module reference it should get
fixed, right?

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH] Fix race in pktcdvd kernel thread handling
  2004-07-01 13:34 [PATCH] CDRW packet writing support for 2.6.7-bk13 Peter Osterlund
  2004-07-02 21:52 ` Peter Osterlund
@ 2004-07-04 12:30 ` Peter Osterlund
  2004-07-04 12:37 ` [PATCH] Fix open/close races in pktcdvd Peter Osterlund
  2004-07-04 13:05 ` [PATCH] CDRW packet writing support for 2.6.7-bk13 Christoph Hellwig
  3 siblings, 0 replies; 27+ messages in thread
From: Peter Osterlund @ 2004-07-04 12:30 UTC (permalink / raw)
  To: linux-kernel; +Cc: Jens Axboe, Andrew Morton

Running "pktsetup -d" immediately after running "pktsetup" can
deadlock, because if the kcdrwd thread hasn't flushed the pending
signals before pkt_remove_dev() calls kill_proc(), kcdrwd() will not
be woken up.

This patch fixes it by making sure the kcdrwd() thread has finished
its initialization before the thread creator returns from
pkt_new_dev().


Signed-off-by: Peter Osterlund <petero2@telia.com>

---

 linux-petero/drivers/block/pktcdvd.c |    9 ++++++---
 linux-petero/include/linux/pktcdvd.h |    2 +-
 2 files changed, 7 insertions(+), 4 deletions(-)

diff -puN drivers/block/pktcdvd.c~packet-kthread-race-fix drivers/block/pktcdvd.c
--- linux/drivers/block/pktcdvd.c~packet-kthread-race-fix	2004-07-04 14:09:40.183162592 +0200
+++ linux-petero/drivers/block/pktcdvd.c	2004-07-04 14:09:40.192161224 +0200
@@ -1182,6 +1182,8 @@ static int kcdrwd(void *foobar)
 	siginitsetinv(&current->blocked, sigmask(SIGKILL));
 	flush_signals(current);
 
+	up(&pd->cdrw.thr_sem);
+
 	for (;;) {
 		DECLARE_WAITQUEUE(wait, current);
 
@@ -1276,7 +1278,7 @@ work_to_do:
 		pkt_iosched_process_queue(pd);
 	}
 
-	complete_and_exit(&pd->cdrw.thr_compl, 0);
+	up(&pd->cdrw.thr_sem);
 	return 0;
 }
 
@@ -2407,7 +2409,7 @@ static int pkt_new_dev(struct pktcdvd_de
 	sprintf(pd->name, "pktcdvd%d", minor);
 	atomic_set(&pd->refcnt, 0);
 	init_waitqueue_head(&pd->wqueue);
-	init_completion(&pd->cdrw.thr_compl);
+	init_MUTEX_LOCKED(&pd->cdrw.thr_sem);
 
 	pkt_init_queue(pd);
 
@@ -2418,6 +2420,7 @@ static int pkt_new_dev(struct pktcdvd_de
 		ret = -EBUSY;
 		goto out_thread;
 	}
+	down(&pd->cdrw.thr_sem);
 
 	create_proc_read_entry(pd->name, 0, pkt_proc, pkt_read_proc, pd);
 	DPRINTK("pktcdvd: writer %d mapped to %s\n", minor, bdevname(bdev, b));
@@ -2484,7 +2487,7 @@ static int pkt_remove_dev(struct pktcdvd
 			printk("pkt_remove_dev: can't kill kernel thread\n");
 			return ret;
 		}
-		wait_for_completion(&pd->cdrw.thr_compl);
+		down(&pd->cdrw.thr_sem);
 	}
 
 	/*
diff -puN include/linux/pktcdvd.h~packet-kthread-race-fix include/linux/pktcdvd.h
--- linux/include/linux/pktcdvd.h~packet-kthread-race-fix	2004-07-04 14:09:40.185162288 +0200
+++ linux-petero/include/linux/pktcdvd.h	2004-07-04 14:09:40.192161224 +0200
@@ -140,7 +140,7 @@ struct packet_cdrw
 	spinlock_t		active_list_lock; /* Serialize access to pkt_active_list */
 	pid_t			pid;
 	int			time_to_die;
-	struct completion	thr_compl;
+	struct semaphore	thr_sem;
 	elevator_merge_fn	*elv_merge_fn;
 	elevator_completed_req_fn *elv_completed_req_fn;
 	merge_requests_fn	*merge_requests_fn;
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* [PATCH] Fix open/close races in pktcdvd
  2004-07-01 13:34 [PATCH] CDRW packet writing support for 2.6.7-bk13 Peter Osterlund
  2004-07-02 21:52 ` Peter Osterlund
  2004-07-04 12:30 ` [PATCH] Fix race in pktcdvd kernel thread handling Peter Osterlund
@ 2004-07-04 12:37 ` Peter Osterlund
  2004-07-04 13:05 ` [PATCH] CDRW packet writing support for 2.6.7-bk13 Christoph Hellwig
  3 siblings, 0 replies; 27+ messages in thread
From: Peter Osterlund @ 2004-07-04 12:37 UTC (permalink / raw)
  To: linux-kernel; +Cc: Jens Axboe, Andrew Morton

The handling of the pd->refcnt variable is racy in a number of
places. For example, running:

while true ; do usleep 10 ; pktsetup /dev/pktcdvd0 /dev/hdc ; done &
while true ; do pktsetup -d /dev/pktcdvd0 ; done

makes a pktsetup process get stuck in D state after a while.

This patch fixes it by introducing a mutex to protect the refcnt
variable and critical sections in the open/release/setup/tear-down
functions.

This patch applies cleanly on top of the "Fix race in pktcdvd kernel
thread handling" patch.


Signed-off-by: Peter Osterlund <petero2@telia.com>

---

 linux-petero/drivers/block/pktcdvd.c |   88 +++++++++++++++++++++--------------
 linux-petero/include/linux/pktcdvd.h |    3 -
 2 files changed, 55 insertions(+), 36 deletions(-)

diff -puN drivers/block/pktcdvd.c~packet-open-race-fix drivers/block/pktcdvd.c
--- linux/drivers/block/pktcdvd.c~packet-open-race-fix	2004-07-04 14:09:40.840062728 +0200
+++ linux-petero/drivers/block/pktcdvd.c	2004-07-04 14:09:40.850061208 +0200
@@ -1998,10 +1998,6 @@ static void pkt_release_dev(struct pktcd
 {
 	struct block_device *bdev;
 
-	atomic_dec(&pd->refcnt);
-	if (atomic_read(&pd->refcnt) > 0)
-		return;
-
 	bdev = bdget(pd->pkt_dev);
 	if (bdev) {
 		fsync_bdev(bdev);
@@ -2029,6 +2025,7 @@ static int pkt_open(struct inode *inode,
 	struct pktcdvd_device *pd = NULL;
 	struct block_device *pkt_bdev;
 	int ret;
+	int special_open, exclusive;
 
 	VPRINTK("pktcdvd: entering open\n");
 
@@ -2038,21 +2035,30 @@ static int pkt_open(struct inode *inode,
 		goto out;
 	}
 
+	special_open = 0;
+	if (((file->f_flags & O_ACCMODE) == O_RDONLY) && (file->f_flags & O_CREAT))
+		special_open = 1;
+
+	exclusive = 0;
+	if ((file->f_mode & FMODE_WRITE) || special_open)
+		exclusive = 1;
+
 	/*
 	 * either device is not configured, or pktsetup is old and doesn't
 	 * use O_CREAT to create device
 	 */
 	pd = &pkt_devs[iminor(inode)];
-	if (!pd->dev && !(file->f_flags & O_CREAT)) {
+	if (!pd->dev && !special_open) {
 		VPRINTK("pktcdvd: not configured and O_CREAT not set\n");
 		ret = -ENXIO;
 		goto out;
 	}
 
-	atomic_inc(&pd->refcnt);
-	if (atomic_read(&pd->refcnt) > 1) {
-		if (file->f_mode & FMODE_WRITE) {
-			VPRINTK("pktcdvd: busy open for write\n");
+	down(&pd->ctl_mutex);
+	pd->refcnt++;
+	if (pd->refcnt > 1) {
+		if (exclusive) {
+			VPRINTK("pktcdvd: busy open\n");
 			ret = -EBUSY;
 			goto out_dec;
 		}
@@ -2060,10 +2066,10 @@ static int pkt_open(struct inode *inode,
 		/*
 		 * Not first open, everything is already set up
 		 */
-		return 0;
+		goto done;
 	}
 
-	if (((file->f_flags & O_ACCMODE) != O_RDONLY) || !(file->f_flags & O_CREAT)) {
+	if (!special_open) {
 		if (pkt_open_dev(pd, file->f_mode & FMODE_WRITE)) {
 			ret = -EIO;
 			goto out_dec;
@@ -2079,16 +2085,20 @@ static int pkt_open(struct inode *inode,
 		set_blocksize(pkt_bdev, CD_FRAMESIZE);
 		bdput(pkt_bdev);
 	}
+
+done:
+	up(&pd->ctl_mutex);
 	return 0;
 
 out_dec:
-	atomic_dec(&pd->refcnt);
-	if (atomic_read(&pd->refcnt) == 0) {
+	pd->refcnt--;
+	if (pd->refcnt == 0) {
 		if (pd->bdev) {
 			blkdev_put(pd->bdev);
 			pd->bdev = NULL;
 		}
 	}
+	up(&pd->ctl_mutex);
 out:
 	VPRINTK("pktcdvd: failed open (%d)\n", ret);
 	return ret;
@@ -2099,11 +2109,17 @@ static int pkt_close(struct inode *inode
 	struct pktcdvd_device *pd = &pkt_devs[iminor(inode)];
 	int ret = 0;
 
+	down(&pd->ctl_mutex);
+	pd->refcnt--;
+	BUG_ON(pd->refcnt < 0);
+	if (pd->refcnt > 0)
+		goto out;
 	if (pd->dev) {
 		int flush = test_bit(PACKET_WRITABLE, &pd->flags);
 		pkt_release_dev(pd, flush);
 	}
-
+out:
+	up(&pd->ctl_mutex);
 	return ret;
 }
 
@@ -2332,7 +2348,7 @@ static int pkt_proc_device(struct pktcdv
 	b += sprintf(b, "\tread:\t\t\t%lukB\n", pd->stats.secs_r >> 1);
 
 	b += sprintf(b, "\nMisc:\n");
-	b += sprintf(b, "\treference count:\t%d\n", atomic_read(&pd->refcnt));
+	b += sprintf(b, "\treference count:\t%d\n", pd->refcnt);
 	b += sprintf(b, "\tflags:\t\t\t0x%lx\n", pd->flags);
 	b += sprintf(b, "\tread speed:\t\t%ukB/s\n", pd->read_speed * 150);
 	b += sprintf(b, "\twrite speed:\t\t%ukB/s\n", pd->write_speed * 150);
@@ -2371,8 +2387,6 @@ static int pkt_read_proc(char *page, cha
 	return len;
 }
 
-extern struct block_device_operations pktcdvd_ops;
-
 static int pkt_new_dev(struct pktcdvd_device *pd, struct block_device *bdev)
 {
 	dev_t dev = bdev->bd_dev;
@@ -2392,10 +2406,8 @@ static int pkt_new_dev(struct pktcdvd_de
 	/* This is safe, since we have a reference from open(). */
 	__module_get(THIS_MODULE);
 
-	memset(pd, 0, sizeof(struct pktcdvd_device));
+	memset(&pd->stats, 0, sizeof(struct packet_stats));
 
-	spin_lock_init(&pd->lock);
-	spin_lock_init(&pd->iosched.lock);
 	if (!pkt_grow_pktlist(pd, CONFIG_CDROM_PKTCDVD_BUFFERS)) {
 		printk("pktcdvd: not enough memory for buffers\n");
 		ret = -ENOMEM;
@@ -2404,12 +2416,7 @@ static int pkt_new_dev(struct pktcdvd_de
 
 	set_blocksize(bdev, CD_FRAMESIZE);
 	pd->dev = dev;
-	pd->bdev = NULL;
-	pd->pkt_dev = MKDEV(PACKET_MAJOR, minor);
-	sprintf(pd->name, "pktcdvd%d", minor);
-	atomic_set(&pd->refcnt, 0);
-	init_waitqueue_head(&pd->wqueue);
-	init_MUTEX_LOCKED(&pd->cdrw.thr_sem);
+	BUG_ON(pd->bdev);
 
 	pkt_init_queue(pd);
 
@@ -2428,7 +2435,6 @@ static int pkt_new_dev(struct pktcdvd_de
 
 out_thread:
 	pkt_shrink_pktlist(pd);
-	memset(pd, 0, sizeof(*pd));
 out_mem:
 	/* This is safe: open() is still holding a reference. */
 	module_put(THIS_MODULE);
@@ -2464,10 +2470,7 @@ static int pkt_setup_dev(struct pktcdvd_
 		printk("pktcdvd: Can't write to read-only dev\n");
 		goto out;
 	}
-	if ((ret = pkt_new_dev(pd, inode->i_bdev)))
-		goto out;
-
-	atomic_inc(&pd->refcnt);
+	ret = pkt_new_dev(pd, inode->i_bdev);
 
 out:
 	fput(file);
@@ -2502,8 +2505,8 @@ static int pkt_remove_dev(struct pktcdvd
 	pkt_shrink_pktlist(pd);
 
 	remove_proc_entry(pd->name, pkt_proc);
+	pd->dev = 0;
 	DPRINTK("pktcdvd: writer %d unmapped\n", pkt_get_minor(pd));
-	memset(pd, 0, sizeof(struct pktcdvd_device));
 	/* This is safe: open() is still holding a reference. */
 	module_put(THIS_MODULE);
 	return 0;
@@ -2527,6 +2530,7 @@ static int pkt_media_changed(struct gend
 static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
 {
 	struct pktcdvd_device *pd = &pkt_devs[iminor(inode)];
+	int ret;
 
 	VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode));
 
@@ -2553,9 +2557,14 @@ static int pkt_ioctl(struct inode *inode
 	case PACKET_TEARDOWN_DEV:
 		if (!capable(CAP_SYS_ADMIN))
 			return -EPERM;
-		if (atomic_read(&pd->refcnt) != 1)
-			return -EBUSY;
-		return pkt_remove_dev(pd);
+		down(&pd->ctl_mutex);
+		BUG_ON(pd->refcnt < 1);
+		if (pd->refcnt != 1)
+			ret = -EBUSY;
+		else
+			ret = pkt_remove_dev(pd);
+		up(&pd->ctl_mutex);
+		return ret;
 
 	case BLKROSET:
 		if (capable(CAP_SYS_ADMIN))
@@ -2629,6 +2638,15 @@ int pkt_init(void)
 	for (i = 0; i < MAX_WRITERS; i++) {
 		struct pktcdvd_device *pd = &pkt_devs[i];
 		struct gendisk *disk = disks[i];
+
+		spin_lock_init(&pd->lock);
+		spin_lock_init(&pd->iosched.lock);
+		pd->pkt_dev = MKDEV(PACKET_MAJOR, i);
+		sprintf(pd->name, "pktcdvd%d", i);
+		init_waitqueue_head(&pd->wqueue);
+		init_MUTEX_LOCKED(&pd->cdrw.thr_sem);
+		init_MUTEX(&pd->ctl_mutex);
+
 		disk->major = PACKET_MAJOR;
 		disk->first_minor = i;
 		disk->fops = &pktcdvd_ops;
diff -puN include/linux/pktcdvd.h~packet-open-race-fix include/linux/pktcdvd.h
--- linux/include/linux/pktcdvd.h~packet-open-race-fix	2004-07-04 14:09:40.843062272 +0200
+++ linux-petero/include/linux/pktcdvd.h	2004-07-04 14:09:40.850061208 +0200
@@ -236,7 +236,8 @@ struct pktcdvd_device
 	char			name[20];
 	struct packet_settings	settings;
 	struct packet_stats	stats;
-	atomic_t		refcnt;
+	struct semaphore	ctl_mutex;	/* Serialize access to refcnt */
+	int			refcnt;		/* Open count */
 	__u8			write_speed;	/* current write speed */
 	__u8			read_speed;	/* current read speed */
 	unsigned long		offset;		/* start offset */
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-01 13:34 [PATCH] CDRW packet writing support for 2.6.7-bk13 Peter Osterlund
                   ` (2 preceding siblings ...)
  2004-07-04 12:37 ` [PATCH] Fix open/close races in pktcdvd Peter Osterlund
@ 2004-07-04 13:05 ` Christoph Hellwig
  2004-07-04 23:49   ` Peter Osterlund
  2004-07-07 10:06   ` Peter Osterlund
  3 siblings, 2 replies; 27+ messages in thread
From: Christoph Hellwig @ 2004-07-04 13:05 UTC (permalink / raw)
  To: Peter Osterlund; +Cc: linux-kernel, Jens Axboe, Andrew Morton

> + * - Generic interface for UDF to submit large packets for variable length
> + *   packet writing

Huh, what's bad about bios?

> +#include <linux/buffer_head.h>

Where do you need buffer_head.h?

> +#define SCSI_IOCTL_SEND_COMMAND	1

Please include scsi_ioctl.h instead of duplicating it.

> +static struct gendisk *disks[MAX_WRITERS];

Please add a pointer to the gendisk to struct pktcdvd_device instead
of a global array

> +
> +static struct pktcdvd_device *pkt_find_dev(request_queue_t *q)
> +{
> +	int i;
> +
> +	for (i = 0; i < MAX_WRITERS; i++)
> +		if (pkt_devs[i].bdev && bdev_get_queue(pkt_devs[i].bdev) == q)
> +			return &pkt_devs[i];
> +
> +	return NULL;
> +}

Please just store the pktcdvd_device * in q->queuedata.

> +	sprintf(current->comm, pd->name);

not needed, saemonize does it for you.

> +static int pkt_get_minor(struct pktcdvd_device *pd)
> +{
> +	int minor;
> +	for (minor = 0; minor < MAX_WRITERS; minor++)
> +		if (pd == &pkt_devs[minor])
> +			break;
> +	BUG_ON(minor == MAX_WRITERS);
> +	return minor;
> +}

Shouldn't be needed at all.  Use an idr allocator to get a free minor in
the setup, the actual I/O code shouldn't care about minors at all (if you
follow my suggestions in the begining of this mail that should be taken
care of)
and the actual code should never

> +	pd->cdrw.elv_merge_fn = q->elevator.elevator_merge_fn;
> +	pd->cdrw.elv_completed_req_fn = q->elevator.elevator_completed_req_fn;
> +	pd->cdrw.merge_requests_fn = q->merge_requests_fn;
> +	q->elevator.elevator_merge_fn = pkt_lowlevel_elv_merge_fn;
> +	q->elevator.elevator_completed_req_fn = pkt_lowlevel_elv_completed_req_fn;

This looks really really fishy.  Playing with other drivers' elevator settings
can't be safe.  I'd say wait for the runtime selectable I/O scheduler that's
planned for a while and add a special packetwriting scheduler that you switch
to.

> +/*
> + * called when the device is closed. makes sure that the device flushes
> + * the internal cache before we close.
> + */
> +static void pkt_release_dev(struct pktcdvd_device *pd, int flush)
> +{
> +	struct block_device *bdev;
> +
> +	atomic_dec(&pd->refcnt);
> +	if (atomic_read(&pd->refcnt) > 0)
> +		return;
> +
> +	bdev = bdget(pd->pkt_dev);
> +	if (bdev) {

You reallu should keep a reference to the underlying bdev as long as you use
it, aka bdev_get + blkdev_get in ->open, blkdev_put in ->release

> +		fsync_bdev(bdev);

fs/block_dev.c already does a sync_blockdev() on last close, that should
be enough.

> +static int pkt_proc_device(struct pktcdvd_device *pd, char *buf)
> +{

seq_file interface please, or even better a one value per file sysfs
interface.

> +	pd->cdrw.pid = kernel_thread(kcdrwd, pd, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);

please use the kernel/ktread.c infastructure.

> +static int pkt_setup_dev(struct pktcdvd_device *pd, unsigned int arg)
> +{
> +	struct inode *inode;
> +	struct file *file;
> +	int ret;
> +
> +	if ((file = fget(arg)) == NULL) {
> +		printk("pktcdvd: bad file descriptor passed\n");
> +		return -EBADF;
> +	}
> +
> +	ret = -EINVAL;
> +	if ((inode = file->f_dentry->d_inode) == NULL) {
> +		printk("pktcdvd: huh? file descriptor contains no inode?\n");
> +		goto out;
> +	}

If fget is successfull file->f_dentry->d_inode can't be NULL.

> +	case BLKROSET:
> +		if (capable(CAP_SYS_ADMIN))
> +			clear_bit(PACKET_WRITABLE, &pd->flags);
> +	case BLKROGET:
> +	case BLKSSZGET:
> +	case BLKFLSBUF:
> +		if (!pd->bdev)
> +			return -ENXIO;
> +		return -EINVAL;		    /* Handled by blkdev layer */
> +

These aren't handled by drivers anyore in 2.6

> +	for (i = 0; i < MAX_WRITERS; i++) {
> +		disks[i] = alloc_disk(1);
> +		if (!disks[i])
> +			goto out_mem2;
> +	}
> +
> +	for (i = 0; i < MAX_WRITERS; i++) {
> +		struct pktcdvd_device *pd = &pkt_devs[i];
> +		struct gendisk *disk = disks[i];
> +		disk->major = PACKET_MAJOR;
> +		disk->first_minor = i;
> +		disk->fops = &pktcdvd_ops;
> +		disk->flags = GENHD_FL_REMOVABLE;
> +		sprintf(disk->disk_name, "pktcdvd%d", i);
> +		sprintf(disk->devfs_name, "pktcdvd/%d", i);
> +		disk->private_data = pd;
> +		disk->queue = blk_alloc_queue(GFP_KERNEL);
> +		if (!disk->queue)
> +			goto out_mem3;
> +		add_disk(disk);
> +	}

Please allocate all these on demand only when you actually attach
a device.


All in all I really wonder whether a separate driver is really that a good
fit for the functionality or whether it should be more integrated with the
block layer, ala drivers/block/scsi_ioctl.c

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-04 11:57             ` Peter Osterlund
@ 2004-07-04 20:58               ` Andrew Morton
  2004-07-04 21:06                 ` Christoph Hellwig
  0 siblings, 1 reply; 27+ messages in thread
From: Andrew Morton @ 2004-07-04 20:58 UTC (permalink / raw)
  To: Peter Osterlund; +Cc: greg, viro, linux-kernel, axboe

Peter Osterlund <petero2@telia.com> wrote:
>
> But anyway, if __bdevname() leaks a module reference it should get
>  fixed, right?

Yes.  The questions is, where's the bug?  Who should be undoing the
module_get() which happened via

	__bdevname
	->get_gendisk
	  ->kobj_lookup
	    ->ata_probe
	      ->get_disk
	        ->try_module_get

?

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-04 20:58               ` Andrew Morton
@ 2004-07-04 21:06                 ` Christoph Hellwig
  0 siblings, 0 replies; 27+ messages in thread
From: Christoph Hellwig @ 2004-07-04 21:06 UTC (permalink / raw)
  To: Andrew Morton; +Cc: Peter Osterlund, greg, viro, linux-kernel, axboe

On Sun, Jul 04, 2004 at 01:58:42PM -0700, Andrew Morton wrote:
> Peter Osterlund <petero2@telia.com> wrote:
> >
> > But anyway, if __bdevname() leaks a module reference it should get
> >  fixed, right?
> 
> Yes.  The questions is, where's the bug?

in __bdevname.  It shouldn't try the get_gendisk.  I introduced that long
ago, but it's wrong.  Use bdevname where you can, else we can't do much
else than printing the namjor/minor sanely.

The packet writing code should always use bdevname() (see my previous
mail on how to get there), and here's a patch to fix __bdevname:


--- 1.124/fs/partitions/check.c	2004-06-18 08:43:53 +02:00
+++ edited/fs/partitions/check.c	2004-07-05 01:04:11 +02:00
@@ -137,25 +137,14 @@
 EXPORT_SYMBOL(bdevname);
 
 /*
- * NOTE: this cannot be called from interrupt context.
- *
- * But in interrupt context you should really have a struct
- * block_device anyway and use bdevname() above.
+ * There's very little reason to use this, you should really
+ * have a struct block_device just about everywhere and use
+ * bdevname() instead.
  */
 const char *__bdevname(dev_t dev, char *buffer)
 {
-	struct gendisk *disk;
-	int part;
-
-	disk = get_gendisk(dev, &part);
-	if (disk) {
-		buffer = disk_name(disk, part, buffer);
-		put_disk(disk);
-	} else {
-		snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
+	snprintf(buffer, BDEVNAME_SIZE, "unknown-block(%u,%u)",
 				MAJOR(dev), MINOR(dev));
-	}
-
 	return buffer;
 }
 

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-04 13:05 ` [PATCH] CDRW packet writing support for 2.6.7-bk13 Christoph Hellwig
@ 2004-07-04 23:49   ` Peter Osterlund
  2004-07-05  0:01     ` Peter Osterlund
                       ` (3 more replies)
  2004-07-07 10:06   ` Peter Osterlund
  1 sibling, 4 replies; 27+ messages in thread
From: Peter Osterlund @ 2004-07-04 23:49 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: linux-kernel, Jens Axboe, Andrew Morton

Hi!

Thanks a lot for reviewing the patch.

Christoph Hellwig <hch@infradead.org> writes:

> > + * - Generic interface for UDF to submit large packets for variable length
> > + *   packet writing
> 
> Huh, what's bad about bios?

Nothing, that comment is a leftover from before bios existed. Fixed.

> > +#include <linux/buffer_head.h>
> 
> Where do you need buffer_head.h?

For fsync_bdev(), which you say is not needed anyway. Also for the
invalidate_bdev() call in pkt_remove_dev(). The loop driver is also
calling invalidate_bdev() in its loop_clr_fd() function, so I guess
that call is needed.

> > +#define SCSI_IOCTL_SEND_COMMAND	1
> 
> Please include scsi_ioctl.h instead of duplicating it.

Done.

> > +static struct gendisk *disks[MAX_WRITERS];
> 
> Please add a pointer to the gendisk to struct pktcdvd_device instead
> of a global array

Done.

> > +
> > +static struct pktcdvd_device *pkt_find_dev(request_queue_t *q)
> > +{
> > +	int i;
> > +
> > +	for (i = 0; i < MAX_WRITERS; i++)
> > +		if (pkt_devs[i].bdev && bdev_get_queue(pkt_devs[i].bdev) == q)
> > +			return &pkt_devs[i];
> > +
> > +	return NULL;
> > +}
> 
> Please just store the pktcdvd_device * in q->queuedata.

I don't think that's possible, because q is the queue for the device
this device is attached to, and the driver for that device might use
queuedata for something else.

> > +	sprintf(current->comm, pd->name);
> 
> not needed, saemonize does it for you.

Fixed.

> > +static int pkt_get_minor(struct pktcdvd_device *pd)
> > +{
> > +	int minor;
> > +	for (minor = 0; minor < MAX_WRITERS; minor++)
> > +		if (pd == &pkt_devs[minor])
> > +			break;
> > +	BUG_ON(minor == MAX_WRITERS);
> > +	return minor;
> > +}
> 
> Shouldn't be needed at all.

Removed.

> > +	pd->cdrw.elv_merge_fn = q->elevator.elevator_merge_fn;
> > +	pd->cdrw.elv_completed_req_fn = q->elevator.elevator_completed_req_fn;
> > +	pd->cdrw.merge_requests_fn = q->merge_requests_fn;
> > +	q->elevator.elevator_merge_fn = pkt_lowlevel_elv_merge_fn;
> > +	q->elevator.elevator_completed_req_fn = pkt_lowlevel_elv_completed_req_fn;
> 
> This looks really really fishy.  Playing with other drivers' elevator settings
> can't be safe.  I'd say wait for the runtime selectable I/O scheduler that's
> planned for a while and add a special packetwriting scheduler that you switch
> to.

OK, I'll wait. ;)

> > +/*
> > + * called when the device is closed. makes sure that the device flushes
> > + * the internal cache before we close.
> > + */
> > +static void pkt_release_dev(struct pktcdvd_device *pd, int flush)
> > +{
> > +	struct block_device *bdev;
> > +
> > +	atomic_dec(&pd->refcnt);
> > +	if (atomic_read(&pd->refcnt) > 0)
> > +		return;
> > +
> > +	bdev = bdget(pd->pkt_dev);
> > +	if (bdev) {
> 
> You reallu should keep a reference to the underlying bdev as long as you use
> it, aka bdev_get + blkdev_get in ->open, blkdev_put in ->release

I'll work on this tomorrow.

> > +		fsync_bdev(bdev);
> 
> fs/block_dev.c already does a sync_blockdev() on last close, that should
> be enough.

OK, removed.

> > +static int pkt_proc_device(struct pktcdvd_device *pd, char *buf)
> > +{
> 
> seq_file interface please, or even better a one value per file sysfs
> interface.

I'll work on this tomorrow too.

> > +	pd->cdrw.pid = kernel_thread(kcdrwd, pd, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
> 
> please use the kernel/ktread.c infastructure.

Fixed.

> > +static int pkt_setup_dev(struct pktcdvd_device *pd, unsigned int arg)
> > +{
> > +	struct inode *inode;
> > +	struct file *file;
> > +	int ret;
> > +
> > +	if ((file = fget(arg)) == NULL) {
> > +		printk("pktcdvd: bad file descriptor passed\n");
> > +		return -EBADF;
> > +	}
> > +
> > +	ret = -EINVAL;
> > +	if ((inode = file->f_dentry->d_inode) == NULL) {
> > +		printk("pktcdvd: huh? file descriptor contains no inode?\n");
> > +		goto out;
> > +	}
> 
> If fget is successfull file->f_dentry->d_inode can't be NULL.

Fixed.

> > +	case BLKROSET:
> > +		if (capable(CAP_SYS_ADMIN))
> > +			clear_bit(PACKET_WRITABLE, &pd->flags);
> > +	case BLKROGET:
> > +	case BLKSSZGET:
> > +	case BLKFLSBUF:
> > +		if (!pd->bdev)
> > +			return -ENXIO;
> > +		return -EINVAL;		    /* Handled by blkdev layer */
> > +
> 
> These aren't handled by drivers anyore in 2.6

OK, removed.

> > +	for (i = 0; i < MAX_WRITERS; i++) {
> > +		disks[i] = alloc_disk(1);
> > +		if (!disks[i])
> > +			goto out_mem2;
> > +	}
> > +
> > +	for (i = 0; i < MAX_WRITERS; i++) {
> > +		struct pktcdvd_device *pd = &pkt_devs[i];
> > +		struct gendisk *disk = disks[i];
> > +		disk->major = PACKET_MAJOR;
> > +		disk->first_minor = i;
> > +		disk->fops = &pktcdvd_ops;
> > +		disk->flags = GENHD_FL_REMOVABLE;
> > +		sprintf(disk->disk_name, "pktcdvd%d", i);
> > +		sprintf(disk->devfs_name, "pktcdvd/%d", i);
> > +		disk->private_data = pd;
> > +		disk->queue = blk_alloc_queue(GFP_KERNEL);
> > +		if (!disk->queue)
> > +			goto out_mem3;
> > +		add_disk(disk);
> > +	}
> 
> Please allocate all these on demand only when you actually attach
> a device.

But I need to open the device to be able to perform the ioctl to
attach a device, and I can't open the device until add_disk() has been
called. The loop device does the same thing.

> All in all I really wonder whether a separate driver is really that
> a good fit for the functionality or whether it should be more
> integrated with the block layer, ala drivers/block/scsi_ioctl.c

Jens is probably better suited than me to comment on that.


Here is an incremental patch that goes on top all patches I've posted
previously.

---------------

Various cleanups in the pktcdvd driver suggested by Christoph Hellwig.

- Removed obsolete comment.
- Don't redefine SCSI_IOCTL_SEND_COMMAND locally, use the proper
  #include instead.
- Removed the disks[] gendisk* array and added a gendisk pointer to
  struct pktcdvd_device instead.
- No need to set current->comm in kcdrwd, since daemonize does that by
  itself.
- No need to call fsync_bdev() in pkt_release_dev(), because it is
  handled by fs/block_dev.c.
- After a successful fget(), file->f_dentry->d_inode can't be NULL, so
  kill the useless check.
- The BLKROSET, BLKROGET, BLKSSZGET and BLKFLSBUF ioctls aren't
  handled by drivers any more in 2.6.
- Removed no longer needed function pkt_get_minor().
- Use the kernel/kthread.c infrastructure in the pktcdvd driver.


Signed-off-by: Peter Osterlund <petero2@telia.com>

---

 linux-petero/drivers/block/pktcdvd.c |  117 ++++++++---------------------------
 linux-petero/include/linux/pktcdvd.h |    5 -
 2 files changed, 31 insertions(+), 91 deletions(-)

diff -puN drivers/block/pktcdvd.c~packet-cleanup drivers/block/pktcdvd.c
--- linux/drivers/block/pktcdvd.c~packet-cleanup	2004-07-05 01:38:40.535610008 +0200
+++ linux-petero/drivers/block/pktcdvd.c	2004-07-05 01:39:06.680635360 +0200
@@ -12,8 +12,6 @@
  * TODO: (circa order of when I will fix it)
  * - Only able to write on CD-RW media right now.
  * - check host application code on media and set it in write page
- * - Generic interface for UDF to submit large packets for variable length
- *   packet writing
  * - interface for UDF <-> packet to negotiate a new location when a write
  *   fails.
  * - handle OPC, especially for -RW media
@@ -40,24 +38,23 @@
 #include <linux/module.h>
 #include <linux/types.h>
 #include <linux/kernel.h>
+#include <linux/kthread.h>
 #include <linux/errno.h>
 #include <linux/spinlock.h>
 #include <linux/file.h>
 #include <linux/proc_fs.h>
-#include <linux/buffer_head.h>
+#include <linux/buffer_head.h>		/* for invalidate_bdev() */
 #include <linux/devfs_fs_kernel.h>
 #include <linux/suspend.h>
 #include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_ioctl.h>
 
 #include <asm/uaccess.h>
 
-#define SCSI_IOCTL_SEND_COMMAND	1
-
 #define ZONE(sector, pd) (((sector) + (pd)->offset) & ~((pd)->settings.size - 1))
 
 static struct pktcdvd_device *pkt_devs;
 static struct proc_dir_entry *pkt_proc;
-static struct gendisk *disks[MAX_WRITERS];
 
 static struct pktcdvd_device *pkt_find_dev(request_queue_t *q)
 {
@@ -1170,19 +1167,7 @@ static int kcdrwd(void *foobar)
 	struct packet_data *pkt;
 	long min_sleep_time, residue;
 
-	/*
-	 * exit_files, mm (move to lazy-tlb, so context switches are come
-	 * extremely cheap) etc
-	 */
-	daemonize(pd->name);
-
 	set_user_nice(current, -20);
-	sprintf(current->comm, pd->name);
-
-	siginitsetinv(&current->blocked, sigmask(SIGKILL));
-	flush_signals(current);
-
-	up(&pd->cdrw.thr_sem);
 
 	for (;;) {
 		DECLARE_WAITQUEUE(wait, current);
@@ -1250,14 +1235,14 @@ static int kcdrwd(void *foobar)
 			if (signal_pending(current)) {
 				flush_signals(current);
 			}
-			if (pd->cdrw.time_to_die)
+			if (kthread_should_stop())
 				break;
 		}
 work_to_do:
 		set_current_state(TASK_RUNNING);
 		remove_wait_queue(&pd->wqueue, &wait);
 
-		if (pd->cdrw.time_to_die)
+		if (kthread_should_stop())
 			break;
 
 		/*
@@ -1278,7 +1263,6 @@ work_to_do:
 		pkt_iosched_process_queue(pd);
 	}
 
-	up(&pd->cdrw.thr_sem);
 	return 0;
 }
 
@@ -1898,16 +1882,6 @@ static int pkt_open_write(struct pktcdvd
 	return 0;
 }
 
-static int pkt_get_minor(struct pktcdvd_device *pd)
-{
-	int minor;
-	for (minor = 0; minor < MAX_WRITERS; minor++)
-		if (pd == &pkt_devs[minor])
-			break;
-	BUG_ON(minor == MAX_WRITERS);
-	return minor;
-}
-
 /*
  * called at open time.
  */
@@ -1938,7 +1912,7 @@ static int pkt_open_dev(struct pktcdvd_d
 		return ret;
 	}
 
-	set_capacity(disks[pkt_get_minor(pd)], lba << 2);
+	set_capacity(pd->disk, lba << 2);
 
 	/*
 	 * The underlying block device needs to have its merge logic
@@ -1996,14 +1970,6 @@ restore_queue:
  */
 static void pkt_release_dev(struct pktcdvd_device *pd, int flush)
 {
-	struct block_device *bdev;
-
-	bdev = bdget(pd->pkt_dev);
-	if (bdev) {
-		fsync_bdev(bdev);
-		bdput(bdev);
-	}
-
 	if (flush && pkt_flush_cache(pd))
 		DPRINTK("pktcdvd: %s not flushing cache\n", pd->name);
 
@@ -2300,7 +2266,7 @@ static int pkt_merge_bvec(request_queue_
 
 static void pkt_init_queue(struct pktcdvd_device *pd)
 {
-	request_queue_t *q = disks[pkt_get_minor(pd)]->queue;
+	request_queue_t *q = pd->disk->queue;
 
 	blk_queue_make_request(q, pkt_make_request);
 	blk_queue_hardsect_size(q, CD_FRAMESIZE);
@@ -2390,19 +2356,17 @@ static int pkt_read_proc(char *page, cha
 static int pkt_new_dev(struct pktcdvd_device *pd, struct block_device *bdev)
 {
 	dev_t dev = bdev->bd_dev;
-	int minor;
+	int i;
 	int ret = 0;
 	char b[BDEVNAME_SIZE];
 
-	for (minor = 0; minor < MAX_WRITERS; minor++) {
-		if (pkt_devs[minor].dev == dev) {
+	for (i = 0; i < MAX_WRITERS; i++) {
+		if (pkt_devs[i].dev == dev) {
 			printk("pktcdvd: %s already setup\n", bdevname(bdev, b));
 			return -EBUSY;
 		}
 	}
 
-	minor = pkt_get_minor(pd);
-
 	/* This is safe, since we have a reference from open(). */
 	__module_get(THIS_MODULE);
 
@@ -2420,17 +2384,15 @@ static int pkt_new_dev(struct pktcdvd_de
 
 	pkt_init_queue(pd);
 
-	pd->cdrw.time_to_die = 0;
-	pd->cdrw.pid = kernel_thread(kcdrwd, pd, CLONE_FS | CLONE_FILES | CLONE_SIGHAND);
-	if (pd->cdrw.pid < 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");
-		ret = -EBUSY;
+		ret = -ENOMEM;
 		goto out_thread;
 	}
-	down(&pd->cdrw.thr_sem);
 
 	create_proc_read_entry(pd->name, 0, pkt_proc, pkt_read_proc, pd);
-	DPRINTK("pktcdvd: writer %d mapped to %s\n", minor, bdevname(bdev, b));
+	DPRINTK("pktcdvd: writer %s mapped to %s\n", pd->name, bdevname(bdev, b));
 	return 0;
 
 out_thread:
@@ -2455,11 +2417,7 @@ static int pkt_setup_dev(struct pktcdvd_
 		return -EBADF;
 	}
 
-	ret = -EINVAL;
-	if ((inode = file->f_dentry->d_inode) == NULL) {
-		printk("pktcdvd: huh? file descriptor contains no inode?\n");
-		goto out;
-	}
+	inode = file->f_dentry->d_inode;
 	ret = -ENOTBLK;
 	if (!S_ISBLK(inode->i_mode)) {
 		printk("pktcdvd: device is not a block device (duh)\n");
@@ -2480,18 +2438,9 @@ out:
 static int pkt_remove_dev(struct pktcdvd_device *pd)
 {
 	struct block_device *bdev;
-	int ret;
 
-	if (pd->cdrw.pid >= 0) {
-		pd->cdrw.time_to_die = 1;
-		wmb();
-		ret = kill_proc(pd->cdrw.pid, SIGKILL, 1);
-		if (ret) {
-			printk("pkt_remove_dev: can't kill kernel thread\n");
-			return ret;
-		}
-		down(&pd->cdrw.thr_sem);
-	}
+	if (!IS_ERR(pd->cdrw.thread))
+		kthread_stop(pd->cdrw.thread);
 
 	/*
 	 * will also invalidate buffers for CD-ROM
@@ -2506,7 +2455,7 @@ static int pkt_remove_dev(struct pktcdvd
 
 	remove_proc_entry(pd->name, pkt_proc);
 	pd->dev = 0;
-	DPRINTK("pktcdvd: writer %d unmapped\n", pkt_get_minor(pd));
+	DPRINTK("pktcdvd: writer %s unmapped\n", pd->name);
 	/* This is safe: open() is still holding a reference. */
 	module_put(THIS_MODULE);
 	return 0;
@@ -2566,16 +2515,6 @@ static int pkt_ioctl(struct inode *inode
 		up(&pd->ctl_mutex);
 		return ret;
 
-	case BLKROSET:
-		if (capable(CAP_SYS_ADMIN))
-			clear_bit(PACKET_WRITABLE, &pd->flags);
-	case BLKROGET:
-	case BLKSSZGET:
-	case BLKFLSBUF:
-		if (!pd->bdev)
-			return -ENXIO;
-		return -EINVAL;		    /* Handled by blkdev layer */
-
 	/*
 	 * forward selected CDROM ioctls to CD-ROM, for UDF
 	 */
@@ -2630,21 +2569,22 @@ int pkt_init(void)
 	memset(pkt_devs, 0, MAX_WRITERS * sizeof(struct pktcdvd_device));
 
 	for (i = 0; i < MAX_WRITERS; i++) {
-		disks[i] = alloc_disk(1);
-		if (!disks[i])
+		struct pktcdvd_device *pd = &pkt_devs[i];
+
+		pd->disk = alloc_disk(1);
+		if (!pd->disk)
 			goto out_mem2;
 	}
 
 	for (i = 0; i < MAX_WRITERS; i++) {
 		struct pktcdvd_device *pd = &pkt_devs[i];
-		struct gendisk *disk = disks[i];
+		struct gendisk *disk = pd->disk;
 
 		spin_lock_init(&pd->lock);
 		spin_lock_init(&pd->iosched.lock);
 		pd->pkt_dev = MKDEV(PACKET_MAJOR, i);
 		sprintf(pd->name, "pktcdvd%d", i);
 		init_waitqueue_head(&pd->wqueue);
-		init_MUTEX_LOCKED(&pd->cdrw.thr_sem);
 		init_MUTEX(&pd->ctl_mutex);
 
 		disk->major = PACKET_MAJOR;
@@ -2667,11 +2607,11 @@ int pkt_init(void)
 
 out_mem3:
 	while (i--)
-		blk_put_queue(disks[i]->queue);
+		blk_put_queue(pkt_devs[i].disk->queue);
 	i = MAX_WRITERS;
 out_mem2:
 	while (i--)
-		put_disk(disks[i]);
+		put_disk(pkt_devs[i].disk);
 	kfree(pkt_devs);
 out_mem:
 	printk("pktcdvd: out of memory\n");
@@ -2684,9 +2624,10 @@ void pkt_exit(void)
 {
 	int i;
 	for (i = 0; i < MAX_WRITERS; i++) {
-		del_gendisk(disks[i]);
-		blk_put_queue(disks[i]->queue);
-		put_disk(disks[i]);
+		struct gendisk *disk = pkt_devs[i].disk;
+		del_gendisk(disk);
+		blk_put_queue(disk->queue);
+		put_disk(disk);
 	}
 
 	devfs_remove("pktcdvd");
diff -puN include/linux/pktcdvd.h~packet-cleanup include/linux/pktcdvd.h
--- linux/include/linux/pktcdvd.h~packet-cleanup	2004-07-05 01:38:40.538609552 +0200
+++ linux-petero/include/linux/pktcdvd.h	2004-07-05 01:39:06.681635208 +0200
@@ -138,9 +138,7 @@ struct packet_cdrw
 	struct list_head	pkt_free_list;
 	struct list_head	pkt_active_list;
 	spinlock_t		active_list_lock; /* Serialize access to pkt_active_list */
-	pid_t			pid;
-	int			time_to_die;
-	struct semaphore	thr_sem;
+	struct task_struct	*thread;
 	elevator_merge_fn	*elv_merge_fn;
 	elevator_completed_req_fn *elv_completed_req_fn;
 	merge_requests_fn	*merge_requests_fn;
@@ -256,6 +254,7 @@ struct pktcdvd_device
 	atomic_t		scan_queue;	/* Set to non-zero when pkt_handle_queue */
 						/* needs to be run. */
 	struct packet_iosched   iosched;
+	struct gendisk		*disk;
 };
 
 #endif /* __KERNEL__ */
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-04 23:49   ` Peter Osterlund
@ 2004-07-05  0:01     ` Peter Osterlund
  2004-07-10 23:20       ` Arnd Bergmann
  2004-07-05  8:17     ` [PATCH] CDRW packet writing support for 2.6.7-bk13 Jens Axboe
                       ` (2 subsequent siblings)
  3 siblings, 1 reply; 27+ messages in thread
From: Peter Osterlund @ 2004-07-05  0:01 UTC (permalink / raw)
  To: linux-kernel; +Cc: Christoph Hellwig, Jens Axboe, Andrew Morton

Peter Osterlund <petero2@telia.com> writes:

> Here is an incremental patch that goes on top all patches I've posted
> previously.

And here is a combined patch consisting of the 7 patches I've posted
so far, which should make it easier for those who want to test the
driver. Applies to 2.6.7-bk15 and probably -bk16 and -bk17 too.

  packet-2.6.7.patch
    CDRW packet writing.
  
  dvd+rw.patch
    DVD+RW support
  
  dvd-rw-packet.patch
    Packet writing support for DVD-RW and DVD+RW discs.
  
  packet-doc-update.patch
    packet-doc-update
  
  packet-kthread-race-fix.patch
    packet-kthread-race-fix
  
  packet-open-race-fix.patch
    packet-open-race-fix
  
  packet-cleanup.patch
    packet-cleanup

unchanged:
--- linux/Documentation/cdrom/00-INDEX~packet-2.6.7	2004-07-05 01:31:05.178834760 +0200
+++ linux-petero/Documentation/cdrom/00-INDEX	2004-07-05 01:31:05.203830960 +0200
@@ -22,6 +22,8 @@ mcdx
 	- info on improved Mitsumi CD-ROM driver.
 optcd
 	- info on the Optics Storage 8000 AT CD-ROM driver
+packet-writing.txt
+	- Info on the CDRW packet writing module
 sbpcd
 	- info on the SoundBlaster/Panasonic CD-ROM interface driver.
 sjcd
unchanged:
--- linux-petero/Documentation/cdrom/packet-writing.txt	2004-07-05 01:31:05.203830960 +0200
+++ linux-petero/Documentation/cdrom/packet-writing.txt	2004-07-05 01:31:37.318948720 +0200
@@ -0,0 +1,88 @@
+Getting started quick
+---------------------
+
+- Select packet support in the block device section and UDF support in
+  the file system section.
+
+- Compile and install kernel and modules, reboot.
+
+- You need the udftools package (pktsetup, mkudffs, cdrwtool).
+  Download from http://sourceforge.net/projects/linux-udf/
+
+- Grab a new CD-RW disc and format it (assuming CD-RW is hdc, substitute
+  as appropriate):
+	# cdrwtool -d /dev/hdc -q
+
+- Make sure that /dev/pktcdvd0 exists (mknod /dev/pktcdvd0 b 97 0)
+
+- Setup your writer
+	# pktsetup /dev/pktcdvd0 /dev/hdc
+
+- Now you can mount /dev/pktcdvd0 and copy files to it. Enjoy!
+	# mount /dev/pktcdvd0 /cdrom -t udf -o rw,noatime
+
+
+Packet writing for DVD-RW media
+-------------------------------
+
+DVD-RW discs can be written to much like CD-RW discs if they are in
+the so called "restricted overwrite" mode. To put a disc in restricted
+overwrite mode, run:
+
+	# dvd+rw-format /dev/hdc
+
+You can then use the disc the same way you would use a CD-RW disc:
+
+	# pktsetup /dev/pktcdvd0 /dev/hdc
+	# mount /dev/pktcdvd0 /cdrom -t udf -o rw,noatime
+
+
+Packet writing for DVD+RW media
+-------------------------------
+
+According to the DVD+RW specification, a drive supporting DVD+RW discs
+shall implement "true random writes with 2KB granularity", which means
+that it should be possible to put any filesystem with a block size >=
+2KB on such a disc. For example, it should be possible to do:
+
+	# mkudffs /dev/hdc
+	# mount /dev/hdc /cdrom -t udf -o rw,noatime
+
+However, some drives don't follow the specification and expect the
+host to perform aligned writes at 32KB boundaries. Other drives does
+follow the specification, but suffer bad performance problems if the
+writes are not 32KB aligned.
+
+Both problems can be solved by using the pktcdvd driver, which always
+generates aligned writes.
+
+	# pktsetup /dev/pktcdvd0 /dev/hdc
+	# mkudffs /dev/pktcdvd0
+	# mount /dev/pktcdvd0 /cdrom -t udf -o rw,noatime
+
+
+Notes
+-----
+
+- CD-RW media can usually not be overwritten more than about 1000
+  times, so to avoid unnecessary wear on the media, you should always
+  use the noatime mount option.
+
+- Defect management (ie automatic remapping of bad sectors) has not
+  been implemented yet, so you are likely to get at least some
+  filesystem corruption if the disc wears out.
+
+- Since the pktcdvd driver makes the disc appear as a regular block
+  device, you can put any filesystem you like on the disc. For
+  example, run:
+
+	# /sbin/mke2fs /dev/pktcdvd0
+
+  to create an ext2 filesystem on the disc.
+
+
+Links
+-----
+
+See http://fy.chalmers.se/~appro/linux/DVD+RW/ for more information
+about DVD writing.
unchanged:
--- linux-petero/drivers/block/Kconfig	2004-07-05 01:31:05.204830808 +0200
+++ linux-petero/drivers/block/Kconfig	2004-07-05 01:31:11.771832472 +0200
@@ -340,6 +340,39 @@
 	  your machine, or if you want to have a raid or loopback device
 	  bigger than 2TB.  Otherwise say N.
 
+config CDROM_PKTCDVD
+	tristate "Packet writing on CD/DVD media"
+	help
+	  If you have a CDROM drive that supports packet writing, say Y to
+	  include preliminary support. It should work with any MMC/Mt Fuji
+	  compliant ATAPI or SCSI drive, which is just about any newer CD
+	  writer.
+
+	  Currently only writing to CD-RW, DVD-RW and DVD+RW discs is possible.
+	  DVD-RW disks must be in restricted overwrite mode.
+
+	  To compile this driver as a module, choose M here: the
+	  module will be called pktcdvd.
+
+config CDROM_PKTCDVD_BUFFERS
+	int "Free buffers for data gathering"
+	depends on CDROM_PKTCDVD
+	default "8"
+	help
+	  This controls the maximum number of active concurrent packets. More
+	  concurrent packets can increase write performance, but also require
+	  more memory. Each concurrent packet will require approximately 64Kb
+	  of non-swappable kernel memory, memory which will be allocated at
+	  pktsetup time.
+
+config CDROM_PKTCDVD_WCACHE
+	bool "Enable write caching"
+	depends on CDROM_PKTCDVD
+	help
+	  If enabled, write caching will be set for the CD-R/W device. For now
+	  this option is dangerous unless the CD-RW media is known good, as we
+	  don't do deferred write error handling yet.
+
 source "drivers/s390/block/Kconfig"
 
 endmenu
unchanged:
--- linux/drivers/block/Makefile~packet-2.6.7	2004-07-05 01:31:05.184833848 +0200
+++ linux-petero/drivers/block/Makefile	2004-07-05 01:31:05.204830808 +0200
@@ -35,6 +35,7 @@ obj-$(CONFIG_BLK_DEV_XD)	+= xd.o
 obj-$(CONFIG_BLK_CPQ_DA)	+= cpqarray.o
 obj-$(CONFIG_BLK_CPQ_CISS_DA)  += cciss.o
 obj-$(CONFIG_BLK_DEV_DAC960)	+= DAC960.o
+obj-$(CONFIG_CDROM_PKTCDVD)	+= pktcdvd.o
 
 obj-$(CONFIG_BLK_DEV_UMEM)	+= umem.o
 obj-$(CONFIG_BLK_DEV_NBD)	+= nbd.o
diff -u linux-petero/drivers/block/pktcdvd.c linux-petero/drivers/block/pktcdvd.c
--- linux-petero/drivers/block/pktcdvd.c	2004-07-05 01:31:43.738972728 +0200
+++ linux-petero/drivers/block/pktcdvd.c	2004-07-05 01:31:44.889797776 +0200
@@ -0,0 +1,2647 @@
+/*
+ * Copyright (C) 2000 Jens Axboe <axboe@suse.de>
+ * Copyright (C) 2001-2004 Peter Osterlund <petero2@telia.com>
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License.  See linux/COPYING for more information.
+ *
+ * Packet writing layer for ATAPI and SCSI CD-R, CD-RW, DVD-R, and
+ * DVD-RW devices (aka an exercise in block layer masturbation)
+ *
+ *
+ * TODO: (circa order of when I will fix it)
+ * - Only able to write on CD-RW media right now.
+ * - check host application code on media and set it in write page
+ * - interface for UDF <-> packet to negotiate a new location when a write
+ *   fails.
+ * - handle OPC, especially for -RW media
+ *
+ * Theory of operation:
+ *
+ * We use a custom make_request_fn function that forwards reads directly to
+ * the underlying CD device. Write requests are either attached directly to
+ * a live packet_data object, or simply stored sequentially in a list for
+ * later processing by the kcdrwd kernel thread. This driver doesn't use
+ * any elevator functionally as defined by the elevator_s struct, but the
+ * underlying CD device uses a standard elevator.
+ *
+ * This strategy makes it possible to do very late merging of IO requests.
+ * A new bio sent to pkt_make_request can be merged with a live packet_data
+ * object even if the object is in the data gathering state.
+ *
+ *************************************************************************/
+
+#define VERSION_CODE	"v0.1.6a 2004-07-01 Jens Axboe (axboe@suse.de) and petero2@telia.com"
+
+#include <linux/pktcdvd.h>
+#include <linux/config.h>
+#include <linux/module.h>
+#include <linux/types.h>
+#include <linux/kernel.h>
+#include <linux/kthread.h>
+#include <linux/errno.h>
+#include <linux/spinlock.h>
+#include <linux/file.h>
+#include <linux/proc_fs.h>
+#include <linux/buffer_head.h>		/* for invalidate_bdev() */
+#include <linux/devfs_fs_kernel.h>
+#include <linux/suspend.h>
+#include <scsi/scsi_cmnd.h>
+#include <scsi/scsi_ioctl.h>
+
+#include <asm/uaccess.h>
+
+#define ZONE(sector, pd) (((sector) + (pd)->offset) & ~((pd)->settings.size - 1))
+
+static struct pktcdvd_device *pkt_devs;
+static struct proc_dir_entry *pkt_proc;
+
+static struct pktcdvd_device *pkt_find_dev(request_queue_t *q)
+{
+	int i;
+
+	for (i = 0; i < MAX_WRITERS; i++)
+		if (pkt_devs[i].bdev && bdev_get_queue(pkt_devs[i].bdev) == q)
+			return &pkt_devs[i];
+
+	return NULL;
+}
+
+/*
+ * The underlying block device is not allowed to merge write requests. Some
+ * CDRW drives can not handle writes larger than one packet, even if the size
+ * is a multiple of the packet size.
+ */
+static int pkt_lowlevel_elv_merge_fn(request_queue_t *q, struct request **req, struct bio *bio)
+{
+	struct pktcdvd_device *pd = pkt_find_dev(q);
+	BUG_ON(!pd);
+
+	if (bio_data_dir(bio) == WRITE)
+		return ELEVATOR_NO_MERGE;
+
+	if (pd->cdrw.elv_merge_fn)
+		return pd->cdrw.elv_merge_fn(q, req, bio);
+
+	return ELEVATOR_NO_MERGE;
+}
+
+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)) {
+		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 int pkt_lowlevel_merge_requests_fn(request_queue_t *q, struct request *rq, struct request *next)
+{
+	struct pktcdvd_device *pd = pkt_find_dev(q);
+	BUG_ON(!pd);
+
+	if (rq_data_dir(rq) == WRITE)
+		return 0;
+
+	return pd->cdrw.merge_requests_fn(q, rq, next);
+}
+
+static void pkt_bio_init(struct bio *bio)
+{
+	bio->bi_next = NULL;
+	bio->bi_flags = 1 << BIO_UPTODATE;
+	bio->bi_rw = 0;
+	bio->bi_vcnt = 0;
+	bio->bi_idx = 0;
+	bio->bi_phys_segments = 0;
+	bio->bi_hw_segments = 0;
+	bio->bi_size = 0;
+	bio->bi_max_vecs = 0;
+	bio->bi_end_io = NULL;
+	atomic_set(&bio->bi_cnt, 1);
+	bio->bi_private = NULL;
+}
+
+static void pkt_bio_destructor(struct bio *bio)
+{
+	kfree(bio->bi_io_vec);
+	kfree(bio);
+}
+
+static struct bio *pkt_bio_alloc(int nr_iovecs)
+{
+	struct bio_vec *bvl = NULL;
+	struct bio *bio;
+
+	bio = kmalloc(sizeof(struct bio), GFP_KERNEL);
+	if (!bio)
+		goto no_bio;
+	pkt_bio_init(bio);
+
+	bvl = kmalloc(nr_iovecs * sizeof(struct bio_vec), GFP_KERNEL);
+	if (!bvl)
+		goto no_bvl;
+	memset(bvl, 0, nr_iovecs * sizeof(struct bio_vec));
+
+	bio->bi_max_vecs = nr_iovecs;
+	bio->bi_io_vec = bvl;
+	bio->bi_destructor = pkt_bio_destructor;
+
+	return bio;
+
+ no_bvl:
+	kfree(bio);
+ no_bio:
+	return NULL;
+}
+
+/*
+ * Allocate a packet_data struct
+ */
+static struct packet_data *pkt_alloc_packet_data(void)
+{
+	int i;
+	struct packet_data *pkt;
+
+	pkt = kmalloc(sizeof(struct packet_data), GFP_KERNEL);
+	if (!pkt)
+		goto no_pkt;
+	memset(pkt, 0, sizeof(struct packet_data));
+
+	pkt->w_bio = pkt_bio_alloc(PACKET_MAX_SIZE);
+	if (!pkt->w_bio)
+		goto no_bio;
+
+	for (i = 0; i < PAGES_PER_PACKET; i++) {
+		pkt->pages[i] = alloc_page(GFP_KERNEL);
+		if (!pkt->pages[i])
+			goto no_page;
+	}
+	for (i = 0; i < PAGES_PER_PACKET; i++)
+		clear_page(page_address(pkt->pages[i]));
+
+	spin_lock_init(&pkt->lock);
+
+	for (i = 0; i < PACKET_MAX_SIZE; i++) {
+		struct bio *bio = pkt_bio_alloc(1);
+		if (!bio)
+			goto no_rd_bio;
+		pkt->r_bios[i] = bio;
+	}
+
+	return pkt;
+
+no_rd_bio:
+	for (i = 0; i < PACKET_MAX_SIZE; i++) {
+		struct bio *bio = pkt->r_bios[i];
+		if (bio)
+			bio_put(bio);
+	}
+
+no_page:
+	for (i = 0; i < PAGES_PER_PACKET; i++)
+		if (pkt->pages[i])
+			__free_page(pkt->pages[i]);
+	bio_put(pkt->w_bio);
+no_bio:
+	kfree(pkt);
+no_pkt:
+	return NULL;
+}
+
+/*
+ * Free a packet_data struct
+ */
+static void pkt_free_packet_data(struct packet_data *pkt)
+{
+	int i;
+
+	for (i = 0; i < PACKET_MAX_SIZE; i++) {
+		struct bio *bio = pkt->r_bios[i];
+		if (bio)
+			bio_put(bio);
+	}
+	for (i = 0; i < PAGES_PER_PACKET; i++)
+		__free_page(pkt->pages[i]);
+	bio_put(pkt->w_bio);
+	kfree(pkt);
+}
+
+static void pkt_shrink_pktlist(struct pktcdvd_device *pd)
+{
+	struct packet_data *pkt, *next;
+
+	BUG_ON(!list_empty(&pd->cdrw.pkt_active_list));
+
+	list_for_each_entry_safe(pkt, next, &pd->cdrw.pkt_free_list, list) {
+		pkt_free_packet_data(pkt);
+	}
+}
+
+static int pkt_grow_pktlist(struct pktcdvd_device *pd, int nr_packets)
+{
+	struct packet_data *pkt;
+
+	INIT_LIST_HEAD(&pd->cdrw.pkt_free_list);
+	INIT_LIST_HEAD(&pd->cdrw.pkt_active_list);
+	spin_lock_init(&pd->cdrw.active_list_lock);
+	while (nr_packets > 0) {
+		pkt = pkt_alloc_packet_data();
+		if (!pkt) {
+			pkt_shrink_pktlist(pd);
+			return 0;
+		}
+		pkt->id = nr_packets;
+		pkt->pd = pd;
+		list_add(&pkt->list, &pd->cdrw.pkt_free_list);
+		nr_packets--;
+	}
+	return 1;
+}
+
+/*
+ * Add a bio to a single linked list defined by its head and tail pointers.
+ */
+static inline void pkt_add_list_last(struct bio *bio, struct bio **list_head, struct bio **list_tail)
+{
+	bio->bi_next = NULL;
+	if (*list_tail) {
+		BUG_ON((*list_head) == NULL);
+		(*list_tail)->bi_next = bio;
+		(*list_tail) = bio;
+	} else {
+		BUG_ON((*list_head) != NULL);
+		(*list_head) = bio;
+		(*list_tail) = bio;
+	}
+}
+
+/*
+ * Remove and return the first bio from a single linked list defined by its
+ * head and tail pointers.
+ */
+static inline struct bio *pkt_get_list_first(struct bio **list_head, struct bio **list_tail)
+{
+	struct bio *bio;
+
+	if (*list_head == NULL)
+		return NULL;
+
+	bio = *list_head;
+	*list_head = bio->bi_next;
+	if (*list_head == NULL)
+		*list_tail = NULL;
+
+	bio->bi_next = NULL;
+	return bio;
+}
+
+/*
+ * Send a packet_command to the underlying block device and
+ * wait for completion.
+ */
+static int pkt_generic_packet(struct pktcdvd_device *pd, struct packet_command *cgc)
+{
+	char sense[SCSI_SENSE_BUFFERSIZE];
+	request_queue_t *q;
+	struct request *rq;
+	DECLARE_COMPLETION(wait);
+	int err = 0;
+
+	if (!pd->bdev) {
+		printk("pkt_generic_packet: no bdev\n");
+		return -ENXIO;
+	}
+
+	q = bdev_get_queue(pd->bdev);
+
+	rq = blk_get_request(q, (cgc->data_direction == CGC_DATA_WRITE) ? WRITE : READ,
+			     __GFP_WAIT);
+	rq->errors = 0;
+	rq->rq_disk = pd->bdev->bd_disk;
+	rq->bio = NULL;
+	rq->buffer = NULL;
+	rq->timeout = 60*HZ;
+	rq->data = cgc->buffer;
+	rq->data_len = cgc->buflen;
+	rq->sense = sense;
+	memset(sense, 0, sizeof(sense));
+	rq->sense_len = 0;
+	rq->flags |= REQ_BLOCK_PC | REQ_HARDBARRIER;
+	if (cgc->quiet)
+		rq->flags |= REQ_QUIET;
+	memcpy(rq->cmd, cgc->cmd, CDROM_PACKET_SIZE);
+	if (sizeof(rq->cmd) > CDROM_PACKET_SIZE)
+		memset(rq->cmd + CDROM_PACKET_SIZE, 0, sizeof(rq->cmd) - CDROM_PACKET_SIZE);
+
+	rq->ref_count++;
+	rq->flags |= REQ_NOMERGE;
+	rq->waiting = &wait;
+	elv_add_request(q, rq, ELEVATOR_INSERT_BACK, 1);
+	generic_unplug_device(q);
+	wait_for_completion(&wait);
+
+	if (rq->errors)
+		err = -EIO;
+
+	blk_put_request(rq);
+	return err;
+}
+
+/*
+ * A generic sense dump / resolve mechanism should be implemented across
+ * all ATAPI + SCSI devices.
+ */
+static void pkt_dump_sense(struct packet_command *cgc)
+{
+	static char *info[9] = { "No sense", "Recovered error", "Not ready",
+				 "Medium error", "Hardware error", "Illegal request",
+				 "Unit attention", "Data protect", "Blank check" };
+	int i;
+	struct request_sense *sense = cgc->sense;
+
+	printk("pktcdvd:");
+	for (i = 0; i < CDROM_PACKET_SIZE; i++)
+		printk(" %02x", cgc->cmd[i]);
+	printk(" - ");
+
+	if (sense == NULL) {
+		printk("no sense\n");
+		return;
+	}
+
+	printk("sense %02x.%02x.%02x", sense->sense_key, sense->asc, sense->ascq);
+
+	if (sense->sense_key > 8) {
+		printk(" (INVALID)\n");
+		return;
+	}
+
+	printk(" (%s)\n", info[sense->sense_key]);
+}
+
+/*
+ * flush the drive cache to media
+ */
+static int pkt_flush_cache(struct pktcdvd_device *pd)
+{
+	struct packet_command cgc;
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_FLUSH_CACHE;
+	cgc.quiet = 1;
+
+	/*
+	 * the IMMED bit -- we default to not setting it, although that
+	 * would allow a much faster close, this is safer
+	 */
+#if 0
+	cgc.cmd[1] = 1 << 1;
+#endif
+	return pkt_generic_packet(pd, &cgc);
+}
+
+/*
+ * speed is given as the normal factor, e.g. 4 for 4x
+ */
+static int pkt_set_speed(struct pktcdvd_device *pd, unsigned write_speed, unsigned read_speed)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	int ret;
+
+	write_speed = write_speed * 177; /* should be 176.4, but CD-RWs rounds down */
+	write_speed = min_t(unsigned, write_speed, 0xffff);
+	read_speed = read_speed * 177;
+	read_speed = min_t(unsigned, read_speed, 0xffff);
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.sense = &sense;
+	cgc.cmd[0] = GPCMD_SET_SPEED;
+	cgc.cmd[2] = (read_speed >> 8) & 0xff;
+	cgc.cmd[3] = read_speed & 0xff;
+	cgc.cmd[4] = (write_speed >> 8) & 0xff;
+	cgc.cmd[5] = write_speed & 0xff;
+
+	if ((ret = pkt_generic_packet(pd, &cgc)))
+		pkt_dump_sense(&cgc);
+
+	return ret;
+}
+
+/*
+ * Queue a bio for processing by the low-level CD device. Must be called
+ * from process context.
+ */
+static void pkt_queue_bio(struct pktcdvd_device *pd, struct bio *bio, int high_prio_read)
+{
+	spin_lock(&pd->iosched.lock);
+	if (bio_data_dir(bio) == READ) {
+		pkt_add_list_last(bio, &pd->iosched.read_queue,
+				  &pd->iosched.read_queue_tail);
+		if (high_prio_read)
+			pd->iosched.high_prio_read = 1;
+	} else {
+		pkt_add_list_last(bio, &pd->iosched.write_queue,
+				  &pd->iosched.write_queue_tail);
+	}
+	spin_unlock(&pd->iosched.lock);
+
+	atomic_set(&pd->iosched.attention, 1);
+	wake_up(&pd->wqueue);
+}
+
+/*
+ * Process the queued read/write requests. This function handles special
+ * requirements for CDRW drives:
+ * - A cache flush command must be inserted before a read request if the
+ *   previous request was a write.
+ * - Switching between reading and writing is slow, so don't it more often
+ *   than necessary.
+ * - Set the read speed according to current usage pattern. When only reading
+ *   from the device, it's best to use the highest possible read speed, but
+ *   when switching often between reading and writing, it's better to have the
+ *   same read and write speeds.
+ * - Reads originating from user space should have higher priority than reads
+ *   originating from pkt_gather_data, because some process is usually waiting
+ *   on reads of the first kind.
+ */
+static void pkt_iosched_process_queue(struct pktcdvd_device *pd)
+{
+	request_queue_t *q;
+
+	if (atomic_read(&pd->iosched.attention) == 0)
+		return;
+	atomic_set(&pd->iosched.attention, 0);
+
+	if (!pd->bdev)
+		return;
+	q = bdev_get_queue(pd->bdev);
+
+	for (;;) {
+		struct bio *bio;
+		int reads_queued, writes_queued, high_prio_read;
+
+		spin_lock(&pd->iosched.lock);
+		reads_queued = (pd->iosched.read_queue != NULL);
+		writes_queued = (pd->iosched.write_queue != NULL);
+		if (!reads_queued)
+			pd->iosched.high_prio_read = 0;
+		high_prio_read = pd->iosched.high_prio_read;
+		spin_unlock(&pd->iosched.lock);
+
+		if (!reads_queued && !writes_queued)
+			break;
+
+		if (pd->iosched.writing) {
+			if (high_prio_read || (!writes_queued && reads_queued)) {
+				if (!elv_queue_empty(q)) {
+					VPRINTK("pktcdvd: write, waiting\n");
+					break;
+				}
+				pkt_flush_cache(pd);
+				pd->iosched.writing = 0;
+			}
+		} else {
+			if (!reads_queued && writes_queued) {
+				if (!elv_queue_empty(q)) {
+					VPRINTK("pktcdvd: read, waiting\n");
+					break;
+				}
+				pd->iosched.writing = 1;
+			}
+		}
+
+		spin_lock(&pd->iosched.lock);
+		if (pd->iosched.writing) {
+			bio = pkt_get_list_first(&pd->iosched.write_queue,
+						 &pd->iosched.write_queue_tail);
+		} else {
+			bio = pkt_get_list_first(&pd->iosched.read_queue,
+						 &pd->iosched.read_queue_tail);
+		}
+		spin_unlock(&pd->iosched.lock);
+
+		if (!bio)
+			continue;
+
+		if (bio_data_dir(bio) == READ)
+			pd->iosched.successive_reads += bio->bi_size >> 10;
+		else
+			pd->iosched.successive_reads = 0;
+		if (pd->iosched.successive_reads >= HI_SPEED_SWITCH) {
+			if (pd->read_speed == pd->write_speed) {
+				pd->read_speed = 0xff;
+				pkt_set_speed(pd, pd->write_speed, pd->read_speed);
+			}
+		} else {
+			if (pd->read_speed != pd->write_speed) {
+				pd->read_speed = pd->write_speed;
+				pkt_set_speed(pd, pd->write_speed, pd->read_speed);
+			}
+		}
+
+		generic_make_request(bio);
+	}
+}
+
+/*
+ * Special care is needed if the underlying block device has a small
+ * max_phys_segments value.
+ */
+static int pkt_set_segment_merging(struct pktcdvd_device *pd, request_queue_t *q)
+{
+	if ((pd->settings.size << 9) / CD_FRAMESIZE <= q->max_phys_segments) {
+		/*
+		 * The cdrom device can handle one segment/frame
+		 */
+		clear_bit(PACKET_MERGE_SEGS, &pd->flags);
+		return 0;
+	} else if ((pd->settings.size << 9) / PAGE_SIZE <= q->max_phys_segments) {
+		/*
+		 * We can handle this case at the expense of some extra memory
+		 * copies during write operations
+		 */
+		set_bit(PACKET_MERGE_SEGS, &pd->flags);
+		return 0;
+	} else {
+		printk("pktcdvd: cdrom max_phys_segments too small\n");
+		return -EIO;
+	}
+}
+
+/*
+ * Copy CD_FRAMESIZE bytes from src_bio into a destination page
+ */
+static void pkt_copy_bio_data(struct bio *src_bio, int seg, int offs,
+			      struct page *dst_page, int dst_offs)
+{
+	unsigned int copy_size = CD_FRAMESIZE;
+
+	while (copy_size > 0) {
+		struct bio_vec *src_bvl = bio_iovec_idx(src_bio, seg);
+		void *vfrom = kmap_atomic(src_bvl->bv_page, KM_USER0) +
+			src_bvl->bv_offset + offs;
+		void *vto = page_address(dst_page) + dst_offs;
+		int len = min_t(int, copy_size, src_bvl->bv_len - offs);
+
+		BUG_ON(len < 0);
+		memcpy(vto, vfrom, len);
+		kunmap_atomic(src_bvl->bv_page, KM_USER0);
+
+		seg++;
+		offs = 0;
+		dst_offs += len;
+		copy_size -= len;
+	}
+}
+
+/*
+ * Copy all data for this packet to pkt->pages[], so that
+ * a) The number of required segments for the write bio is minimized, which
+ *    is necessary for some scsi controllers.
+ * b) The data can be used as cache to avoid read requests if we receive a
+ *    new write request for the same zone.
+ */
+static void pkt_make_local_copy(struct packet_data *pkt, struct page **pages, int *offsets)
+{
+	int f, p, offs;
+
+	/* Copy all data to pkt->pages[] */
+	p = 0;
+	offs = 0;
+	for (f = 0; f < pkt->frames; f++) {
+		if (pages[f] != pkt->pages[p]) {
+			void *vfrom = kmap_atomic(pages[f], KM_USER0) + offsets[f];
+			void *vto = page_address(pkt->pages[p]) + offs;
+			memcpy(vto, vfrom, CD_FRAMESIZE);
+			kunmap_atomic(pages[f], KM_USER0);
+			pages[f] = pkt->pages[p];
+			offsets[f] = offs;
+		} else {
+			BUG_ON(offsets[f] != offs);
+		}
+		offs += CD_FRAMESIZE;
+		if (offs >= PAGE_SIZE) {
+			BUG_ON(offs > PAGE_SIZE);
+			offs = 0;
+			p++;
+		}
+	}
+}
+
+static int pkt_end_io_read(struct bio *bio, unsigned int bytes_done, int err)
+{
+	struct packet_data *pkt = bio->bi_private;
+	struct pktcdvd_device *pd = pkt->pd;
+	BUG_ON(!pd);
+
+	if (bio->bi_size)
+		return 1;
+
+	VPRINTK("pkt_end_io_read: bio=%p sec0=%llx sec=%llx err=%d\n", bio,
+		(unsigned long long)pkt->sector, (unsigned long long)bio->bi_sector, err);
+
+	if (err)
+		atomic_inc(&pkt->io_errors);
+	if (atomic_dec_and_test(&pkt->io_wait)) {
+		atomic_inc(&pkt->run_sm);
+		wake_up(&pd->wqueue);
+	}
+
+	return 0;
+}
+
+static int pkt_end_io_packet_write(struct bio *bio, unsigned int bytes_done, int err)
+{
+	struct packet_data *pkt = bio->bi_private;
+	struct pktcdvd_device *pd = pkt->pd;
+	BUG_ON(!pd);
+
+	if (bio->bi_size)
+		return 1;
+
+	VPRINTK("pkt_end_io_packet_write: id=%d, err=%d\n", pkt->id, err);
+
+	pd->stats.pkt_ended++;
+
+	atomic_dec(&pkt->io_wait);
+	atomic_inc(&pkt->run_sm);
+	wake_up(&pd->wqueue);
+	return 0;
+}
+
+/*
+ * Schedule reads for the holes in a packet
+ */
+static void pkt_gather_data(struct pktcdvd_device *pd, struct packet_data *pkt)
+{
+	int frames_read = 0;
+	struct bio *bio;
+	int f;
+	char written[PACKET_MAX_SIZE];
+
+	BUG_ON(!pkt->orig_bios);
+
+	atomic_set(&pkt->io_wait, 0);
+	atomic_set(&pkt->io_errors, 0);
+
+	if (pkt->cache_valid) {
+		VPRINTK("pkt_gather_data: zone %llx cached\n",
+			(unsigned long long)pkt->sector);
+		goto out_account;
+	}
+
+	/*
+	 * Figure out which frames we need to read before we can write.
+	 */
+	memset(written, 0, sizeof(written));
+	spin_lock(&pkt->lock);
+	for (bio = pkt->orig_bios; bio; bio = bio->bi_next) {
+		int first_frame = (bio->bi_sector - pkt->sector) / (CD_FRAMESIZE >> 9);
+		int num_frames = bio->bi_size / CD_FRAMESIZE;
+		BUG_ON(first_frame < 0);
+		BUG_ON(first_frame + num_frames > pkt->frames);
+		for (f = first_frame; f < first_frame + num_frames; f++)
+			written[f] = 1;
+	}
+	spin_unlock(&pkt->lock);
+
+	/*
+	 * Schedule reads for missing parts of the packet.
+	 */
+	for (f = 0; f < pkt->frames; f++) {
+		int p, offset;
+		if (written[f])
+			continue;
+		bio = pkt->r_bios[f];
+		pkt_bio_init(bio);
+		bio->bi_max_vecs = 1;
+		bio->bi_sector = pkt->sector + f * (CD_FRAMESIZE >> 9);
+		bio->bi_bdev = pd->bdev;
+		bio->bi_end_io = pkt_end_io_read;
+		bio->bi_private = pkt;
+
+		p = (f * CD_FRAMESIZE) / PAGE_SIZE;
+		offset = (f * CD_FRAMESIZE) % PAGE_SIZE;
+		VPRINTK("pkt_gather_data: Adding frame %d, page:%p offs:%d\n",
+			f, pkt->pages[p], offset);
+		if (!bio_add_page(bio, pkt->pages[p], CD_FRAMESIZE, offset))
+			BUG();
+
+		atomic_inc(&pkt->io_wait);
+		bio->bi_rw = READ;
+		pkt_queue_bio(pd, bio, 0);
+		frames_read++;
+	}
+
+out_account:
+	VPRINTK("pkt_gather_data: need %d frames for zone %llx\n",
+		frames_read, (unsigned long long)pkt->sector);
+	pd->stats.pkt_started++;
+	pd->stats.secs_rg += frames_read * (CD_FRAMESIZE >> 9);
+	pd->stats.secs_w += pd->settings.size;
+}
+
+/*
+ * Find a packet matching zone, or the least recently used packet if
+ * there is no match.
+ */
+static struct packet_data *pkt_get_packet_data(struct pktcdvd_device *pd, int zone)
+{
+	struct packet_data *pkt;
+
+	list_for_each_entry(pkt, &pd->cdrw.pkt_free_list, list) {
+		if (pkt->sector == zone || pkt->list.next == &pd->cdrw.pkt_free_list) {
+			list_del_init(&pkt->list);
+			if (pkt->sector != zone)
+				pkt->cache_valid = 0;
+			break;
+		}
+	}
+	return pkt;
+}
+
+static void pkt_put_packet_data(struct pktcdvd_device *pd, struct packet_data *pkt)
+{
+	if (pkt->cache_valid) {
+		list_add(&pkt->list, &pd->cdrw.pkt_free_list);
+	} else {
+		list_add_tail(&pkt->list, &pd->cdrw.pkt_free_list);
+	}
+}
+
+/*
+ * recover a failed write, query for relocation if possible
+ *
+ * returns 1 if recovery is possible, or 0 if not
+ *
+ */
+static int pkt_start_recovery(struct packet_data *pkt)
+{
+	/*
+	 * FIXME. We need help from the file system to implement
+	 * recovery handling.
+	 */
+	return 0;
+#if 0
+	struct request *rq = pkt->rq;
+	struct pktcdvd_device *pd = rq->rq_disk->private_data;
+	struct block_device *pkt_bdev;
+	struct super_block *sb = NULL;
+	unsigned long old_block, new_block;
+	sector_t new_sector;
+
+	pkt_bdev = bdget(kdev_t_to_nr(pd->pkt_dev));
+	if (pkt_bdev) {
+		sb = get_super(pkt_bdev);
+		bdput(pkt_bdev);
+	}
+
+	if (!sb)
+		return 0;
+
+	if (!sb->s_op || !sb->s_op->relocate_blocks)
+		goto out;
+
+	old_block = pkt->sector / (CD_FRAMESIZE >> 9);
+	if (sb->s_op->relocate_blocks(sb, old_block, &new_block))
+		goto out;
+
+	new_sector = new_block * (CD_FRAMESIZE >> 9);
+	pkt->sector = new_sector;
+
+	pkt->bio->bi_sector = new_sector;
+	pkt->bio->bi_next = NULL;
+	pkt->bio->bi_flags = 1 << BIO_UPTODATE;
+	pkt->bio->bi_idx = 0;
+
+	BUG_ON(pkt->bio->bi_rw != (1 << BIO_RW));
+	BUG_ON(pkt->bio->bi_vcnt != pkt->frames);
+	BUG_ON(pkt->bio->bi_size != pkt->frames * CD_FRAMESIZE);
+	BUG_ON(pkt->bio->bi_end_io != pkt_end_io_packet_write);
+	BUG_ON(pkt->bio->bi_private != pkt);
+
+	drop_super(sb);
+	return 1;
+
+out:
+	drop_super(sb);
+	return 0;
+#endif
+}
+
+static inline void pkt_set_state(struct packet_data *pkt, enum packet_data_state state)
+{
+#if PACKET_DEBUG > 1
+	static const char *state_name[] = {
+		"IDLE", "WAITING", "READ_WAIT", "WRITE_WAIT", "RECOVERY", "FINISHED"
+	};
+	enum packet_data_state old_state = pkt->state;
+	VPRINTK("pkt %2d : s=%6llx %s -> %s\n", pkt->id, (unsigned long long)pkt->sector,
+		state_name[old_state], state_name[state]);
+#endif
+	pkt->state = state;
+}
+
+/*
+ * Scan the work queue to see if we can start a new packet.
+ * returns non-zero if any work was done.
+ */
+static int pkt_handle_queue(struct pktcdvd_device *pd)
+{
+	struct packet_data *pkt, *p;
+	struct bio *bio, *prev, *next;
+	sector_t zone = 0; /* Suppress gcc warning */
+
+	VPRINTK("handle_queue\n");
+
+	atomic_set(&pd->scan_queue, 0);
+
+	if (list_empty(&pd->cdrw.pkt_free_list)) {
+		VPRINTK("handle_queue: no pkt\n");
+		return 0;
+	}
+
+	/*
+	 * Try to find a zone we are not already working on.
+	 */
+	spin_lock(&pd->lock);
+	for (bio = pd->bio_queue; bio; bio = bio->bi_next) {
+		zone = ZONE(bio->bi_sector, pd);
+		list_for_each_entry(p, &pd->cdrw.pkt_active_list, list) {
+			if (p->sector == zone)
+				goto try_next_bio;
+		}
+		break;
+try_next_bio: ;
+	}
+	spin_unlock(&pd->lock);
+	if (!bio) {
+		VPRINTK("handle_queue: no bio\n");
+		return 0;
+	}
+
+	pkt = pkt_get_packet_data(pd, zone);
+	BUG_ON(!pkt);
+
+	pkt->sector = zone;
+	pkt->frames = pd->settings.size >> 2;
+	BUG_ON(pkt->frames > PACKET_MAX_SIZE);
+	pkt->write_size = 0;
+
+	/*
+	 * Scan work queue for bios in the same zone and link them
+	 * to this packet.
+	 */
+	spin_lock(&pd->lock);
+	prev = NULL;
+	VPRINTK("pkt_handle_queue: looking for zone %llx\n", (unsigned long long)zone);
+	bio = pd->bio_queue;
+	while (bio) {
+		VPRINTK("pkt_handle_queue: found zone=%llx\n",
+			(unsigned long long)ZONE(bio->bi_sector, pd));
+		if (ZONE(bio->bi_sector, pd) == zone) {
+			if (prev) {
+				prev->bi_next = bio->bi_next;
+			} else {
+				pd->bio_queue = bio->bi_next;
+			}
+			if (bio == pd->bio_queue_tail)
+				pd->bio_queue_tail = prev;
+			next = bio->bi_next;
+			spin_lock(&pkt->lock);
+			pkt_add_list_last(bio, &pkt->orig_bios,
+					  &pkt->orig_bios_tail);
+			pkt->write_size += bio->bi_size / CD_FRAMESIZE;
+			if (pkt->write_size >= pkt->frames) {
+				VPRINTK("pkt_handle_queue: pkt is full\n");
+				next = NULL; /* Stop searching if the packet is full */
+			}
+			spin_unlock(&pkt->lock);
+			bio = next;
+		} else {
+			prev = bio;
+			bio = bio->bi_next;
+		}
+	}
+	spin_unlock(&pd->lock);
+
+	pkt->sleep_time = max(PACKET_WAIT_TIME, 1);
+	pkt_set_state(pkt, PACKET_WAITING_STATE);
+	atomic_set(&pkt->run_sm, 1);
+
+	spin_lock(&pd->cdrw.active_list_lock);
+	list_add(&pkt->list, &pd->cdrw.pkt_active_list);
+	spin_unlock(&pd->cdrw.active_list_lock);
+
+	return 1;
+}
+
+/*
+ * Assemble a bio to write one packet and queue the bio for processing
+ * by the underlying block device.
+ */
+static void pkt_start_write(struct pktcdvd_device *pd, struct packet_data *pkt)
+{
+	struct bio *bio;
+	struct page *pages[PACKET_MAX_SIZE];
+	int offsets[PACKET_MAX_SIZE];
+	int f;
+	int frames_write;
+
+	for (f = 0; f < pkt->frames; f++) {
+		pages[f] = pkt->pages[(f * CD_FRAMESIZE) / PAGE_SIZE];
+		offsets[f] = (f * CD_FRAMESIZE) % PAGE_SIZE;
+	}
+
+	/*
+	 * Fill-in pages[] and offsets[] with data from orig_bios.
+	 */
+	frames_write = 0;
+	spin_lock(&pkt->lock);
+	for (bio = pkt->orig_bios; bio; bio = bio->bi_next) {
+		int segment = bio->bi_idx;
+		int src_offs = 0;
+		int first_frame = (bio->bi_sector - pkt->sector) / (CD_FRAMESIZE >> 9);
+		int num_frames = bio->bi_size / CD_FRAMESIZE;
+		BUG_ON(first_frame < 0);
+		BUG_ON(first_frame + num_frames > pkt->frames);
+		for (f = first_frame; f < first_frame + num_frames; f++) {
+			struct bio_vec *src_bvl = bio_iovec_idx(bio, segment);
+
+			while (src_offs >= src_bvl->bv_len) {
+				src_offs -= src_bvl->bv_len;
+				segment++;
+				BUG_ON(segment >= bio->bi_vcnt);
+				src_bvl = bio_iovec_idx(bio, segment);
+			}
+
+			if (src_bvl->bv_len - src_offs >= CD_FRAMESIZE) {
+				pages[f] = src_bvl->bv_page;
+				offsets[f] = src_bvl->bv_offset + src_offs;
+			} else {
+				pkt_copy_bio_data(bio, segment, src_offs,
+						  pages[f], offsets[f]);
+			}
+			src_offs += CD_FRAMESIZE;
+			frames_write++;
+		}
+	}
+	pkt_set_state(pkt, PACKET_WRITE_WAIT_STATE);
+	spin_unlock(&pkt->lock);
+
+	VPRINTK("pkt_start_write: Writing %d frames for zone %llx\n",
+		frames_write, (unsigned long long)pkt->sector);
+	BUG_ON(frames_write != pkt->write_size);
+
+	if (test_bit(PACKET_MERGE_SEGS, &pd->flags) || (pkt->write_size < pkt->frames)) {
+		pkt_make_local_copy(pkt, pages, offsets);
+		pkt->cache_valid = 1;
+	} else {
+		pkt->cache_valid = 0;
+	}
+
+	/* Start the write request */
+	pkt_bio_init(pkt->w_bio);
+	pkt->w_bio->bi_max_vecs = PACKET_MAX_SIZE;
+	pkt->w_bio->bi_sector = pkt->sector;
+	pkt->w_bio->bi_bdev = pd->bdev;
+	pkt->w_bio->bi_end_io = pkt_end_io_packet_write;
+	pkt->w_bio->bi_private = pkt;
+	for (f = 0; f < pkt->frames; f++) {
+		if ((f + 1 < pkt->frames) && (pages[f + 1] == pages[f]) &&
+		    (offsets[f + 1] = offsets[f] + CD_FRAMESIZE)) {
+			if (!bio_add_page(pkt->w_bio, pages[f], CD_FRAMESIZE * 2, offsets[f]))
+				BUG();
+			f++;
+		} else {
+			if (!bio_add_page(pkt->w_bio, pages[f], CD_FRAMESIZE, offsets[f]))
+				BUG();
+		}
+	}
+	VPRINTK("pktcdvd: vcnt=%d\n", pkt->w_bio->bi_vcnt);
+
+	atomic_set(&pkt->io_wait, 1);
+	pkt->w_bio->bi_rw = WRITE;
+	pkt_queue_bio(pd, pkt->w_bio, 0);
+}
+
+static void pkt_finish_packet(struct packet_data *pkt, int uptodate)
+{
+	struct bio *bio, *next;
+
+	if (!uptodate)
+		pkt->cache_valid = 0;
+
+	/* Finish all bios corresponding to this packet */
+	bio = pkt->orig_bios;
+	while (bio) {
+		next = bio->bi_next;
+		bio->bi_next = NULL;
+		bio_endio(bio, bio->bi_size, uptodate ? 0 : -EIO);
+		bio = next;
+	}
+	pkt->orig_bios = pkt->orig_bios_tail = NULL;
+}
+
+static void pkt_run_state_machine(struct pktcdvd_device *pd, struct packet_data *pkt)
+{
+	int uptodate;
+
+	VPRINTK("run_state_machine: pkt %d\n", pkt->id);
+
+	for (;;) {
+		switch (pkt->state) {
+		case PACKET_WAITING_STATE:
+			if ((pkt->write_size < pkt->frames) && (pkt->sleep_time > 0))
+				return;
+
+			pkt->sleep_time = 0;
+			pkt_gather_data(pd, pkt);
+			pkt_set_state(pkt, PACKET_READ_WAIT_STATE);
+			break;
+
+		case PACKET_READ_WAIT_STATE:
+			if (atomic_read(&pkt->io_wait) > 0)
+				return;
+
+			if (atomic_read(&pkt->io_errors) > 0) {
+				pkt_set_state(pkt, PACKET_RECOVERY_STATE);
+			} else {
+				pkt_start_write(pd, pkt);
+			}
+			break;
+
+		case PACKET_WRITE_WAIT_STATE:
+			if (atomic_read(&pkt->io_wait) > 0)
+				return;
+
+			if (test_bit(BIO_UPTODATE, &pkt->w_bio->bi_flags)) {
+				pkt_set_state(pkt, PACKET_FINISHED_STATE);
+			} else {
+				pkt_set_state(pkt, PACKET_RECOVERY_STATE);
+			}
+			break;
+
+		case PACKET_RECOVERY_STATE:
+			if (pkt_start_recovery(pkt)) {
+				pkt_start_write(pd, pkt);
+			} else {
+				VPRINTK("No recovery possible\n");
+				pkt_set_state(pkt, PACKET_FINISHED_STATE);
+			}
+			break;
+
+		case PACKET_FINISHED_STATE:
+			uptodate = test_bit(BIO_UPTODATE, &pkt->w_bio->bi_flags);
+			pkt_finish_packet(pkt, uptodate);
+			return;
+
+		default:
+			BUG();
+			break;
+		}
+	}
+}
+
+static void pkt_handle_packets(struct pktcdvd_device *pd)
+{
+	struct packet_data *pkt, *next;
+
+	VPRINTK("pkt_handle_packets\n");
+
+	/*
+	 * Run state machine for active packets
+	 */
+	list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+		if (atomic_read(&pkt->run_sm) > 0) {
+			atomic_set(&pkt->run_sm, 0);
+			pkt_run_state_machine(pd, pkt);
+		}
+	}
+
+	/*
+	 * Move no longer active packets to the free list
+	 */
+	spin_lock(&pd->cdrw.active_list_lock);
+	list_for_each_entry_safe(pkt, next, &pd->cdrw.pkt_active_list, list) {
+		if (pkt->state == PACKET_FINISHED_STATE) {
+			list_del(&pkt->list);
+			pkt_put_packet_data(pd, pkt);
+			pkt_set_state(pkt, PACKET_IDLE_STATE);
+			atomic_set(&pd->scan_queue, 1);
+		}
+	}
+	spin_unlock(&pd->cdrw.active_list_lock);
+}
+
+static void pkt_count_states(struct pktcdvd_device *pd, int *states)
+{
+	struct packet_data *pkt;
+	int i;
+
+	for (i = 0; i <= PACKET_NUM_STATES; i++)
+		states[i] = 0;
+
+	spin_lock(&pd->cdrw.active_list_lock);
+	list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+		states[pkt->state]++;
+	}
+	spin_unlock(&pd->cdrw.active_list_lock);
+}
+
+/*
+ * kcdrwd is woken up when writes have been queued for one of our
+ * registered devices
+ */
+static int kcdrwd(void *foobar)
+{
+	struct pktcdvd_device *pd = foobar;
+	struct packet_data *pkt;
+	long min_sleep_time, residue;
+
+	set_user_nice(current, -20);
+
+	for (;;) {
+		DECLARE_WAITQUEUE(wait, current);
+
+		/*
+		 * Wait until there is something to do
+		 */
+		add_wait_queue(&pd->wqueue, &wait);
+		for (;;) {
+			set_current_state(TASK_INTERRUPTIBLE);
+
+			/* Check if we need to run pkt_handle_queue */
+			if (atomic_read(&pd->scan_queue) > 0)
+				goto work_to_do;
+
+			/* Check if we need to run the state machine for some packet */
+			list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+				if (atomic_read(&pkt->run_sm) > 0)
+					goto work_to_do;
+			}
+
+			/* Check if we need to process the iosched queues */
+			if (atomic_read(&pd->iosched.attention) != 0)
+				goto work_to_do;
+
+			/* Otherwise, go to sleep */
+			if (PACKET_DEBUG > 1) {
+				int states[PACKET_NUM_STATES];
+				pkt_count_states(pd, states);
+				VPRINTK("kcdrwd: i:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n",
+					states[0], states[1], states[2], states[3],
+					states[4], states[5]);
+			}
+
+			min_sleep_time = MAX_SCHEDULE_TIMEOUT;
+			list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+				if (pkt->sleep_time && pkt->sleep_time < min_sleep_time)
+					min_sleep_time = pkt->sleep_time;
+			}
+
+			if (pd->bdev) {
+				request_queue_t *q;
+				q = bdev_get_queue(pd->bdev);
+				generic_unplug_device(q);
+			}
+
+			VPRINTK("kcdrwd: sleeping\n");
+			residue = schedule_timeout(min_sleep_time);
+			VPRINTK("kcdrwd: wake up\n");
+
+			/* make swsusp happy with our thread */
+			if (current->flags & PF_FREEZE)
+				refrigerator(PF_FREEZE);
+
+			list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+				if (!pkt->sleep_time)
+					continue;
+				pkt->sleep_time -= min_sleep_time - residue;
+				if (pkt->sleep_time <= 0) {
+					pkt->sleep_time = 0;
+					atomic_inc(&pkt->run_sm);
+				}
+			}
+
+			if (signal_pending(current)) {
+				flush_signals(current);
+			}
+			if (kthread_should_stop())
+				break;
+		}
+work_to_do:
+		set_current_state(TASK_RUNNING);
+		remove_wait_queue(&pd->wqueue, &wait);
+
+		if (kthread_should_stop())
+			break;
+
+		/*
+		 * if pkt_handle_queue returns true, we can queue
+		 * another request.
+		 */
+		while (pkt_handle_queue(pd))
+			;
+
+		/*
+		 * Handle packet state machine
+		 */
+		pkt_handle_packets(pd);
+
+		/*
+		 * Handle iosched queues
+		 */
+		pkt_iosched_process_queue(pd);
+	}
+
+	return 0;
+}
+
+static void pkt_print_settings(struct pktcdvd_device *pd)
+{
+	printk("pktcdvd: %s packets, ", pd->settings.fp ? "Fixed" : "Variable");
+	printk("%u blocks, ", pd->settings.size >> 2);
+	printk("Mode-%c disc\n", pd->settings.block_mode == 8 ? '1' : '2');
+}
+
+static int pkt_mode_sense(struct pktcdvd_device *pd, struct packet_command *cgc,
+			  int page_code, int page_control)
+{
+	memset(cgc->cmd, 0, sizeof(cgc->cmd));
+
+	cgc->cmd[0] = GPCMD_MODE_SENSE_10;
+	cgc->cmd[2] = page_code | (page_control << 6);
+	cgc->cmd[7] = cgc->buflen >> 8;
+	cgc->cmd[8] = cgc->buflen & 0xff;
+	cgc->data_direction = CGC_DATA_READ;
+	return pkt_generic_packet(pd, cgc);
+}
+
+static int pkt_mode_select(struct pktcdvd_device *pd, struct packet_command *cgc)
+{
+	memset(cgc->cmd, 0, sizeof(cgc->cmd));
+	memset(cgc->buffer, 0, 2);
+	cgc->cmd[0] = GPCMD_MODE_SELECT_10;
+	cgc->cmd[1] = 0x10;		/* PF */
+	cgc->cmd[7] = cgc->buflen >> 8;
+	cgc->cmd[8] = cgc->buflen & 0xff;
+	cgc->data_direction = CGC_DATA_WRITE;
+	return pkt_generic_packet(pd, cgc);
+}
+
+static int pkt_get_disc_info(struct pktcdvd_device *pd, disc_information *di)
+{
+	struct packet_command cgc;
+	int ret;
+
+	/* set up command and get the disc info */
+	init_cdrom_command(&cgc, di, sizeof(*di), CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_DISC_INFO;
+	cgc.cmd[8] = cgc.buflen = 2;
+	cgc.quiet = 1;
+
+	if ((ret = pkt_generic_packet(pd, &cgc)))
+		return ret;
+
+	/* not all drives have the same disc_info length, so requeue
+	 * packet with the length the drive tells us it can supply
+	 */
+	cgc.buflen = be16_to_cpu(di->disc_information_length) +
+		     sizeof(di->disc_information_length);
+
+	if (cgc.buflen > sizeof(disc_information))
+		cgc.buflen = sizeof(disc_information);
+
+	cgc.cmd[8] = cgc.buflen;
+	return pkt_generic_packet(pd, &cgc);
+}
+
+static int pkt_get_track_info(struct pktcdvd_device *pd, __u16 track, __u8 type, track_information *ti)
+{
+	struct packet_command cgc;
+	int ret;
+
+	init_cdrom_command(&cgc, ti, 8, CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_READ_TRACK_RZONE_INFO;
+	cgc.cmd[1] = type & 3;
+	cgc.cmd[4] = (track & 0xff00) >> 8;
+	cgc.cmd[5] = track & 0xff;
+	cgc.cmd[8] = 8;
+	cgc.quiet = 1;
+
+	if ((ret = pkt_generic_packet(pd, &cgc)))
+		return ret;
+
+	cgc.buflen = be16_to_cpu(ti->track_information_length) +
+		     sizeof(ti->track_information_length);
+
+	if (cgc.buflen > sizeof(track_information))
+		cgc.buflen = sizeof(track_information);
+
+	cgc.cmd[8] = cgc.buflen;
+	return pkt_generic_packet(pd, &cgc);
+}
+
+static int pkt_get_last_written(struct pktcdvd_device *pd, long *last_written)
+{
+	disc_information di;
+	track_information ti;
+	__u32 last_track;
+	int ret = -1;
+
+	if ((ret = pkt_get_disc_info(pd, &di)))
+		return ret;
+
+	last_track = (di.last_track_msb << 8) | di.last_track_lsb;
+	if ((ret = pkt_get_track_info(pd, last_track, 1, &ti)))
+		return ret;
+
+	/* if this track is blank, try the previous. */
+	if (ti.blank) {
+		last_track--;
+		if ((ret = pkt_get_track_info(pd, last_track, 1, &ti)))
+			return ret;
+	}
+
+	/* if last recorded field is valid, return it. */
+	if (ti.lra_v) {
+		*last_written = be32_to_cpu(ti.last_rec_address);
+	} else {
+		/* make it up instead */
+		*last_written = be32_to_cpu(ti.track_start) +
+				be32_to_cpu(ti.track_size);
+		if (ti.free_blocks)
+			*last_written -= (be32_to_cpu(ti.free_blocks) + 7);
+	}
+	return 0;
+}
+
+/*
+ * write mode select package based on pd->settings
+ */
+static int pkt_set_write_settings(struct pktcdvd_device *pd)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	write_param_page *wp;
+	char buffer[128];
+	int ret, size;
+
+	/* doesn't apply to DVD+RW */
+	if (pd->mmc3_profile == 0x1a)
+		return 0;
+
+	memset(buffer, 0, sizeof(buffer));
+	init_cdrom_command(&cgc, buffer, sizeof(*wp), CGC_DATA_READ);
+	cgc.sense = &sense;
+	if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WRITE_PARMS_PAGE, 0))) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+
+	size = 2 + ((buffer[0] << 8) | (buffer[1] & 0xff));
+	pd->mode_offset = (buffer[6] << 8) | (buffer[7] & 0xff);
+	if (size > sizeof(buffer))
+		size = sizeof(buffer);
+
+	/*
+	 * now get it all
+	 */
+	init_cdrom_command(&cgc, buffer, size, CGC_DATA_READ);
+	cgc.sense = &sense;
+	if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WRITE_PARMS_PAGE, 0))) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+
+	/*
+	 * write page is offset header + block descriptor length
+	 */
+	wp = (write_param_page *) &buffer[sizeof(struct mode_page_header) + pd->mode_offset];
+
+	wp->fp = pd->settings.fp;
+	wp->track_mode = pd->settings.track_mode;
+	wp->write_type = pd->settings.write_type;
+	wp->data_block_type = pd->settings.block_mode;
+
+	wp->multi_session = 0;
+
+#ifdef PACKET_USE_LS
+	wp->link_size = 7;
+	wp->ls_v = 1;
+#endif
+
+	if (wp->data_block_type == PACKET_BLOCK_MODE1) {
+		wp->session_format = 0;
+		wp->subhdr2 = 0x20;
+	} else if (wp->data_block_type == PACKET_BLOCK_MODE2) {
+		wp->session_format = 0x20;
+		wp->subhdr2 = 8;
+#if 0
+		wp->mcn[0] = 0x80;
+		memcpy(&wp->mcn[1], PACKET_MCN, sizeof(wp->mcn) - 1);
+#endif
+	} else {
+		/*
+		 * paranoia
+		 */
+		printk("pktcdvd: write mode wrong %d\n", wp->data_block_type);
+		return 1;
+	}
+	wp->packet_size = cpu_to_be32(pd->settings.size >> 2);
+
+	cgc.buflen = cgc.cmd[8] = size;
+	if ((ret = pkt_mode_select(pd, &cgc))) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+
+	pkt_print_settings(pd);
+	return 0;
+}
+
+/*
+ * 0 -- we can write to this track, 1 -- we can't
+ */
+static int pkt_good_track(track_information *ti)
+{
+	/*
+	 * only good for CD-RW at the moment, not DVD-RW
+	 */
+
+	/*
+	 * FIXME: only for FP
+	 */
+	if (ti->fp == 0)
+		return 0;
+
+	/*
+	 * "good" settings as per Mt Fuji.
+	 */
+	if (ti->rt == 0 && ti->blank == 0 && ti->packet == 1)
+		return 0;
+
+	if (ti->rt == 0 && ti->blank == 1 && ti->packet == 1)
+		return 0;
+
+	if (ti->rt == 1 && ti->blank == 0 && ti->packet == 1)
+		return 0;
+
+	printk("pktcdvd: bad state %d-%d-%d\n", ti->rt, ti->blank, ti->packet);
+	return 1;
+}
+
+/*
+ * 0 -- we can write to this disc, 1 -- we can't
+ */
+static int pkt_good_disc(struct pktcdvd_device *pd, disc_information *di)
+{
+	switch (pd->mmc3_profile) {
+		case 0x0a: /* CD-RW */
+		case 0xffff: /* MMC3 not supported */
+			break;
+		case 0x1a: /* DVD+RW */
+		case 0x13: /* DVD-RW */
+			return 0;
+		default:
+			printk("pktcdvd: Wrong disc profile (%x)\n", pd->mmc3_profile);
+			return 1;
+	}
+
+	/*
+	 * for disc type 0xff we should probably reserve a new track.
+	 * but i'm not sure, should we leave this to user apps? probably.
+	 */
+	if (di->disc_type == 0xff) {
+		printk("pktcdvd: Unknown disc. No track?\n");
+		return 1;
+	}
+
+	if (di->disc_type != 0x20 && di->disc_type != 0) {
+		printk("pktcdvd: Wrong disc type (%x)\n", di->disc_type);
+		return 1;
+	}
+
+	if (di->erasable == 0) {
+		printk("pktcdvd: Disc not erasable\n");
+		return 1;
+	}
+
+	if (di->border_status == PACKET_SESSION_RESERVED) {
+		printk("pktcdvd: Can't write to last track (reserved)\n");
+		return 1;
+	}
+
+	return 0;
+}
+
+static int pkt_probe_settings(struct pktcdvd_device *pd)
+{
+	struct packet_command cgc;
+	unsigned char buf[12];
+	disc_information di;
+	track_information ti;
+	int ret, track;
+
+	init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ);
+	cgc.cmd[0] = GPCMD_GET_CONFIGURATION;
+	cgc.cmd[8] = 8;
+	ret = pkt_generic_packet(pd, &cgc);
+	pd->mmc3_profile = ret ? 0xffff : buf[6] << 8 | buf[7];
+
+	memset(&di, 0, sizeof(disc_information));
+	memset(&ti, 0, sizeof(track_information));
+
+	if ((ret = pkt_get_disc_info(pd, &di))) {
+		printk("failed get_disc\n");
+		return ret;
+	}
+
+	if (pkt_good_disc(pd, &di))
+		return -ENXIO;
+
+	printk("pktcdvd: inserted media is CD-R%s\n", di.erasable ? "W" : "");
+	pd->type = di.erasable ? PACKET_CDRW : PACKET_CDR;
+
+	track = 1; /* (di.last_track_msb << 8) | di.last_track_lsb; */
+	if ((ret = pkt_get_track_info(pd, track, 1, &ti))) {
+		printk("pktcdvd: failed get_track\n");
+		return ret;
+	}
+
+	if (pkt_good_track(&ti)) {
+		printk("pktcdvd: can't write to this track\n");
+		return -ENXIO;
+	}
+
+	/*
+	 * we keep packet size in 512 byte units, makes it easier to
+	 * deal with request calculations.
+	 */
+	pd->settings.size = be32_to_cpu(ti.fixed_packet_size) << 2;
+	if (pd->settings.size == 0) {
+		printk("pktcdvd: detected zero packet size!\n");
+		pd->settings.size = 128;
+	}
+	pd->settings.fp = ti.fp;
+	pd->offset = (be32_to_cpu(ti.track_start) << 2) & (pd->settings.size - 1);
+
+	if (ti.nwa_v) {
+		pd->nwa = be32_to_cpu(ti.next_writable);
+		set_bit(PACKET_NWA_VALID, &pd->flags);
+	}
+
+	/*
+	 * in theory we could use lra on -RW media as well and just zero
+	 * blocks that haven't been written yet, but in practice that
+	 * is just a no-go. we'll use that for -R, naturally.
+	 */
+	if (ti.lra_v) {
+		pd->lra = be32_to_cpu(ti.last_rec_address);
+		set_bit(PACKET_LRA_VALID, &pd->flags);
+	} else {
+		pd->lra = 0xffffffff;
+		set_bit(PACKET_LRA_VALID, &pd->flags);
+	}
+
+	/*
+	 * fine for now
+	 */
+	pd->settings.link_loss = 7;
+	pd->settings.write_type = 0;	/* packet */
+	pd->settings.track_mode = ti.track_mode;
+
+	/*
+	 * mode1 or mode2 disc
+	 */
+	switch (ti.data_mode) {
+		case PACKET_MODE1:
+			pd->settings.block_mode = PACKET_BLOCK_MODE1;
+			break;
+		case PACKET_MODE2:
+			pd->settings.block_mode = PACKET_BLOCK_MODE2;
+			break;
+		default:
+			printk("pktcdvd: unknown data mode\n");
+			return 1;
+	}
+	return 0;
+}
+
+/*
+ * enable/disable write caching on drive
+ */
+static int pkt_write_caching(struct pktcdvd_device *pd, int set)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	unsigned char buf[64];
+	int ret;
+
+	memset(buf, 0, sizeof(buf));
+	init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_READ);
+	cgc.sense = &sense;
+	cgc.buflen = pd->mode_offset + 12;
+
+	/*
+	 * caching mode page might not be there, so quiet this command
+	 */
+	cgc.quiet = 1;
+
+	if ((ret = pkt_mode_sense(pd, &cgc, GPMODE_WCACHING_PAGE, 0)))
+		return ret;
+
+	buf[pd->mode_offset + 10] |= (!!set << 2);
+
+	cgc.buflen = cgc.cmd[8] = 2 + ((buf[0] << 8) | (buf[1] & 0xff));
+	ret = pkt_mode_select(pd, &cgc);
+	if (ret) {
+		printk("pktcdvd: write caching control failed\n");
+		pkt_dump_sense(&cgc);
+	} else if (!ret && set)
+		printk("pktcdvd: enabled write caching on %s\n", pd->name);
+	return ret;
+}
+
+static int pkt_lock_door(struct pktcdvd_device *pd, int lockflag)
+{
+	struct packet_command cgc;
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_PREVENT_ALLOW_MEDIUM_REMOVAL;
+	cgc.cmd[4] = lockflag ? 1 : 0;
+	return pkt_generic_packet(pd, &cgc);
+}
+
+/*
+ * Returns drive maximum write speed
+ */
+static int pkt_get_max_speed(struct pktcdvd_device *pd, unsigned *write_speed)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	unsigned char buf[256+18];
+	unsigned char *cap_buf;
+	int ret, offset;
+
+	memset(buf, 0, sizeof(buf));
+	cap_buf = &buf[sizeof(struct mode_page_header) + pd->mode_offset];
+	init_cdrom_command(&cgc, buf, sizeof(buf), CGC_DATA_UNKNOWN);
+	cgc.sense = &sense;
+
+	ret = pkt_mode_sense(pd, &cgc, GPMODE_CAPABILITIES_PAGE, 0);
+	if (ret) {
+		cgc.buflen = pd->mode_offset + cap_buf[1] + 2 +
+			     sizeof(struct mode_page_header);
+		ret = pkt_mode_sense(pd, &cgc, GPMODE_CAPABILITIES_PAGE, 0);
+		if (ret) {
+			pkt_dump_sense(&cgc);
+			return ret;
+		}
+	}
+
+	offset = 20;			    /* Obsoleted field, used by older drives */
+	if (cap_buf[1] >= 28)
+		offset = 28;		    /* Current write speed selected */
+	if (cap_buf[1] >= 30) {
+		/* If the drive reports at least one "Logical Unit Write
+		 * Speed Performance Descriptor Block", use the information
+		 * in the first block. (contains the highest speed)
+		 */
+		int num_spdb = (cap_buf[30] << 8) + cap_buf[31];
+		if (num_spdb > 0)
+			offset = 34;
+	}
+
+	*write_speed = ((cap_buf[offset] << 8) | cap_buf[offset + 1]) / 0xb0;
+	return 0;
+}
+
+/* These tables from cdrecord - I don't have orange book */
+/* standard speed CD-RW (1-4x) */
+static char clv_to_speed[16] = {
+	/* 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 */
+	   0, 2, 4, 6, 8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+/* high speed CD-RW (-10x) */
+static char hs_clv_to_speed[16] = {
+	/* 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 */
+	   0, 2, 4, 6, 10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
+};
+/* ultra high speed CD-RW */
+static char us_clv_to_speed[16] = {
+	/* 0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 */
+	   0, 2, 4, 8, 0, 0,16, 0,24,32,40,48, 0, 0, 0, 0
+};
+
+/*
+ * reads the maximum media speed from ATIP
+ */
+static int pkt_media_speed(struct pktcdvd_device *pd, unsigned *speed)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	unsigned char buf[64];
+	unsigned int size, st, sp;
+	int ret;
+
+	init_cdrom_command(&cgc, buf, 2, CGC_DATA_READ);
+	cgc.sense = &sense;
+	cgc.cmd[0] = GPCMD_READ_TOC_PMA_ATIP;
+	cgc.cmd[1] = 2;
+	cgc.cmd[2] = 4; /* READ ATIP */
+	cgc.cmd[8] = 2;
+	ret = pkt_generic_packet(pd, &cgc);
+	if (ret) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+	size = ((unsigned int) buf[0]<<8) + buf[1] + 2;
+	if (size > sizeof(buf))
+		size = sizeof(buf);
+
+	init_cdrom_command(&cgc, buf, size, CGC_DATA_READ);
+	cgc.sense = &sense;
+	cgc.cmd[0] = GPCMD_READ_TOC_PMA_ATIP;
+	cgc.cmd[1] = 2;
+	cgc.cmd[2] = 4;
+	cgc.cmd[8] = size;
+	ret = pkt_generic_packet(pd, &cgc);
+	if (ret) {
+		pkt_dump_sense(&cgc);
+		return ret;
+	}
+
+	if (!buf[6] & 0x40) {
+		printk("pktcdvd: Disc type is not CD-RW\n");
+		return 1;
+	}
+	if (!buf[6] & 0x4) {
+		printk("pktcdvd: A1 values on media are not valid, maybe not CDRW?\n");
+		return 1;
+	}
+
+	st = (buf[6] >> 3) & 0x7; /* disc sub-type */
+
+	sp = buf[16] & 0xf; /* max speed from ATIP A1 field */
+
+	/* Info from cdrecord */
+	switch (st) {
+		case 0: /* standard speed */
+			*speed = clv_to_speed[sp];
+			break;
+		case 1: /* high speed */
+			*speed = hs_clv_to_speed[sp];
+			break;
+		case 2: /* ultra high speed */
+			*speed = us_clv_to_speed[sp];
+			break;
+		default:
+			printk("pktcdvd: Unknown disc sub-type %d\n",st);
+			return 1;
+	}
+	if (*speed) {
+		printk("pktcdvd: Max. media speed: %d\n",*speed);
+		return 0;
+	} else {
+		printk("pktcdvd: Unknown speed %d for sub-type %d\n",sp,st);
+		return 1;
+	}
+}
+
+static int pkt_perform_opc(struct pktcdvd_device *pd)
+{
+	struct packet_command cgc;
+	struct request_sense sense;
+	int ret;
+
+	VPRINTK("pktcdvd: Performing OPC\n");
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.sense = &sense;
+	cgc.timeout = 60*HZ;
+	cgc.cmd[0] = GPCMD_SEND_OPC;
+	cgc.cmd[1] = 1;
+	if ((ret = pkt_generic_packet(pd, &cgc)))
+		pkt_dump_sense(&cgc);
+	return ret;
+}
+
+static int pkt_open_write(struct pktcdvd_device *pd)
+{
+	int ret;
+	unsigned int write_speed, media_write_speed, read_speed;
+
+	if ((ret = pkt_probe_settings(pd))) {
+		DPRINTK("pktcdvd: %s failed probe\n", pd->name);
+		return -EIO;
+	}
+
+	if ((ret = pkt_set_write_settings(pd))) {
+		DPRINTK("pktcdvd: %s failed saving write settings\n", pd->name);
+		return -EIO;
+	}
+
+	pkt_write_caching(pd, USE_WCACHING);
+
+	if ((ret = pkt_get_max_speed(pd, &write_speed)))
+		write_speed = 16;
+	switch (pd->mmc3_profile) {
+		case 0x13: /* DVD-RW */
+		case 0x1a: /* DVD+RW */
+			break;
+		default:
+			if ((ret = pkt_media_speed(pd, &media_write_speed)))
+				media_write_speed = 16;
+			write_speed = min(write_speed, media_write_speed);
+			break;
+	}
+	read_speed = write_speed;
+
+	if ((ret = pkt_set_speed(pd, write_speed, read_speed))) {
+		DPRINTK("pktcdvd: %s couldn't set write speed\n", pd->name);
+		return -EIO;
+	}
+	DPRINTK("pktcdvd: write speed %u\n", write_speed);
+	pd->write_speed = write_speed;
+	pd->read_speed = read_speed;
+
+	if ((ret = pkt_perform_opc(pd))) {
+		DPRINTK("pktcdvd: %s Optimum Power Calibration failed\n", pd->name);
+	}
+
+	return 0;
+}
+
+/*
+ * called at open time.
+ */
+static int pkt_open_dev(struct pktcdvd_device *pd, int write)
+{
+	int ret;
+	long lba;
+	request_queue_t *q;
+	int i;
+	char b[BDEVNAME_SIZE];
+
+	if (!pd->dev)
+		return -ENXIO;
+
+	pd->bdev = bdget(pd->dev);
+	if (!pd->bdev) {
+		printk("pktcdvd: can't find cdrom block device\n");
+		return -ENXIO;
+	}
+
+	if ((ret = blkdev_get(pd->bdev, FMODE_READ, 0))) {
+		pd->bdev = NULL;
+		return ret;
+	}
+
+	if ((ret = pkt_get_last_written(pd, &lba))) {
+		printk("pktcdvd: pkt_get_last_written failed\n");
+		return ret;
+	}
+
+	set_capacity(pd->disk, lba << 2);
+
+	/*
+	 * The underlying block device needs to have its merge logic
+	 * modified, so that it doesn't try to merge write requests.
+	 * First make sure the queue isn't already in use by another
+	 * pktcdvd_device.
+	 */
+	q = bdev_get_queue(pd->bdev);
+	for (i = 0; i < MAX_WRITERS; i++) {
+		if (pd == &pkt_devs[i])
+			continue;
+		if (pkt_devs[i].bdev && bdev_get_queue(pkt_devs[i].bdev) == q) {
+			printk("pktcdvd: %s request queue busy\n", bdevname(pd->bdev, b));
+			return -EBUSY;
+		}
+	}
+	spin_lock_irq(q->queue_lock);
+	pd->cdrw.elv_merge_fn = q->elevator.elevator_merge_fn;
+	pd->cdrw.elv_completed_req_fn = q->elevator.elevator_completed_req_fn;
+	pd->cdrw.merge_requests_fn = q->merge_requests_fn;
+	q->elevator.elevator_merge_fn = pkt_lowlevel_elv_merge_fn;
+	q->elevator.elevator_completed_req_fn = pkt_lowlevel_elv_completed_req_fn;
+	q->merge_requests_fn = pkt_lowlevel_merge_requests_fn;
+	spin_unlock_irq(q->queue_lock);
+
+	if (write) {
+		if ((ret = pkt_open_write(pd)))
+			goto restore_queue;
+		set_bit(PACKET_WRITABLE, &pd->flags);
+	} else {
+		pkt_set_speed(pd, 0xffff, 0xffff);
+		clear_bit(PACKET_WRITABLE, &pd->flags);
+	}
+
+	if ((ret = pkt_set_segment_merging(pd, q)))
+		goto restore_queue;
+
+	if (write)
+		printk("pktcdvd: %lukB available on disc\n", lba << 1);
+
+	return 0;
+
+restore_queue:
+	spin_lock_irq(q->queue_lock);
+	q->elevator.elevator_merge_fn = pd->cdrw.elv_merge_fn;
+	q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
+	q->merge_requests_fn = pd->cdrw.merge_requests_fn;
+	spin_unlock_irq(q->queue_lock);
+	return ret;
+}
+
+/*
+ * called when the device is closed. makes sure that the device flushes
+ * the internal cache before we close.
+ */
+static void pkt_release_dev(struct pktcdvd_device *pd, int flush)
+{
+	if (flush && pkt_flush_cache(pd))
+		DPRINTK("pktcdvd: %s not flushing cache\n", pd->name);
+
+	if (pd->bdev) {
+		request_queue_t *q = bdev_get_queue(pd->bdev);
+		pkt_set_speed(pd, 0xffff, 0xffff);
+		spin_lock_irq(q->queue_lock);
+		q->elevator.elevator_merge_fn = pd->cdrw.elv_merge_fn;
+		q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
+		q->merge_requests_fn = pd->cdrw.merge_requests_fn;
+		spin_unlock_irq(q->queue_lock);
+		blkdev_put(pd->bdev);
+		pd->bdev = NULL;
+	}
+}
+
+static int pkt_open(struct inode *inode, struct file *file)
+{
+	struct pktcdvd_device *pd = NULL;
+	struct block_device *pkt_bdev;
+	int ret;
+	int special_open, exclusive;
+
+	VPRINTK("pktcdvd: entering open\n");
+
+	if (iminor(inode) >= MAX_WRITERS) {
+		printk("pktcdvd: max %d writers supported\n", MAX_WRITERS);
+		ret = -ENODEV;
+		goto out;
+	}
+
+	special_open = 0;
+	if (((file->f_flags & O_ACCMODE) == O_RDONLY) && (file->f_flags & O_CREAT))
+		special_open = 1;
+
+	exclusive = 0;
+	if ((file->f_mode & FMODE_WRITE) || special_open)
+		exclusive = 1;
+
+	/*
+	 * either device is not configured, or pktsetup is old and doesn't
+	 * use O_CREAT to create device
+	 */
+	pd = &pkt_devs[iminor(inode)];
+	if (!pd->dev && !special_open) {
+		VPRINTK("pktcdvd: not configured and O_CREAT not set\n");
+		ret = -ENXIO;
+		goto out;
+	}
+
+	down(&pd->ctl_mutex);
+	pd->refcnt++;
+	if (pd->refcnt > 1) {
+		if (exclusive) {
+			VPRINTK("pktcdvd: busy open\n");
+			ret = -EBUSY;
+			goto out_dec;
+		}
+
+		/*
+		 * Not first open, everything is already set up
+		 */
+		goto done;
+	}
+
+	if (!special_open) {
+		if (pkt_open_dev(pd, file->f_mode & FMODE_WRITE)) {
+			ret = -EIO;
+			goto out_dec;
+		}
+	}
+
+	/*
+	 * needed here as well, since ext2 (among others) may change
+	 * the blocksize at mount time
+	 */
+	pkt_bdev = bdget(inode->i_rdev);
+	if (pkt_bdev) {
+		set_blocksize(pkt_bdev, CD_FRAMESIZE);
+		bdput(pkt_bdev);
+	}
+
+done:
+	up(&pd->ctl_mutex);
+	return 0;
+
+out_dec:
+	pd->refcnt--;
+	if (pd->refcnt == 0) {
+		if (pd->bdev) {
+			blkdev_put(pd->bdev);
+			pd->bdev = NULL;
+		}
+	}
+	up(&pd->ctl_mutex);
+out:
+	VPRINTK("pktcdvd: failed open (%d)\n", ret);
+	return ret;
+}
+
+static int pkt_close(struct inode *inode, struct file *file)
+{
+	struct pktcdvd_device *pd = &pkt_devs[iminor(inode)];
+	int ret = 0;
+
+	down(&pd->ctl_mutex);
+	pd->refcnt--;
+	BUG_ON(pd->refcnt < 0);
+	if (pd->refcnt > 0)
+		goto out;
+	if (pd->dev) {
+		int flush = test_bit(PACKET_WRITABLE, &pd->flags);
+		pkt_release_dev(pd, flush);
+	}
+out:
+	up(&pd->ctl_mutex);
+	return ret;
+}
+
+static int pkt_make_request(request_queue_t *q, struct bio *bio)
+{
+	struct pktcdvd_device *pd;
+	char b[BDEVNAME_SIZE];
+	sector_t zone;
+	struct packet_data *pkt;
+	int was_empty, blocked_bio;
+
+	pd = q->queuedata;
+	if (!pd) {
+		printk("pktcdvd: %s incorrect request queue\n", bdevname(bio->bi_bdev, b));
+		goto end_io;
+	}
+
+	if (!pd->dev) {
+		printk("pktcdvd: request received for non-active pd\n");
+		goto end_io;
+	}
+
+	/*
+	 * quick remap a READ
+	 */
+	if (bio_data_dir(bio) == READ) {
+		bio->bi_bdev = pd->bdev;
+		pd->stats.secs_r += bio->bi_size >> 9;
+		pkt_queue_bio(pd, bio, 1);
+		return 0;
+	}
+
+	if (!test_bit(PACKET_WRITABLE, &pd->flags)) {
+		printk("pktcdvd: WRITE for ro device %s (%llu)\n",
+			pd->name, (unsigned long long)bio->bi_sector);
+		goto end_io;
+	}
+
+	if (!bio->bi_size || (bio->bi_size % CD_FRAMESIZE)) {
+		printk("pktcdvd: wrong bio size\n");
+		goto end_io;
+	}
+
+	blk_queue_bounce(q, &bio);
+
+	zone = ZONE(bio->bi_sector, pd);
+	VPRINTK("pkt_make_request: start = %6llx stop = %6llx\n",
+		(unsigned long long)bio->bi_sector,
+		(unsigned long long)(bio->bi_sector + bio_sectors(bio)));
+
+	/* Check if we have to split the bio */
+	{
+		struct bio_pair *bp;
+		sector_t last_zone;
+		int first_sectors;
+
+		last_zone = ZONE(bio->bi_sector + bio_sectors(bio) - 1, pd);
+		if (last_zone != zone) {
+			BUG_ON(last_zone != zone + pd->settings.size);
+			first_sectors = last_zone - bio->bi_sector;
+			bp = bio_split(bio, bio_split_pool, first_sectors);
+			BUG_ON(!bp);
+			pkt_make_request(q, &bp->bio1);
+			pkt_make_request(q, &bp->bio2);
+			bio_pair_release(bp);
+			return 0;
+		}
+	}
+
+	/*
+	 * If we find a matching packet in state WAITING or READ_WAIT, we can
+	 * just append this bio to that packet.
+	 */
+	spin_lock(&pd->cdrw.active_list_lock);
+	blocked_bio = 0;
+	list_for_each_entry(pkt, &pd->cdrw.pkt_active_list, list) {
+		if (pkt->sector == zone) {
+			spin_lock(&pkt->lock);
+			if ((pkt->state == PACKET_WAITING_STATE) ||
+			    (pkt->state == PACKET_READ_WAIT_STATE)) {
+				pkt_add_list_last(bio, &pkt->orig_bios,
+						  &pkt->orig_bios_tail);
+				pkt->write_size += bio->bi_size / CD_FRAMESIZE;
+				if ((pkt->write_size >= pkt->frames) &&
+				    (pkt->state == PACKET_WAITING_STATE)) {
+					atomic_inc(&pkt->run_sm);
+					wake_up(&pd->wqueue);
+				}
+				spin_unlock(&pkt->lock);
+				spin_unlock(&pd->cdrw.active_list_lock);
+				return 0;
+			} else {
+				blocked_bio = 1;
+			}
+			spin_unlock(&pkt->lock);
+		}
+	}
+	spin_unlock(&pd->cdrw.active_list_lock);
+
+	/*
+	 * No matching packet found. Store the bio in the work queue.
+	 */
+	spin_lock(&pd->lock);
+	if (pd->bio_queue == NULL) {
+		was_empty = 1;
+		bio->bi_next = NULL;
+		pd->bio_queue = bio;
+		pd->bio_queue_tail = bio;
+	} else {
+		struct bio *bio2, *insert_after;
+		int distance, z, cnt;
+
+		was_empty = 0;
+		z = ZONE(pd->bio_queue_tail->bi_sector, pd);
+		distance = (zone >= z ? zone - z : INT_MAX);
+		insert_after = pd->bio_queue_tail;
+		if (distance > pd->settings.size) {
+			for (bio2 = pd->bio_queue, cnt = 0; bio2 && (cnt < 10000);
+			     bio2 = bio2->bi_next, cnt++) {
+				int d2;
+				z = ZONE(bio2->bi_sector, pd);
+				d2 = (zone >= z ? zone - z : INT_MAX);
+				if (d2 < distance) {
+					distance = d2;
+					insert_after = bio2;
+					if (distance == 0)
+						break;
+				}
+			}
+		}
+		bio->bi_next = insert_after->bi_next;
+		insert_after->bi_next = bio;
+		if (insert_after == pd->bio_queue_tail)
+			pd->bio_queue_tail = bio;
+	}
+	spin_unlock(&pd->lock);
+
+	/*
+	 * Wake up the worker thread.
+	 */
+	atomic_set(&pd->scan_queue, 1);
+	if (was_empty) {
+		/* This wake_up is required for correct operation */
+		wake_up(&pd->wqueue);
+	} else if (!list_empty(&pd->cdrw.pkt_free_list) && !blocked_bio) {
+		/*
+		 * This wake up is not required for correct operation,
+		 * but improves performance in some cases.
+		 */
+		wake_up(&pd->wqueue);
+	}
+	return 0;
+end_io:
+	bio_io_error(bio, bio->bi_size);
+	return 0;
+}
+
+
+
+static int pkt_merge_bvec(request_queue_t *q, struct bio *bio, struct bio_vec *bvec)
+{
+	struct pktcdvd_device *pd = &pkt_devs[MINOR(bio->bi_bdev->bd_dev)];
+	sector_t zone = ZONE(bio->bi_sector, pd);
+	int used = ((bio->bi_sector - zone) << 9) + bio->bi_size;
+	int remaining = (pd->settings.size << 9) - used;
+	int remaining2;
+
+	/*
+	 * A bio <= PAGE_SIZE must be allowed. If it crosses a packet
+	 * boundary, pkt_make_request() will split the bio.
+	 */
+	remaining2 = PAGE_SIZE - bio->bi_size;
+	remaining = max(remaining, remaining2);
+
+	BUG_ON(remaining < 0);
+	return remaining;
+}
+
+static void pkt_init_queue(struct pktcdvd_device *pd)
+{
+	request_queue_t *q = pd->disk->queue;
+
+	blk_queue_make_request(q, pkt_make_request);
+	blk_queue_hardsect_size(q, CD_FRAMESIZE);
+	blk_queue_max_sectors(q, PACKET_MAX_SECTORS);
+	blk_queue_merge_bvec(q, pkt_merge_bvec);
+	q->queuedata = pd;
+}
+
+static int pkt_proc_device(struct pktcdvd_device *pd, char *buf)
+{
+	char *b = buf, *msg;
+	char bdev_buf[BDEVNAME_SIZE];
+	int states[PACKET_NUM_STATES];
+
+	b += sprintf(b, "\nWriter %s mapped to %s:\n", pd->name,
+		     __bdevname(pd->dev, bdev_buf));
+
+	b += sprintf(b, "\nSettings:\n");
+	b += sprintf(b, "\tpacket size:\t\t%dkB\n", pd->settings.size / 2);
+
+	if (pd->settings.write_type == 0)
+		msg = "Packet";
+	else
+		msg = "Unknown";
+	b += sprintf(b, "\twrite type:\t\t%s\n", msg);
+
+	b += sprintf(b, "\tpacket type:\t\t%s\n", pd->settings.fp ? "Fixed" : "Variable");
+	b += sprintf(b, "\tlink loss:\t\t%d\n", pd->settings.link_loss);
+
+	b += sprintf(b, "\ttrack mode:\t\t%d\n", pd->settings.track_mode);
+
+	if (pd->settings.block_mode == PACKET_BLOCK_MODE1)
+		msg = "Mode 1";
+	else if (pd->settings.block_mode == PACKET_BLOCK_MODE2)
+		msg = "Mode 2";
+	else
+		msg = "Unknown";
+	b += sprintf(b, "\tblock mode:\t\t%s\n", msg);
+
+	b += sprintf(b, "\nStatistics:\n");
+	b += sprintf(b, "\tpackets started:\t%lu\n", pd->stats.pkt_started);
+	b += sprintf(b, "\tpackets ended:\t\t%lu\n", pd->stats.pkt_ended);
+	b += sprintf(b, "\twritten:\t\t%lukB\n", pd->stats.secs_w >> 1);
+	b += sprintf(b, "\tread gather:\t\t%lukB\n", pd->stats.secs_rg >> 1);
+	b += sprintf(b, "\tread:\t\t\t%lukB\n", pd->stats.secs_r >> 1);
+
+	b += sprintf(b, "\nMisc:\n");
+	b += sprintf(b, "\treference count:\t%d\n", pd->refcnt);
+	b += sprintf(b, "\tflags:\t\t\t0x%lx\n", pd->flags);
+	b += sprintf(b, "\tread speed:\t\t%ukB/s\n", pd->read_speed * 150);
+	b += sprintf(b, "\twrite speed:\t\t%ukB/s\n", pd->write_speed * 150);
+	b += sprintf(b, "\tstart offset:\t\t%lu\n", pd->offset);
+	b += sprintf(b, "\tmode page offset:\t%u\n", pd->mode_offset);
+
+	b += sprintf(b, "\nQueue state:\n");
+	b += sprintf(b, "\tbios queued:\t\t%s\n", pd->bio_queue ? "yes" : "no");
+
+	pkt_count_states(pd, states);
+	b += sprintf(b, "\tstate:\t\t\ti:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n",
+		     states[0], states[1], states[2], states[3], states[4], states[5]);
+
+	return b - buf;
+}
+
+static int pkt_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
+{
+	struct pktcdvd_device *pd = data;
+	char *buf = page;
+	int len;
+
+	len = pkt_proc_device(pd, buf);
+	buf += len;
+
+	if (len <= off + count)
+		*eof = 1;
+
+	*start = page + off;
+	len -= off;
+	if (len > count)
+		len = count;
+	if (len < 0)
+		len = 0;
+
+	return len;
+}
+
+static int pkt_new_dev(struct pktcdvd_device *pd, struct block_device *bdev)
+{
+	dev_t dev = bdev->bd_dev;
+	int i;
+	int ret = 0;
+	char b[BDEVNAME_SIZE];
+
+	for (i = 0; i < MAX_WRITERS; i++) {
+		if (pkt_devs[i].dev == dev) {
+			printk("pktcdvd: %s already setup\n", bdevname(bdev, b));
+			return -EBUSY;
+		}
+	}
+
+	/* This is safe, since we have a reference from open(). */
+	__module_get(THIS_MODULE);
+
+	memset(&pd->stats, 0, sizeof(struct packet_stats));
+
+	if (!pkt_grow_pktlist(pd, CONFIG_CDROM_PKTCDVD_BUFFERS)) {
+		printk("pktcdvd: not enough memory for buffers\n");
+		ret = -ENOMEM;
+		goto out_mem;
+	}
+
+	set_blocksize(bdev, CD_FRAMESIZE);
+	pd->dev = dev;
+	BUG_ON(pd->bdev);
+
+	pkt_init_queue(pd);
+
+	pd->cdrw.thread = kthread_run(kcdrwd, pd, "%s", pd->name);
+	if (IS_ERR(pd->cdrw.thread)) {
+		printk("pktcdvd: can't start kernel thread\n");
+		ret = -ENOMEM;
+		goto out_thread;
+	}
+
+	create_proc_read_entry(pd->name, 0, pkt_proc, pkt_read_proc, pd);
+	DPRINTK("pktcdvd: writer %s mapped to %s\n", pd->name, bdevname(bdev, b));
+	return 0;
+
+out_thread:
+	pkt_shrink_pktlist(pd);
+out_mem:
+	/* This is safe: open() is still holding a reference. */
+	module_put(THIS_MODULE);
+	return ret;
+}
+
+/*
+ * arg contains file descriptor of CD-ROM device.
+ */
+static int pkt_setup_dev(struct pktcdvd_device *pd, unsigned int arg)
+{
+	struct inode *inode;
+	struct file *file;
+	int ret;
+
+	if ((file = fget(arg)) == NULL) {
+		printk("pktcdvd: bad file descriptor passed\n");
+		return -EBADF;
+	}
+
+	inode = file->f_dentry->d_inode;
+	ret = -ENOTBLK;
+	if (!S_ISBLK(inode->i_mode)) {
+		printk("pktcdvd: device is not a block device (duh)\n");
+		goto out;
+	}
+	ret = -EROFS;
+	if (IS_RDONLY(inode)) {
+		printk("pktcdvd: Can't write to read-only dev\n");
+		goto out;
+	}
+	ret = pkt_new_dev(pd, inode->i_bdev);
+
+out:
+	fput(file);
+	return ret;
+}
+
+static int pkt_remove_dev(struct pktcdvd_device *pd)
+{
+	struct block_device *bdev;
+
+	if (!IS_ERR(pd->cdrw.thread))
+		kthread_stop(pd->cdrw.thread);
+
+	/*
+	 * will also invalidate buffers for CD-ROM
+	 */
+	bdev = bdget(pd->pkt_dev);
+	if (bdev) {
+		invalidate_bdev(bdev, 1);
+		bdput(bdev);
+	}
+
+	pkt_shrink_pktlist(pd);
+
+	remove_proc_entry(pd->name, pkt_proc);
+	pd->dev = 0;
+	DPRINTK("pktcdvd: writer %s unmapped\n", pd->name);
+	/* This is safe: open() is still holding a reference. */
+	module_put(THIS_MODULE);
+	return 0;
+}
+
+static int pkt_media_changed(struct gendisk *disk)
+{
+	struct pktcdvd_device *pd = disk->private_data;
+	struct gendisk *attached_disk;
+
+	if (!pd)
+		return 0;
+	if (!pd->bdev)
+		return 0;
+	attached_disk = pd->bdev->bd_disk;
+	if (!attached_disk)
+		return 0;
+	return attached_disk->fops->media_changed(attached_disk);
+}
+
+static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	struct pktcdvd_device *pd = &pkt_devs[iminor(inode)];
+	int ret;
+
+	VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode));
+
+	if ((cmd != PACKET_SETUP_DEV) && !pd->dev) {
+		DPRINTK("pktcdvd: dev not setup\n");
+		return -ENXIO;
+	}
+
+	switch (cmd) {
+	case PACKET_GET_STATS:
+		if (copy_to_user(&arg, &pd->stats, sizeof(struct packet_stats)))
+			return -EFAULT;
+		break;
+
+	case PACKET_SETUP_DEV:
+		if (pd->dev) {
+			printk("pktcdvd: dev already setup\n");
+			return -EBUSY;
+		}
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		return pkt_setup_dev(pd, arg);
+
+	case PACKET_TEARDOWN_DEV:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		down(&pd->ctl_mutex);
+		BUG_ON(pd->refcnt < 1);
+		if (pd->refcnt != 1)
+			ret = -EBUSY;
+		else
+			ret = pkt_remove_dev(pd);
+		up(&pd->ctl_mutex);
+		return ret;
+
+	/*
+	 * forward selected CDROM ioctls to CD-ROM, for UDF
+	 */
+	case CDROMMULTISESSION:
+	case CDROMREADTOCENTRY:
+	case CDROM_LAST_WRITTEN:
+	case CDROM_SEND_PACKET:
+	case SCSI_IOCTL_SEND_COMMAND:
+		if (!pd->bdev)
+			return -ENXIO;
+		return ioctl_by_bdev(pd->bdev, cmd, arg);
+
+	case CDROMEJECT:
+		if (!pd->bdev)
+			return -ENXIO;
+		/*
+		 * The door gets locked when the device is opened, so we
+		 * have to unlock it or else the eject command fails.
+		 */
+		pkt_lock_door(pd, 0);
+		return ioctl_by_bdev(pd->bdev, cmd, arg);
+
+	default:
+		printk("pktcdvd: Unknown ioctl for %s (%x)\n", pd->name, cmd);
+		return -ENOTTY;
+	}
+
+	return 0;
+}
+
+static struct block_device_operations pktcdvd_ops = {
+	.owner =		THIS_MODULE,
+	.open =			pkt_open,
+	.release =		pkt_close,
+	.ioctl =		pkt_ioctl,
+	.media_changed =	pkt_media_changed,
+};
+
+int pkt_init(void)
+{
+	int i;
+
+	devfs_mk_dir("pktcdvd");
+	if (register_blkdev(PACKET_MAJOR, "pktcdvd")) {
+		printk("unable to register pktcdvd device\n");
+		return -EIO;
+	}
+
+	pkt_devs = kmalloc(MAX_WRITERS * sizeof(struct pktcdvd_device), GFP_KERNEL);
+	if (pkt_devs == NULL)
+		goto out_mem;
+	memset(pkt_devs, 0, MAX_WRITERS * sizeof(struct pktcdvd_device));
+
+	for (i = 0; i < MAX_WRITERS; i++) {
+		struct pktcdvd_device *pd = &pkt_devs[i];
+
+		pd->disk = alloc_disk(1);
+		if (!pd->disk)
+			goto out_mem2;
+	}
+
+	for (i = 0; i < MAX_WRITERS; i++) {
+		struct pktcdvd_device *pd = &pkt_devs[i];
+		struct gendisk *disk = pd->disk;
+
+		spin_lock_init(&pd->lock);
+		spin_lock_init(&pd->iosched.lock);
+		pd->pkt_dev = MKDEV(PACKET_MAJOR, i);
+		sprintf(pd->name, "pktcdvd%d", i);
+		init_waitqueue_head(&pd->wqueue);
+		init_MUTEX(&pd->ctl_mutex);
+
+		disk->major = PACKET_MAJOR;
+		disk->first_minor = i;
+		disk->fops = &pktcdvd_ops;
+		disk->flags = GENHD_FL_REMOVABLE;
+		sprintf(disk->disk_name, "pktcdvd%d", i);
+		sprintf(disk->devfs_name, "pktcdvd/%d", i);
+		disk->private_data = pd;
+		disk->queue = blk_alloc_queue(GFP_KERNEL);
+		if (!disk->queue)
+			goto out_mem3;
+		add_disk(disk);
+	}
+
+	pkt_proc = proc_mkdir("pktcdvd", proc_root_driver);
+
+	DPRINTK("pktcdvd: %s\n", VERSION_CODE);
+	return 0;
+
+out_mem3:
+	while (i--)
+		blk_put_queue(pkt_devs[i].disk->queue);
+	i = MAX_WRITERS;
+out_mem2:
+	while (i--)
+		put_disk(pkt_devs[i].disk);
+	kfree(pkt_devs);
+out_mem:
+	printk("pktcdvd: out of memory\n");
+	devfs_remove("pktcdvd");
+	unregister_blkdev(PACKET_MAJOR, "pktcdvd");
+	return -ENOMEM;
+}
+
+void pkt_exit(void)
+{
+	int i;
+	for (i = 0; i < MAX_WRITERS; i++) {
+		struct gendisk *disk = pkt_devs[i].disk;
+		del_gendisk(disk);
+		blk_put_queue(disk->queue);
+		put_disk(disk);
+	}
+
+	devfs_remove("pktcdvd");
+	unregister_blkdev(PACKET_MAJOR, "pktcdvd");
+
+	remove_proc_entry("pktcdvd", proc_root_driver);
+	kfree(pkt_devs);
+}
+
+MODULE_DESCRIPTION("Packet writing layer for CD/DVD drives");
+MODULE_AUTHOR("Jens Axboe <axboe@suse.de>");
+MODULE_LICENSE("GPL");
+
+module_init(pkt_init);
+module_exit(pkt_exit);
+
+MODULE_ALIAS_BLOCKDEV_MAJOR(PACKET_MAJOR);
unchanged:
--- linux/drivers/cdrom/Makefile~packet-2.6.7	2004-07-05 01:31:05.187833392 +0200
+++ linux-petero/drivers/cdrom/Makefile	2004-07-05 01:31:05.210829896 +0200
@@ -8,6 +8,7 @@
 obj-$(CONFIG_BLK_DEV_IDECD)	+=              cdrom.o
 obj-$(CONFIG_BLK_DEV_SR)	+=              cdrom.o
 obj-$(CONFIG_PARIDE_PCD)	+=		cdrom.o
+obj-$(CONFIG_CDROM_PKTCDVD)	+=		cdrom.o
 
 obj-$(CONFIG_AZTCD)		+= aztcd.o
 obj-$(CONFIG_CDU31A)		+= cdu31a.o     cdrom.o
unchanged:
--- linux-petero/drivers/ide/ide-cd.c	2004-07-05 01:31:05.234826248 +0200
+++ linux-petero/drivers/ide/ide-cd.c	2004-07-05 01:31:10.599010768 +0200
@@ -1940,6 +1940,8 @@
 	info->dma = drive->using_dma ? 1 : 0;
 	info->cmd = WRITE;
 
+	info->devinfo.media_written = 1;
+
 	/* Start sending the write request to the drive. */
 	return cdrom_start_packet_command(drive, 32768, cdrom_start_write_cont);
 }
@@ -2003,7 +2005,7 @@
 			}
 			CDROM_CONFIG_FLAGS(drive)->seeking = 0;
 		}
-		if (IDE_LARGE_SEEK(info->last_block, block, IDECD_SEEK_THRESHOLD) && drive->dsc_overlap) {
+		if ((rq_data_dir(rq) == READ) && IDE_LARGE_SEEK(info->last_block, block, IDECD_SEEK_THRESHOLD) && drive->dsc_overlap) {
 			action = cdrom_start_seek(drive, block);
 		} else {
 			if (rq_data_dir(rq) == READ)
@@ -2960,8 +2962,10 @@
 		CDROM_CONFIG_FLAGS(drive)->no_eject = 0;
 	if (cap.cd_r_write)
 		CDROM_CONFIG_FLAGS(drive)->cd_r = 1;
-	if (cap.cd_rw_write)
+	if (cap.cd_rw_write) {
 		CDROM_CONFIG_FLAGS(drive)->cd_rw = 1;
+		CDROM_CONFIG_FLAGS(drive)->ram = 1;
+	}
 	if (cap.test_write)
 		CDROM_CONFIG_FLAGS(drive)->test_write = 1;
 	if (cap.dvd_ram_read || cap.dvd_r_read || cap.dvd_rom)
unchanged:
--- linux-petero/drivers/scsi/sr.c	2004-07-05 01:31:05.235826096 +0200
+++ linux-petero/drivers/scsi/sr.c	2004-07-05 01:31:10.596011224 +0200
@@ -379,6 +379,7 @@
 			return 0;
 		SCpnt->cmnd[0] = WRITE_10;
 		SCpnt->sc_data_direction = DMA_TO_DEVICE;
+ 	 	cd->cdi.media_written = 1;
 	} else if (rq_data_dir(SCpnt->request) == READ) {
 		SCpnt->cmnd[0] = READ_10;
 		SCpnt->sc_data_direction = DMA_FROM_DEVICE;
@@ -880,10 +881,10 @@
 		cd->cdi.mask |= CDC_CLOSE_TRAY; */
 
 	/*
-	 * if DVD-RAM of MRW-W, we are randomly writeable
+	 * if DVD-RAM, MRW-W or CD-RW, we are randomly writable
 	 */
-	if ((cd->cdi.mask & (CDC_DVD_RAM | CDC_MRW_W | CDC_RAM)) !=
-			(CDC_DVD_RAM | CDC_MRW_W | CDC_RAM)) {
+	if ((cd->cdi.mask & (CDC_DVD_RAM | CDC_MRW_W | CDC_RAM | CDC_CD_RW)) !=
+			(CDC_DVD_RAM | CDC_MRW_W | CDC_RAM | CDC_CD_RW)) {
 		cd->device->writeable = 1;
 		set_disk_ro(cd->disk, 0);
 	}
unchanged:
--- linux-petero/include/linux/cdrom.h	2004-07-05 01:31:05.236825944 +0200
+++ linux-petero/include/linux/cdrom.h	2004-07-05 01:31:10.600010616 +0200
@@ -498,6 +498,7 @@
 #define GPMODE_VENDOR_PAGE		0x00
 #define GPMODE_R_W_ERROR_PAGE		0x01
 #define GPMODE_WRITE_PARMS_PAGE		0x05
+#define GPMODE_WCACHING_PAGE		0x08
 #define GPMODE_AUDIO_CTL_PAGE		0x0e
 #define GPMODE_POWER_PAGE		0x1a
 #define GPMODE_FAULT_FAIL_PAGE		0x1c
@@ -946,6 +947,8 @@
         __u8 reserved		: 6;	/* not used yet */
 	int cdda_method;		/* see flags */
 	__u8 last_sense;
+	__u8 media_written;		/* dirty flag, DVD+RW bookkeeping */
+	unsigned short mmc3_profile;	/* current MMC3 profile */
 	int for_data;
 	int (*exit)(struct cdrom_device_info *);
 	int mrw_mode_page;
unchanged:
--- linux/include/linux/major.h~packet-2.6.7	2004-07-05 01:31:05.197831872 +0200
+++ linux-petero/include/linux/major.h	2004-07-05 01:31:05.236825944 +0200
@@ -111,6 +111,8 @@
 
 #define MDISK_MAJOR		95
 
+#define PACKET_MAJOR		97
+
 #define UBD_MAJOR		98
 
 #define JSFD_MAJOR		99
diff -u linux-petero/include/linux/pktcdvd.h linux-petero/include/linux/pktcdvd.h
--- linux-petero/include/linux/pktcdvd.h	2004-07-05 01:31:43.739972576 +0200
+++ linux-petero/include/linux/pktcdvd.h	2004-07-05 01:31:44.890797624 +0200
@@ -0,0 +1,262 @@
+/*
+ * Copyright (C) 2000 Jens Axboe <axboe@suse.de>
+ * Copyright (C) 2001-2004 Peter Osterlund <petero2@telia.com>
+ *
+ * May be copied or modified under the terms of the GNU General Public
+ * License.  See linux/COPYING for more information.
+ *
+ * Packet writing layer for ATAPI and SCSI CD-R, CD-RW, DVD-R, and
+ * DVD-RW devices.
+ *
+ */
+#ifndef __PKTCDVD_H
+#define __PKTCDVD_H
+
+/*
+ * 1 for normal debug messages, 2 is very verbose. 0 to turn it off.
+ */
+#define PACKET_DEBUG		1
+
+#define	MAX_WRITERS		8
+
+/*
+ * How long we should hold a non-full packet before starting data gathering.
+ */
+#define PACKET_WAIT_TIME	(HZ * 5 / 1000)
+
+/*
+ * use drive write caching -- we need deferred error handling to be
+ * able to sucessfully recover with this option (drive will return good
+ * status as soon as the cdb is validated).
+ */
+#if defined(CONFIG_CDROM_PKTCDVD_WCACHE)
+#warning Enabling write caching, use at your own risk
+#define USE_WCACHING		1
+#else
+#define USE_WCACHING		0
+#endif
+
+/*
+ * No user-servicable parts beyond this point ->
+ */
+
+#if PACKET_DEBUG
+#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+#if PACKET_DEBUG > 1
+#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
+#else
+#define VPRINTK(fmt, args...)
+#endif
+
+/*
+ * device types
+ */
+#define PACKET_CDR		1
+#define	PACKET_CDRW		2
+#define PACKET_DVDR		3
+#define PACKET_DVDRW		4
+
+/*
+ * flags
+ */
+#define PACKET_WRITABLE		1	/* pd is writable */
+#define PACKET_NWA_VALID	2	/* next writable address valid */
+#define PACKET_LRA_VALID	3	/* last recorded address valid */
+#define PACKET_MERGE_SEGS	4	/* perform segment merging to keep */
+					/* underlying cdrom device happy */
+
+/*
+ * Disc status -- from READ_DISC_INFO
+ */
+#define PACKET_DISC_EMPTY	0
+#define PACKET_DISC_INCOMPLETE	1
+#define PACKET_DISC_COMPLETE	2
+#define PACKET_DISC_OTHER	3
+
+/*
+ * write type, and corresponding data block type
+ */
+#define PACKET_MODE1		1
+#define PACKET_MODE2		2
+#define PACKET_BLOCK_MODE1	8
+#define PACKET_BLOCK_MODE2	10
+
+/*
+ * Last session/border status
+ */
+#define PACKET_SESSION_EMPTY		0
+#define PACKET_SESSION_INCOMPLETE	1
+#define PACKET_SESSION_RESERVED		2
+#define PACKET_SESSION_COMPLETE		3
+
+#define PACKET_MCN			"4a656e734178626f65323030300000"
+
+#undef PACKET_USE_LS
+
+/*
+ * Very crude stats for now
+ */
+struct packet_stats
+{
+	unsigned long		pkt_started;
+	unsigned long		pkt_ended;
+	unsigned long		secs_w;
+	unsigned long		secs_rg;
+	unsigned long		secs_r;
+};
+
+/*
+ * packet ioctls
+ */
+#define PACKET_IOCTL_MAGIC	('X')
+#define PACKET_GET_STATS	_IOR(PACKET_IOCTL_MAGIC, 0, struct packet_stats)
+#define PACKET_SETUP_DEV	_IOW(PACKET_IOCTL_MAGIC, 1, unsigned int)
+#define PACKET_TEARDOWN_DEV	_IOW(PACKET_IOCTL_MAGIC, 2, unsigned int)
+
+#ifdef __KERNEL__
+#include <linux/blkdev.h>
+#include <linux/completion.h>
+#include <linux/cdrom.h>
+
+struct packet_settings
+{
+	__u8			size;		/* packet size in (512 byte) sectors */
+	__u8			fp;		/* fixed packets */
+	__u8			link_loss;	/* the rest is specified
+						 * as per Mt Fuji */
+	__u8			write_type;
+	__u8			track_mode;
+	__u8			block_mode;
+};
+
+struct packet_cdrw
+{
+	struct list_head	pkt_free_list;
+	struct list_head	pkt_active_list;
+	spinlock_t		active_list_lock; /* Serialize access to pkt_active_list */
+	struct task_struct	*thread;
+	elevator_merge_fn	*elv_merge_fn;
+	elevator_completed_req_fn *elv_completed_req_fn;
+	merge_requests_fn	*merge_requests_fn;
+};
+
+/*
+ * Switch to high speed reading after reading this many kilobytes
+ * with no interspersed writes.
+ */
+#define HI_SPEED_SWITCH 512
+
+struct packet_iosched
+{
+	atomic_t		attention;	/* Set to non-zero when queue processing is needed */
+	int			writing;	/* Non-zero when writing, zero when reading */
+	spinlock_t		lock;		/* Protecting read/write queue manipulations */
+	struct bio		*read_queue;
+	struct bio		*read_queue_tail;
+	struct bio		*write_queue;
+	struct bio		*write_queue_tail;
+	int			high_prio_read;	/* An important read request has been queued */
+	int			successive_reads;
+};
+
+/*
+ * 32 buffers of 2048 bytes
+ */
+#define PACKET_MAX_SIZE		32
+#define PAGES_PER_PACKET	(PACKET_MAX_SIZE * CD_FRAMESIZE / PAGE_SIZE)
+#define PACKET_MAX_SECTORS	(PACKET_MAX_SIZE * CD_FRAMESIZE >> 9)
+
+enum packet_data_state {
+	PACKET_IDLE_STATE,			/* Not used at the moment */
+	PACKET_WAITING_STATE,			/* Waiting for more bios to arrive, so */
+						/* we don't have to do as much */
+						/* data gathering */
+	PACKET_READ_WAIT_STATE,			/* Waiting for reads to fill in holes */
+	PACKET_WRITE_WAIT_STATE,		/* Waiting for the write to complete */
+	PACKET_RECOVERY_STATE,			/* Recover after read/write errors */
+	PACKET_FINISHED_STATE,			/* After write has finished */
+
+	PACKET_NUM_STATES			/* Number of possible states */
+};
+
+/*
+ * Information needed for writing a single packet
+ */
+struct pktcdvd_device;
+
+struct packet_data
+{
+	struct list_head	list;
+
+	spinlock_t		lock;		/* Lock protecting state transitions and */
+						/* orig_bios list */
+
+	struct bio		*orig_bios;	/* Original bios passed to pkt_make_request */
+	struct bio		*orig_bios_tail;/* that will be handled by this packet */
+	int			write_size;	/* Total size of all bios in the orig_bios */
+						/* list, measured in number of frames */
+
+	struct bio		*w_bio;		/* The bio we will send to the real CD */
+						/* device once we have all data for the */
+						/* packet we are going to write */
+	sector_t		sector;		/* First sector in this packet */
+	int			frames;		/* Number of frames in this packet */
+
+	enum packet_data_state	state;		/* Current state */
+	atomic_t		run_sm;		/* Incremented whenever the state */
+						/* machine needs to be run */
+	long			sleep_time;	/* Set this to non-zero to make the state */
+						/* machine run after this many jiffies. */
+
+	atomic_t		io_wait;	/* Number of pending IO operations */
+	atomic_t		io_errors;	/* Number of read/write errors during IO */
+
+	struct bio		*r_bios[PACKET_MAX_SIZE]; /* bios to use during data gathering */
+	struct page		*pages[PAGES_PER_PACKET];
+
+	int			cache_valid;	/* If non-zero, the data for the zone defined */
+						/* by the sector variable is completely cached */
+						/* in the pages[] vector. */
+
+	int			id;		/* ID number for debugging */
+	struct pktcdvd_device	*pd;
+};
+
+struct pktcdvd_device
+{
+	struct block_device	*bdev;		/* dev attached */
+	dev_t			dev;		/* dev attached */
+	dev_t			pkt_dev;	/* our dev */
+	char			name[20];
+	struct packet_settings	settings;
+	struct packet_stats	stats;
+	struct semaphore	ctl_mutex;	/* Serialize access to refcnt */
+	int			refcnt;		/* Open count */
+	__u8			write_speed;	/* current write speed */
+	__u8			read_speed;	/* current read speed */
+	unsigned long		offset;		/* start offset */
+	__u8			mode_offset;	/* 0 / 8 */
+	__u8			type;
+	unsigned long		flags;
+	__u16			mmc3_profile;
+	__u32			nwa;		/* next writable address */
+	__u32			lra;		/* last recorded address */
+	struct packet_cdrw	cdrw;
+	wait_queue_head_t	wqueue;
+
+	spinlock_t		lock;		/* Serialize access to bio_queue */
+	struct bio		*bio_queue;	/* Work queue of bios we need to handle */
+	struct bio		*bio_queue_tail;
+	atomic_t		scan_queue;	/* Set to non-zero when pkt_handle_queue */
+						/* needs to be run. */
+	struct packet_iosched   iosched;
+	struct gendisk		*disk;
+};
+
+#endif /* __KERNEL__ */
+
+#endif /* __PKTCDVD_H */
unchanged:
--- linux/drivers/cdrom/cdrom.c~dvd+rw	2004-07-05 01:31:10.579013808 +0200
+++ linux-petero/drivers/cdrom/cdrom.c	2004-07-05 01:31:10.595011376 +0200
@@ -821,6 +821,41 @@ static int cdrom_ram_open_write(struct c
 	return ret;
 }
 
+static void cdrom_mmc3_profile(struct cdrom_device_info *cdi)
+{
+	struct packet_command cgc;
+	char buffer[32];
+	int ret, mmc3_profile;
+
+	init_cdrom_command(&cgc, buffer, sizeof(buffer), CGC_DATA_READ);
+
+	cgc.cmd[0] = GPCMD_GET_CONFIGURATION;
+	cgc.cmd[1] = 0;
+	cgc.cmd[2] = cgc.cmd[3] = 0;		/* Starting Feature Number */
+	cgc.cmd[7] = 0; cgc.cmd [8] = 8;	/* Allocation Length */
+	cgc.quiet = 1;
+
+	if ((ret = cdi->ops->generic_packet(cdi, &cgc))) {
+		mmc3_profile = 0xffff;
+	} else {
+		mmc3_profile = (buffer[6] << 8) | buffer[7];
+		printk(KERN_INFO "cdrom: %s: mmc-3 profile capable, current profile: %Xh\n",
+		       cdi->name, mmc3_profile);
+	}
+	cdi->mmc3_profile = mmc3_profile;
+}
+
+static int cdrom_is_dvd_rw(struct cdrom_device_info *cdi)
+{
+	switch (cdi->mmc3_profile) {
+	case 0x12:	/* DVD-RAM	*/
+	case 0x1A:	/* DVD+RW	*/
+		return 0;
+	default:
+		return 1;
+	}
+}
+
 /*
  * returns 0 for ok to open write, non-0 to disallow
  */
@@ -859,10 +894,50 @@ static int cdrom_open_write(struct cdrom
  		ret = cdrom_ram_open_write(cdi);
 	else if (CDROM_CAN(CDC_MO_DRIVE))
 		ret = mo_open_write(cdi);
+	else if (!cdrom_is_dvd_rw(cdi))
+		ret = 0;
 
 	return ret;
 }
 
+static void cdrom_dvd_rw_close_write(struct cdrom_device_info *cdi)
+{
+	struct packet_command cgc;
+
+	if (cdi->mmc3_profile != 0x1a) {
+		cdinfo(CD_CLOSE, "%s: No DVD+RW\n", cdi->name);
+		return;
+	}
+
+	if (!cdi->media_written) {
+		cdinfo(CD_CLOSE, "%s: DVD+RW media clean\n", cdi->name);
+		return;
+	}
+
+	printk(KERN_INFO "cdrom: %s: dirty DVD+RW media, \"finalizing\"\n",
+	       cdi->name);
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_FLUSH_CACHE;
+	cgc.timeout = 30*HZ;
+	cdi->ops->generic_packet(cdi, &cgc);
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_CLOSE_TRACK;
+	cgc.timeout = 3000*HZ;
+	cgc.quiet = 1;
+	cdi->ops->generic_packet(cdi, &cgc);
+
+	init_cdrom_command(&cgc, NULL, 0, CGC_DATA_NONE);
+	cgc.cmd[0] = GPCMD_CLOSE_TRACK;
+	cgc.cmd[2] = 2;	 /* Close session */
+	cgc.quiet = 1;
+	cgc.timeout = 3000*HZ;
+	cdi->ops->generic_packet(cdi, &cgc);
+
+	cdi->media_written = 0;
+}
+
 static int cdrom_close_write(struct cdrom_device_info *cdi)
 {
 #if 0
@@ -895,6 +970,7 @@ int cdrom_open(struct cdrom_device_info 
 		ret = open_for_data(cdi);
 		if (ret)
 			goto err;
+		cdrom_mmc3_profile(cdi);
 		if (fp->f_mode & FMODE_WRITE) {
 			ret = -EROFS;
 			if (!CDROM_CAN(CDC_RAM))
@@ -902,6 +978,7 @@ int cdrom_open(struct cdrom_device_info 
 			if (cdrom_open_write(cdi))
 				goto err;
 			ret = 0;
+			cdi->media_written = 0;
 		}
 	}
 
@@ -1093,6 +1170,8 @@ int cdrom_release(struct cdrom_device_in
 		cdi->use_count--;
 	if (cdi->use_count == 0)
 		cdinfo(CD_CLOSE, "Use count for \"/dev/%s\" now zero\n", cdi->name);
+	if (cdi->use_count == 0)
+		cdrom_dvd_rw_close_write(cdi);
 	if (cdi->use_count == 0 &&
 	    (cdo->capability & CDC_LOCK) && !keeplocked) {
 		cdinfo(CD_CLOSE, "Unlocking door!\n");
@@ -1299,6 +1378,7 @@ int media_changed(struct cdrom_device_in
 	if (cdi->ops->media_changed(cdi, CDSL_CURRENT)) {
 		cdi->mc_flags = 0x3;    /* set bit on both queues */
 		ret |= 1;
+		cdi->media_written = 0;
 	}
 	cdi->mc_flags &= ~mask;         /* clear bit */
 	return ret;

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-04 23:49   ` Peter Osterlund
  2004-07-05  0:01     ` Peter Osterlund
@ 2004-07-05  8:17     ` Jens Axboe
  2004-07-05 10:26     ` Christoph Hellwig
  2004-07-06  8:45     ` Peter Osterlund
  3 siblings, 0 replies; 27+ messages in thread
From: Jens Axboe @ 2004-07-05  8:17 UTC (permalink / raw)
  To: Peter Osterlund; +Cc: Christoph Hellwig, linux-kernel, Andrew Morton

On Mon, Jul 05 2004, Peter Osterlund wrote:
> > All in all I really wonder whether a separate driver is really that
> > a good fit for the functionality or whether it should be more
> > integrated with the block layer, ala drivers/block/scsi_ioctl.c
> 
> Jens is probably better suited than me to comment on that.

That would by far be the superior approach. The data gathering really
does belong in the fs (it's just a larger fs block size) and would rid
the driver of the elevator hacks it's currently using. Plus all the data
gathering code, which has been notoriously buggy and hard to get right
and deadlock free (in the past at least for 2.4, I haven't worked on
this for a long time so it's not a comment on 2.6 code quality).

That would basically allow the merge of the remaining setup code into
cdrom.c. It needs someone to do the actual UDF work though... The
upside is that you would get automatic support for other devices as
well, such as the 8KiB UDO.

Ideally, someone would add support for bigger PAGE_CACHE_SIZE :)

-- 
Jens Axboe


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-04 23:49   ` Peter Osterlund
  2004-07-05  0:01     ` Peter Osterlund
  2004-07-05  8:17     ` [PATCH] CDRW packet writing support for 2.6.7-bk13 Jens Axboe
@ 2004-07-05 10:26     ` Christoph Hellwig
  2004-07-06  8:45     ` Peter Osterlund
  3 siblings, 0 replies; 27+ messages in thread
From: Christoph Hellwig @ 2004-07-05 10:26 UTC (permalink / raw)
  To: Peter Osterlund; +Cc: linux-kernel, Jens Axboe, Andrew Morton

On Mon, Jul 05, 2004 at 01:49:43AM +0200, Peter Osterlund wrote:
> For fsync_bdev(), which you say is not needed anyway. Also for the
> invalidate_bdev() call in pkt_remove_dev(). The loop driver is also
> calling invalidate_bdev() in its loop_clr_fd() function, so I guess
> that call is needed.

Only as long as it's reusing the same gendisk for different devices,
more on that below.

> > > +	for (i = 0; i < MAX_WRITERS; i++) {
> > > +		struct pktcdvd_device *pd = &pkt_devs[i];
> > > +		struct gendisk *disk = disks[i];
> > > +		disk->major = PACKET_MAJOR;
> > > +		disk->first_minor = i;
> > > +		disk->fops = &pktcdvd_ops;
> > > +		disk->flags = GENHD_FL_REMOVABLE;
> > > +		sprintf(disk->disk_name, "pktcdvd%d", i);
> > > +		sprintf(disk->devfs_name, "pktcdvd/%d", i);
> > > +		disk->private_data = pd;
> > > +		disk->queue = blk_alloc_queue(GFP_KERNEL);
> > > +		if (!disk->queue)
> > > +			goto out_mem3;
> > > +		add_disk(disk);
> > > +	}
> > 
> > Please allocate all these on demand only when you actually attach
> > a device.
> 
> But I need to open the device to be able to perform the ioctl to
> attach a device, and I can't open the device until add_disk() has been
> called. The loop device does the same thing.

And loops is broken the same way ;-)  This I need an blockdevice to actually
attach it scheme breaks in many ways, like the need to reset a gendisk
instead of allocating a new one, lots of warts in ->open and problems with
udev.  Just keep a single character device around for all administrative
work (like device mapper) - otoh if packet writing really moves to the
block layer all this is magically fixed..


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-04 23:49   ` Peter Osterlund
                       ` (2 preceding siblings ...)
  2004-07-05 10:26     ` Christoph Hellwig
@ 2004-07-06  8:45     ` Peter Osterlund
  3 siblings, 0 replies; 27+ messages in thread
From: Peter Osterlund @ 2004-07-06  8:45 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: linux-kernel, Jens Axboe, Andrew Morton

Peter Osterlund <petero2@telia.com> writes:

> Christoph Hellwig <hch@infradead.org> writes:
> 
> > > +static int pkt_proc_device(struct pktcdvd_device *pd, char *buf)
> > > +{
> > 
> > seq_file interface please, or even better a one value per file sysfs
> > interface.
> 
> I'll work on this tomorrow too.

I don't know if there's any point converting this to the sysfs
interface, since the proc file only contains a bunch of read-only
values which are mostly only useful for debugging.

Anyway, here is a patch to convert it to seq_file.

-----------------

Convert the /proc code in the pktcdvd driver to use the seq_file
interface.

Signed-off-by: Peter Osterlund <petero2@telia.com>

---

 linux-petero/drivers/block/pktcdvd.c |   97 ++++++++++++++++-------------------
 1 files changed, 47 insertions(+), 50 deletions(-)

diff -puN drivers/block/pktcdvd.c~packet-seqfile drivers/block/pktcdvd.c
--- linux/drivers/block/pktcdvd.c~packet-seqfile	2004-07-06 01:37:31.000000000 +0200
+++ linux-petero/drivers/block/pktcdvd.c	2004-07-06 03:33:44.000000000 +0200
@@ -43,6 +43,7 @@
 #include <linux/spinlock.h>
 #include <linux/file.h>
 #include <linux/proc_fs.h>
+#include <linux/seq_file.h>
 #include <linux/buffer_head.h>		/* for invalidate_bdev() */
 #include <linux/devfs_fs_kernel.h>
 #include <linux/suspend.h>
@@ -2275,28 +2276,29 @@ static void pkt_init_queue(struct pktcdv
 	q->queuedata = pd;
 }
 
-static int pkt_proc_device(struct pktcdvd_device *pd, char *buf)
+static int pkt_seq_show(struct seq_file *m, void *p)
 {
-	char *b = buf, *msg;
+	struct pktcdvd_device *pd = m->private;
+	char *msg;
 	char bdev_buf[BDEVNAME_SIZE];
 	int states[PACKET_NUM_STATES];
 
-	b += sprintf(b, "\nWriter %s mapped to %s:\n", pd->name,
-		     __bdevname(pd->dev, bdev_buf));
+	seq_printf(m, "Writer %s mapped to %s:\n", pd->name,
+		   __bdevname(pd->dev, bdev_buf));
 
-	b += sprintf(b, "\nSettings:\n");
-	b += sprintf(b, "\tpacket size:\t\t%dkB\n", pd->settings.size / 2);
+	seq_printf(m, "\nSettings:\n");
+	seq_printf(m, "\tpacket size:\t\t%dkB\n", pd->settings.size / 2);
 
 	if (pd->settings.write_type == 0)
 		msg = "Packet";
 	else
 		msg = "Unknown";
-	b += sprintf(b, "\twrite type:\t\t%s\n", msg);
+	seq_printf(m, "\twrite type:\t\t%s\n", msg);
 
-	b += sprintf(b, "\tpacket type:\t\t%s\n", pd->settings.fp ? "Fixed" : "Variable");
-	b += sprintf(b, "\tlink loss:\t\t%d\n", pd->settings.link_loss);
+	seq_printf(m, "\tpacket type:\t\t%s\n", pd->settings.fp ? "Fixed" : "Variable");
+	seq_printf(m, "\tlink loss:\t\t%d\n", pd->settings.link_loss);
 
-	b += sprintf(b, "\ttrack mode:\t\t%d\n", pd->settings.track_mode);
+	seq_printf(m, "\ttrack mode:\t\t%d\n", pd->settings.track_mode);
 
 	if (pd->settings.block_mode == PACKET_BLOCK_MODE1)
 		msg = "Mode 1";
@@ -2304,61 +2306,52 @@ static int pkt_proc_device(struct pktcdv
 		msg = "Mode 2";
 	else
 		msg = "Unknown";
-	b += sprintf(b, "\tblock mode:\t\t%s\n", msg);
+	seq_printf(m, "\tblock mode:\t\t%s\n", msg);
 
-	b += sprintf(b, "\nStatistics:\n");
-	b += sprintf(b, "\tpackets started:\t%lu\n", pd->stats.pkt_started);
-	b += sprintf(b, "\tpackets ended:\t\t%lu\n", pd->stats.pkt_ended);
-	b += sprintf(b, "\twritten:\t\t%lukB\n", pd->stats.secs_w >> 1);
-	b += sprintf(b, "\tread gather:\t\t%lukB\n", pd->stats.secs_rg >> 1);
-	b += sprintf(b, "\tread:\t\t\t%lukB\n", pd->stats.secs_r >> 1);
-
-	b += sprintf(b, "\nMisc:\n");
-	b += sprintf(b, "\treference count:\t%d\n", pd->refcnt);
-	b += sprintf(b, "\tflags:\t\t\t0x%lx\n", pd->flags);
-	b += sprintf(b, "\tread speed:\t\t%ukB/s\n", pd->read_speed * 150);
-	b += sprintf(b, "\twrite speed:\t\t%ukB/s\n", pd->write_speed * 150);
-	b += sprintf(b, "\tstart offset:\t\t%lu\n", pd->offset);
-	b += sprintf(b, "\tmode page offset:\t%u\n", pd->mode_offset);
+	seq_printf(m, "\nStatistics:\n");
+	seq_printf(m, "\tpackets started:\t%lu\n", pd->stats.pkt_started);
+	seq_printf(m, "\tpackets ended:\t\t%lu\n", pd->stats.pkt_ended);
+	seq_printf(m, "\twritten:\t\t%lukB\n", pd->stats.secs_w >> 1);
+	seq_printf(m, "\tread gather:\t\t%lukB\n", pd->stats.secs_rg >> 1);
+	seq_printf(m, "\tread:\t\t\t%lukB\n", pd->stats.secs_r >> 1);
+
+	seq_printf(m, "\nMisc:\n");
+	seq_printf(m, "\treference count:\t%d\n", pd->refcnt);
+	seq_printf(m, "\tflags:\t\t\t0x%lx\n", pd->flags);
+	seq_printf(m, "\tread speed:\t\t%ukB/s\n", pd->read_speed * 150);
+	seq_printf(m, "\twrite speed:\t\t%ukB/s\n", pd->write_speed * 150);
+	seq_printf(m, "\tstart offset:\t\t%lu\n", pd->offset);
+	seq_printf(m, "\tmode page offset:\t%u\n", pd->mode_offset);
 
-	b += sprintf(b, "\nQueue state:\n");
-	b += sprintf(b, "\tbios queued:\t\t%s\n", pd->bio_queue ? "yes" : "no");
+	seq_printf(m, "\nQueue state:\n");
+	seq_printf(m, "\tbios queued:\t\t%s\n", pd->bio_queue ? "yes" : "no");
 
 	pkt_count_states(pd, states);
-	b += sprintf(b, "\tstate:\t\t\ti:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n",
-		     states[0], states[1], states[2], states[3], states[4], states[5]);
+	seq_printf(m, "\tstate:\t\t\ti:%d ow:%d rw:%d ww:%d rec:%d fin:%d\n",
+		   states[0], states[1], states[2], states[3], states[4], states[5]);
 
-	return b - buf;
+	return 0;
 }
 
-static int pkt_read_proc(char *page, char **start, off_t off, int count, int *eof, void *data)
+static int pkt_seq_open(struct inode *inode, struct file *file)
 {
-	struct pktcdvd_device *pd = data;
-	char *buf = page;
-	int len;
-
-	len = pkt_proc_device(pd, buf);
-	buf += len;
-
-	if (len <= off + count)
-		*eof = 1;
-
-	*start = page + off;
-	len -= off;
-	if (len > count)
-		len = count;
-	if (len < 0)
-		len = 0;
-
-	return len;
+	return single_open(file, pkt_seq_show, PDE(inode)->data);
 }
 
+static struct file_operations pkt_proc_fops = {
+	.open	= pkt_seq_open,
+	.read	= seq_read,
+	.llseek	= seq_lseek,
+	.release = single_release
+};
+
 static int pkt_new_dev(struct pktcdvd_device *pd, struct block_device *bdev)
 {
 	dev_t dev = bdev->bd_dev;
 	int i;
 	int ret = 0;
 	char b[BDEVNAME_SIZE];
+	struct proc_dir_entry *proc;
 
 	for (i = 0; i < MAX_WRITERS; i++) {
 		if (pkt_devs[i].dev == dev) {
@@ -2391,7 +2384,11 @@ static int pkt_new_dev(struct pktcdvd_de
 		goto out_thread;
 	}
 
-	create_proc_read_entry(pd->name, 0, pkt_proc, pkt_read_proc, pd);
+	proc = create_proc_entry(pd->name, 0, pkt_proc);
+	if (proc) {
+		proc->data = pd;
+		proc->proc_fops = &pkt_proc_fops;
+	}
 	DPRINTK("pktcdvd: writer %s mapped to %s\n", pd->name, bdevname(bdev, b));
 	return 0;
 
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-04 13:05 ` [PATCH] CDRW packet writing support for 2.6.7-bk13 Christoph Hellwig
  2004-07-04 23:49   ` Peter Osterlund
@ 2004-07-07 10:06   ` Peter Osterlund
  1 sibling, 0 replies; 27+ messages in thread
From: Peter Osterlund @ 2004-07-07 10:06 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: linux-kernel, Jens Axboe, Andrew Morton

Christoph Hellwig <hch@infradead.org> writes:

> > +static void pkt_release_dev(struct pktcdvd_device *pd, int flush)
> > +{
> > +	struct block_device *bdev;
> > +
> > +	atomic_dec(&pd->refcnt);
> > +	if (atomic_read(&pd->refcnt) > 0)
> > +		return;
> > +
> > +	bdev = bdget(pd->pkt_dev);
> > +	if (bdev) {
> 
> You reallu should keep a reference to the underlying bdev as long as you use
> it

Remove pkt_dev from struct pktcdvd_device in the pktcdvd driver and
remove unnecessary calls to bdget(). Suggested by Christoph Hellwig.

Signed-off-by: Peter Osterlund <petero2@telia.com>

---

 linux-petero/drivers/block/pktcdvd.c |   20 ++++----------------
 linux-petero/include/linux/pktcdvd.h |    1 -
 2 files changed, 4 insertions(+), 17 deletions(-)

diff -puN drivers/block/pktcdvd.c~packet-bdev drivers/block/pktcdvd.c
--- linux/drivers/block/pktcdvd.c~packet-bdev	2004-07-06 21:39:26.000000000 +0200
+++ linux-petero/drivers/block/pktcdvd.c	2004-07-06 23:47:54.243208280 +0200
@@ -1990,7 +1990,6 @@ static void pkt_release_dev(struct pktcd
 static int pkt_open(struct inode *inode, struct file *file)
 {
 	struct pktcdvd_device *pd = NULL;
-	struct block_device *pkt_bdev;
 	int ret;
 	int special_open, exclusive;
 
@@ -2047,11 +2046,7 @@ static int pkt_open(struct inode *inode,
 	 * needed here as well, since ext2 (among others) may change
 	 * the blocksize at mount time
 	 */
-	pkt_bdev = bdget(inode->i_rdev);
-	if (pkt_bdev) {
-		set_blocksize(pkt_bdev, CD_FRAMESIZE);
-		bdput(pkt_bdev);
-	}
+	set_blocksize(inode->i_bdev, CD_FRAMESIZE);
 
 done:
 	up(&pd->ctl_mutex);
@@ -2432,21 +2427,15 @@ out:
 	return ret;
 }
 
-static int pkt_remove_dev(struct pktcdvd_device *pd)
+static int pkt_remove_dev(struct pktcdvd_device *pd, struct block_device *bdev)
 {
-	struct block_device *bdev;
-
 	if (!IS_ERR(pd->cdrw.thread))
 		kthread_stop(pd->cdrw.thread);
 
 	/*
 	 * will also invalidate buffers for CD-ROM
 	 */
-	bdev = bdget(pd->pkt_dev);
-	if (bdev) {
-		invalidate_bdev(bdev, 1);
-		bdput(bdev);
-	}
+	invalidate_bdev(bdev, 1);
 
 	pkt_shrink_pktlist(pd);
 
@@ -2508,7 +2497,7 @@ static int pkt_ioctl(struct inode *inode
 		if (pd->refcnt != 1)
 			ret = -EBUSY;
 		else
-			ret = pkt_remove_dev(pd);
+			ret = pkt_remove_dev(pd, inode->i_bdev);
 		up(&pd->ctl_mutex);
 		return ret;
 
@@ -2579,7 +2568,6 @@ int pkt_init(void)
 
 		spin_lock_init(&pd->lock);
 		spin_lock_init(&pd->iosched.lock);
-		pd->pkt_dev = MKDEV(PACKET_MAJOR, i);
 		sprintf(pd->name, "pktcdvd%d", i);
 		init_waitqueue_head(&pd->wqueue);
 		init_MUTEX(&pd->ctl_mutex);
diff -puN include/linux/pktcdvd.h~packet-bdev include/linux/pktcdvd.h
--- linux/include/linux/pktcdvd.h~packet-bdev	2004-07-06 21:39:26.000000000 +0200
+++ linux-petero/include/linux/pktcdvd.h	2004-07-06 23:47:54.244208128 +0200
@@ -230,7 +230,6 @@ struct pktcdvd_device
 {
 	struct block_device	*bdev;		/* dev attached */
 	dev_t			dev;		/* dev attached */
-	dev_t			pkt_dev;	/* our dev */
 	char			name[20];
 	struct packet_settings	settings;
 	struct packet_stats	stats;
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-05  0:01     ` Peter Osterlund
@ 2004-07-10 23:20       ` Arnd Bergmann
  2004-07-10 23:27         ` Christoph Hellwig
  0 siblings, 1 reply; 27+ messages in thread
From: Arnd Bergmann @ 2004-07-10 23:20 UTC (permalink / raw)
  To: Peter Osterlund
  Cc: linux-kernel, Christoph Hellwig, Jens Axboe, Andrew Morton

On Montag, 5. Juli 2004 02:01, Peter Osterlund wrote:

> And here is a combined patch consisting of the 7 patches I've posted
> so far, which should make it easier for those who want to test the
> driver. Applies to 2.6.7-bk15 and probably -bk16 and -bk17 too.

I tried the version from -mm7 on my opteron box with a dvd+rw medium
and 32 bit user tools. The success was rather mixed. Besides the patch
below to make it work at all, I noticed that I was not able to rw-mount
/dev/dvd after writing a disk through /dev/pktcdvd0. However, I could
actually remount,rw /dev/dvd after mounting it read-only.

Also, I believe I hit a race running hdparm while writing to a mounted
/dev/pktcdvd0. This resulted in the writing process getting stuck in
wait_on_buffer, but I could not reproduce this behaviour later.

> +static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
> +{

> +	switch (cmd) {
> +	case PACKET_GET_STATS:
> +		if (copy_to_user(&arg, &pd->stats, sizeof(struct packet_stats)))
> +			return -EFAULT;
> +		break;

This clearly doesn't work, since arg is not what copy_to_user expects here.
Even if it worked, this particular interface would still be just wrong and
should be removed.

> +#if PACKET_DEBUG
> +#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
> +#else
> +#define DPRINTK(fmt, args...)
> +#endif
> +
> +#if PACKET_DEBUG > 1
> +#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
> +#else
> +#define VPRINTK(fmt, args...)
> +#endif

These definitions should not be in the pktcdvd header file, since they
conflict with similar definitions in other headers. If you want to keep
them, they should be moved to the .c file.

> +#define PACKET_SETUP_DEV	_IOW(PACKET_IOCTL_MAGIC, 1, unsigned int)
> +#define PACKET_TEARDOWN_DEV	_IOW(PACKET_IOCTL_MAGIC, 2, unsigned int)

These are actually incorrect definitions since the ioctl argument is
not a pointer to unsigned int but instead just an int. However, that's
too late to fix without breaking the existing tools.

	Arnd <><

========
[PATCH] fix packet CD/DVD writing for compat mode

This adds support for 32 bit compatibility mode to the new pktcdvd
ioctl interface. The contained changes are:
- Add ULONG_IOCTL() definitions to <linux/compat_ioctl.h> for the 
  setup/teardown ioctls.
- Remove the PACKET_GET_STATS ioctl. It would require a compat handler
  function to emulate, however it is completely bogus and did not
  work as designed anyway.
- move conflicting DPRINTK/VPRINTK out of the header file so it can
  be included from fs/compat_ioctl.c. 

Signed-off-by: Arnd Bergmann <arnd@arndb.de>

--- 1.1/drivers/block/pktcdvd.c	Sat Jul 10 20:34:47 2004
+++ edited/drivers/block/pktcdvd.c	Sat Jul 10 22:33:51 2004
@@ -52,6 +52,18 @@
 
 #include <asm/uaccess.h>
 
+#if PACKET_DEBUG
+#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+#if PACKET_DEBUG > 1
+#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
+#else
+#define VPRINTK(fmt, args...)
+#endif
+
 #define ZONE(sector, pd) (((sector) + (pd)->offset) & ~((pd)->settings.size - 1))
 
 static struct pktcdvd_device *pkt_devs;
@@ -2475,11 +2487,6 @@
 	}
 
 	switch (cmd) {
-	case PACKET_GET_STATS:
-		if (copy_to_user(&arg, &pd->stats, sizeof(struct packet_stats)))
-			return -EFAULT;
-		break;
-
 	case PACKET_SETUP_DEV:
 		if (pd->dev) {
 			printk("pktcdvd: dev already setup\n");
===== fs/compat_ioctl.c 1.37 vs edited =====
--- 1.37/fs/compat_ioctl.c	Fri Jul  9 07:40:22 2004
+++ edited/fs/compat_ioctl.c	Sat Jul 10 22:30:15 2004
@@ -68,6 +68,7 @@
 #include <linux/i2c.h>
 #include <linux/i2c-dev.h>
 #include <linux/wireless.h>
+#include <linux/pktcdvd.h>
 
 #include <net/sock.h>          /* siocdevprivate_ioctl */
 #include <net/bluetooth/bluetooth.h>
--- 1.28/include/linux/compat_ioctl.h	Fri Jul  9 07:40:22 2004
+++ edited/include/linux/compat_ioctl.h	Sat Jul 10 22:31:17 2004
@@ -382,6 +382,9 @@
 COMPATIBLE_IOCTL(DVD_READ_STRUCT)
 COMPATIBLE_IOCTL(DVD_WRITE_STRUCT)
 COMPATIBLE_IOCTL(DVD_AUTH)
+/* pktcdvd */
+ULONG_IOCTL(PACKET_SETUP_DEV)
+ULONG_IOCTL(PACKET_TEARDOWN_DEV)
 /* Big L */
 ULONG_IOCTL(LOOP_SET_FD)
 COMPATIBLE_IOCTL(LOOP_CLR_FD)
===== include/linux/pktcdvd.h 1.1 vs edited =====
--- 1.1/include/linux/pktcdvd.h	Sat Jul 10 20:34:51 2004
+++ edited/include/linux/pktcdvd.h	Sat Jul 10 22:34:46 2004
@@ -40,18 +40,6 @@
  * No user-servicable parts beyond this point ->
  */
 
-#if PACKET_DEBUG
-#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
-#else
-#define DPRINTK(fmt, args...)
-#endif
-
-#if PACKET_DEBUG > 1
-#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
-#else
-#define VPRINTK(fmt, args...)
-#endif
-
 /*
  * device types
  */
@@ -98,22 +86,9 @@
 #undef PACKET_USE_LS
 
 /*
- * Very crude stats for now
- */
-struct packet_stats
-{
-	unsigned long		pkt_started;
-	unsigned long		pkt_ended;
-	unsigned long		secs_w;
-	unsigned long		secs_rg;
-	unsigned long		secs_r;
-};
-
-/*
  * packet ioctls
  */
 #define PACKET_IOCTL_MAGIC	('X')
-#define PACKET_GET_STATS	_IOR(PACKET_IOCTL_MAGIC, 0, struct packet_stats)
 #define PACKET_SETUP_DEV	_IOW(PACKET_IOCTL_MAGIC, 1, unsigned int)
 #define PACKET_TEARDOWN_DEV	_IOW(PACKET_IOCTL_MAGIC, 2, unsigned int)
 
@@ -131,6 +106,18 @@
 	__u8			write_type;
 	__u8			track_mode;
 	__u8			block_mode;
+};
+
+/*
+ * Very crude stats for now
+ */
+struct packet_stats
+{
+	unsigned long		pkt_started;
+	unsigned long		pkt_ended;
+	unsigned long		secs_w;
+	unsigned long		secs_rg;
+	unsigned long		secs_r;
 };
 
 struct packet_cdrw

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-10 23:20       ` Arnd Bergmann
@ 2004-07-10 23:27         ` Christoph Hellwig
  2004-07-11  1:06           ` Peter Osterlund
  0 siblings, 1 reply; 27+ messages in thread
From: Christoph Hellwig @ 2004-07-10 23:27 UTC (permalink / raw)
  To: Arnd Bergmann; +Cc: Peter Osterlund, linux-kernel, Jens Axboe, Andrew Morton

On Sun, Jul 11, 2004 at 01:20:45AM +0200, Arnd Bergmann wrote:
> These are actually incorrect definitions since the ioctl argument is
> not a pointer to unsigned int but instead just an int. However, that's
> too late to fix without breaking the existing tools.

The tools need to change anyway to get away from the broken behaviour to
issue in ioctl on the actual block device to bind it..


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-10 23:27         ` Christoph Hellwig
@ 2004-07-11  1:06           ` Peter Osterlund
  2004-07-12 16:25             ` Arnd Bergmann
  2004-07-14  0:06             ` [RFC][PATCH] Control pktcdvd with an auxiliary character device Peter Osterlund
  0 siblings, 2 replies; 27+ messages in thread
From: Peter Osterlund @ 2004-07-11  1:06 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: Arnd Bergmann, linux-kernel, Jens Axboe, Andrew Morton

Christoph Hellwig <hch@infradead.org> writes:

> On Sun, Jul 11, 2004 at 01:20:45AM +0200, Arnd Bergmann wrote:
> > These are actually incorrect definitions since the ioctl argument is
> > not a pointer to unsigned int but instead just an int. However, that's
> > too late to fix without breaking the existing tools.
> 
> The tools need to change anyway to get away from the broken behaviour to
> issue in ioctl on the actual block device to bind it..

OK, I'll create a patch that gets rid of the ioctl interface and uses
an auxiliary character device instead to control device bindings.

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-11  1:06           ` Peter Osterlund
@ 2004-07-12 16:25             ` Arnd Bergmann
  2004-07-12 16:34               ` Christoph Hellwig
  2004-07-14  0:06             ` [RFC][PATCH] Control pktcdvd with an auxiliary character device Peter Osterlund
  1 sibling, 1 reply; 27+ messages in thread
From: Arnd Bergmann @ 2004-07-12 16:25 UTC (permalink / raw)
  To: Peter Osterlund
  Cc: Christoph Hellwig, linux-kernel, Jens Axboe, Andrew Morton

[-- Attachment #1: Type: text/plain, Size: 1027 bytes --]

On Sonntag, 11. Juli 2004 03:06, Peter Osterlund wrote:
> OK, I'll create a patch that gets rid of the ioctl interface and uses
> an auxiliary character device instead to control device bindings.

I don't think that's a good idea either. It will solve the udev problem,
but it essentially means you're sneaking in system calls through the
back door here.

Maybe we can decide on one of the following approaches (or yet another
one):

- Do the ioctl call on the cdrom device through cdrom_ioctl() instead of
  the pktcdvd driver. You need to hook into the cdrom driver subsystem,
  but leave everything else as it is today.
- Register all cdrom devices with pktcdvd as soon as they are detected.
  This needs some more changes to the cdrom subsystem, but gets rid of
  the need for the pktsetup interface because you can do the setup stuff
  at open time.
- Merge pktcdvd completely into the cdrom subsystem so the existing cdrom
  device node passes everything down to pktcdvd if an RW medium is mounted
  writable.

	Arnd <><

[-- Attachment #2: signature --]
[-- Type: application/pgp-signature, Size: 189 bytes --]

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-12 16:25             ` Arnd Bergmann
@ 2004-07-12 16:34               ` Christoph Hellwig
  2004-07-13  6:04                 ` Jens Axboe
  0 siblings, 1 reply; 27+ messages in thread
From: Christoph Hellwig @ 2004-07-12 16:34 UTC (permalink / raw)
  To: Arnd Bergmann; +Cc: Peter Osterlund, linux-kernel, Jens Axboe, Andrew Morton

> - Merge pktcdvd completely into the cdrom subsystem so the existing cdrom
>   device node passes everything down to pktcdvd if an RW medium is mounted
>   writable.

Well, we had that already earlier on.  I'd prefer that and it seems Jens, too.
But it's a lot of work, and definitly not 2.6 material.


^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [PATCH] CDRW packet writing support for 2.6.7-bk13
  2004-07-12 16:34               ` Christoph Hellwig
@ 2004-07-13  6:04                 ` Jens Axboe
  0 siblings, 0 replies; 27+ messages in thread
From: Jens Axboe @ 2004-07-13  6:04 UTC (permalink / raw)
  To: Christoph Hellwig, Arnd Bergmann, Peter Osterlund, linux-kernel,
	Andrew Morton

On Mon, Jul 12 2004, Christoph Hellwig wrote:
> > - Merge pktcdvd completely into the cdrom subsystem so the existing cdrom
> >   device node passes everything down to pktcdvd if an RW medium is mounted
> >   writable.
> 
> Well, we had that already earlier on.  I'd prefer that and it seems Jens, too.
> But it's a lot of work, and definitly not 2.6 material.

Not really. I'd prefer adding the large block size support to UDF, so
the ide-cd/sr write setup would just automatically work without any
changes in that path. The needed changes would then just be in setup and
closing of the device.

-- 
Jens Axboe


^ permalink raw reply	[flat|nested] 27+ messages in thread

* [RFC][PATCH] Control pktcdvd with an auxiliary character device
  2004-07-11  1:06           ` Peter Osterlund
  2004-07-12 16:25             ` Arnd Bergmann
@ 2004-07-14  0:06             ` Peter Osterlund
  2004-07-14  0:17               ` Peter Osterlund
  1 sibling, 1 reply; 27+ messages in thread
From: Peter Osterlund @ 2004-07-14  0:06 UTC (permalink / raw)
  To: Christoph Hellwig; +Cc: Arnd Bergmann, linux-kernel, Jens Axboe, Andrew Morton

Peter Osterlund <petero2@telia.com> writes:

> Christoph Hellwig <hch@infradead.org> writes:
> 
> > On Sun, Jul 11, 2004 at 01:20:45AM +0200, Arnd Bergmann wrote:
> > > These are actually incorrect definitions since the ioctl argument is
> > > not a pointer to unsigned int but instead just an int. However, that's
> > > too late to fix without breaking the existing tools.
> > 
> > The tools need to change anyway to get away from the broken behaviour to
> > issue in ioctl on the actual block device to bind it..
> 
> OK, I'll create a patch that gets rid of the ioctl interface and uses
> an auxiliary character device instead to control device bindings.

Here is a patch for 2.6.7-mm7 that does that. The driver creates a
misc character device and bind/unbind of the block devices are
controlled by ioctl commands on the char device.

This patch needs corresponding changes in the pktsetup user space
program. I'll post a patch for pktsetup as a separate message.

The driver no longer uses reserved device numbers. Instead, pktsetup
reads /proc/misc to find the pktcdvd char device.

Signed-off-by: Peter Osterlund <petero2@telia.com>
---

 linux-petero/Documentation/cdrom/packet-writing.txt |   18 
 linux-petero/drivers/block/pktcdvd.c                |  588 ++++++++++----------
 linux-petero/include/linux/compat_ioctl.h           |    2 
 linux-petero/include/linux/major.h                  |    2 
 linux-petero/include/linux/pktcdvd.h                |   54 -
 5 files changed, 341 insertions(+), 323 deletions(-)

diff -puN Documentation/cdrom/packet-writing.txt~packet-char-dev Documentation/cdrom/packet-writing.txt
--- linux/Documentation/cdrom/packet-writing.txt~packet-char-dev	2004-07-14 01:02:38.256478896 +0200
+++ linux-petero/Documentation/cdrom/packet-writing.txt	2004-07-14 01:02:38.268477072 +0200
@@ -13,13 +13,11 @@ Getting started quick
   as appropriate):
 	# cdrwtool -d /dev/hdc -q
 
-- Make sure that /dev/pktcdvd0 exists (mknod /dev/pktcdvd0 b 97 0)
-
 - Setup your writer
-	# pktsetup /dev/pktcdvd0 /dev/hdc
+	# pktsetup dev_name /dev/hdc
 
-- Now you can mount /dev/pktcdvd0 and copy files to it. Enjoy!
-	# mount /dev/pktcdvd0 /cdrom -t udf -o rw,noatime
+- Now you can mount /dev/pktcdvd/dev_name and copy files to it. Enjoy!
+	# mount /dev/pktcdvd/dev_name /cdrom -t udf -o rw,noatime
 
 
 Packet writing for DVD-RW media
@@ -33,8 +31,8 @@ overwrite mode, run:
 
 You can then use the disc the same way you would use a CD-RW disc:
 
-	# pktsetup /dev/pktcdvd0 /dev/hdc
-	# mount /dev/pktcdvd0 /cdrom -t udf -o rw,noatime
+	# pktsetup dev_name /dev/hdc
+	# mount /dev/pktcdvd/dev_name /cdrom -t udf -o rw,noatime
 
 
 Packet writing for DVD+RW media
@@ -56,9 +54,9 @@ writes are not 32KB aligned.
 Both problems can be solved by using the pktcdvd driver, which always
 generates aligned writes.
 
-	# pktsetup /dev/pktcdvd0 /dev/hdc
-	# mkudffs /dev/pktcdvd0
-	# mount /dev/pktcdvd0 /cdrom -t udf -o rw,noatime
+	# pktsetup dev_name /dev/hdc
+	# mkudffs /dev/pktcdvd/dev_name
+	# mount /dev/pktcdvd/dev_name /cdrom -t udf -o rw,noatime
 
 
 Notes
@@ -76,7 +74,7 @@ Notes
   device, you can put any filesystem you like on the disc. For
   example, run:
 
-	# /sbin/mke2fs /dev/pktcdvd0
+	# /sbin/mke2fs /dev/pktcdvd/dev_name
 
   to create an ext2 filesystem on the disc.
 
diff -puN drivers/block/pktcdvd.c~packet-char-dev drivers/block/pktcdvd.c
--- linux/drivers/block/pktcdvd.c~packet-char-dev	2004-07-14 01:02:38.246480416 +0200
+++ linux-petero/drivers/block/pktcdvd.c	2004-07-14 01:02:38.266477376 +0200
@@ -31,7 +31,7 @@
  *
  *************************************************************************/
 
-#define VERSION_CODE	"v0.1.6a 2004-07-01 Jens Axboe (axboe@suse.de) and petero2@telia.com"
+#define VERSION_CODE	"v0.2.0a 2004-07-14 Jens Axboe (axboe@suse.de) and petero2@telia.com"
 
 #include <linux/pktcdvd.h>
 #include <linux/config.h>
@@ -44,26 +44,43 @@
 #include <linux/file.h>
 #include <linux/proc_fs.h>
 #include <linux/seq_file.h>
-#include <linux/buffer_head.h>		/* for invalidate_bdev() */
-#include <linux/devfs_fs_kernel.h>
 #include <linux/suspend.h>
+#include <linux/miscdevice.h>
 #include <scsi/scsi_cmnd.h>
 #include <scsi/scsi_ioctl.h>
 
 #include <asm/uaccess.h>
 
+#if PACKET_DEBUG
+#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
+#else
+#define DPRINTK(fmt, args...)
+#endif
+
+#if PACKET_DEBUG > 1
+#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
+#else
+#define VPRINTK(fmt, args...)
+#endif
+
 #define ZONE(sector, pd) (((sector) + (pd)->offset) & ~((pd)->settings.size - 1))
 
-static struct pktcdvd_device *pkt_devs;
+
+static struct pktcdvd_device *pkt_devs[MAX_WRITERS];
 static struct proc_dir_entry *pkt_proc;
+static int pkt_major;
+static struct semaphore ctl_mutex;	/* Serialize open/close/setup/teardown */
+
 
 static struct pktcdvd_device *pkt_find_dev(request_queue_t *q)
 {
 	int i;
 
-	for (i = 0; i < MAX_WRITERS; i++)
-		if (pkt_devs[i].bdev && bdev_get_queue(pkt_devs[i].bdev) == q)
-			return &pkt_devs[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;
 }
@@ -315,11 +332,6 @@ static int pkt_generic_packet(struct pkt
 	DECLARE_COMPLETION(wait);
 	int err = 0;
 
-	if (!pd->bdev) {
-		printk("pkt_generic_packet: no bdev\n");
-		return -ENXIO;
-	}
-
 	q = bdev_get_queue(pd->bdev);
 
 	rq = blk_get_request(q, (cgc->data_direction == CGC_DATA_WRITE) ? WRITE : READ,
@@ -481,8 +493,6 @@ static void pkt_iosched_process_queue(st
 		return;
 	atomic_set(&pd->iosched.attention, 0);
 
-	if (!pd->bdev)
-		return;
 	q = bdev_get_queue(pd->bdev);
 
 	for (;;) {
@@ -1209,11 +1219,7 @@ static int kcdrwd(void *foobar)
 					min_sleep_time = pkt->sleep_time;
 			}
 
-			if (pd->bdev) {
-				request_queue_t *q;
-				q = bdev_get_queue(pd->bdev);
-				generic_unplug_device(q);
-			}
+			generic_unplug_device(bdev_get_queue(pd->bdev));
 
 			VPRINTK("kcdrwd: sleeping\n");
 			residue = schedule_timeout(min_sleep_time);
@@ -1894,23 +1900,18 @@ static int pkt_open_dev(struct pktcdvd_d
 	int i;
 	char b[BDEVNAME_SIZE];
 
-	if (!pd->dev)
-		return -ENXIO;
-
-	pd->bdev = bdget(pd->dev);
-	if (!pd->bdev) {
-		printk("pktcdvd: can't find cdrom block device\n");
-		return -ENXIO;
-	}
-
-	if ((ret = blkdev_get(pd->bdev, FMODE_READ, 0))) {
-		pd->bdev = NULL;
-		return ret;
-	}
+	/*
+	 * We need to re-open the cdrom device without O_NONBLOCK to be able
+	 * to read/write from/to it. It is already opened in O_NONBLOCK mode
+	 * so bdget() can't fail.
+	 */
+	bdget(pd->bdev->bd_dev);
+	if ((ret = blkdev_get(pd->bdev, FMODE_READ, O_RDONLY)))
+		goto out;
 
 	if ((ret = pkt_get_last_written(pd, &lba))) {
 		printk("pktcdvd: pkt_get_last_written failed\n");
-		return ret;
+		goto out_putdev;
 	}
 
 	set_capacity(pd->disk, lba << 2);
@@ -1923,11 +1924,12 @@ static int pkt_open_dev(struct pktcdvd_d
 	 */
 	q = bdev_get_queue(pd->bdev);
 	for (i = 0; i < MAX_WRITERS; i++) {
-		if (pd == &pkt_devs[i])
+		if (pd == pkt_devs[i])
 			continue;
-		if (pkt_devs[i].bdev && bdev_get_queue(pkt_devs[i].bdev) == q) {
+		if (pkt_devs[i] && bdev_get_queue(pkt_devs[i]->bdev) == q) {
 			printk("pktcdvd: %s request queue busy\n", bdevname(pd->bdev, b));
-			return -EBUSY;
+			ret = -EBUSY;
+			goto out_putdev;
 		}
 	}
 	spin_lock_irq(q->queue_lock);
@@ -1962,6 +1964,9 @@ restore_queue:
 	q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
 	q->merge_requests_fn = pd->cdrw.merge_requests_fn;
 	spin_unlock_irq(q->queue_lock);
+out_putdev:
+	blkdev_put(pd->bdev);
+out:
 	return ret;
 }
 
@@ -1971,117 +1976,80 @@ restore_queue:
  */
 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);
 
-	if (pd->bdev) {
-		request_queue_t *q = bdev_get_queue(pd->bdev);
-		pkt_set_speed(pd, 0xffff, 0xffff);
-		spin_lock_irq(q->queue_lock);
-		q->elevator.elevator_merge_fn = pd->cdrw.elv_merge_fn;
-		q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
-		q->merge_requests_fn = pd->cdrw.merge_requests_fn;
-		spin_unlock_irq(q->queue_lock);
-		blkdev_put(pd->bdev);
-		pd->bdev = NULL;
-	}
+	q = bdev_get_queue(pd->bdev);
+	pkt_set_speed(pd, 0xffff, 0xffff);
+	spin_lock_irq(q->queue_lock);
+	q->elevator.elevator_merge_fn = pd->cdrw.elv_merge_fn;
+	q->elevator.elevator_completed_req_fn = pd->cdrw.elv_completed_req_fn;
+	q->merge_requests_fn = pd->cdrw.merge_requests_fn;
+	spin_unlock_irq(q->queue_lock);
+	blkdev_put(pd->bdev);
+}
+
+static struct pktcdvd_device *pkt_find_dev_from_minor(int dev_minor)
+{
+	if (dev_minor >= MAX_WRITERS)
+		return NULL;
+	return pkt_devs[dev_minor];
 }
 
 static int pkt_open(struct inode *inode, struct file *file)
 {
 	struct pktcdvd_device *pd = NULL;
 	int ret;
-	int special_open, exclusive;
 
 	VPRINTK("pktcdvd: entering open\n");
 
-	if (iminor(inode) >= MAX_WRITERS) {
-		printk("pktcdvd: max %d writers supported\n", MAX_WRITERS);
+	down(&ctl_mutex);
+	pd = pkt_find_dev_from_minor(iminor(inode));
+	if (!pd) {
 		ret = -ENODEV;
 		goto out;
 	}
+	BUG_ON(pd->refcnt < 0);
 
-	special_open = 0;
-	if (((file->f_flags & O_ACCMODE) == O_RDONLY) && (file->f_flags & O_CREAT))
-		special_open = 1;
-
-	exclusive = 0;
-	if ((file->f_mode & FMODE_WRITE) || special_open)
-		exclusive = 1;
-
-	/*
-	 * either device is not configured, or pktsetup is old and doesn't
-	 * use O_CREAT to create device
-	 */
-	pd = &pkt_devs[iminor(inode)];
-	if (!pd->dev && !special_open) {
-		VPRINTK("pktcdvd: not configured and O_CREAT not set\n");
-		ret = -ENXIO;
-		goto out;
-	}
-
-	down(&pd->ctl_mutex);
 	pd->refcnt++;
-	if (pd->refcnt > 1) {
-		if (exclusive) {
-			VPRINTK("pktcdvd: busy open\n");
-			ret = -EBUSY;
-			goto out_dec;
-		}
-
-		/*
-		 * Not first open, everything is already set up
-		 */
-		goto done;
-	}
-
-	if (!special_open) {
+	if (pd->refcnt == 1) {
 		if (pkt_open_dev(pd, file->f_mode & FMODE_WRITE)) {
 			ret = -EIO;
 			goto out_dec;
 		}
+		/*
+		 * needed here as well, since ext2 (among others) may change
+		 * the blocksize at mount time
+		 */
+		set_blocksize(inode->i_bdev, CD_FRAMESIZE);
 	}
 
-	/*
-	 * needed here as well, since ext2 (among others) may change
-	 * the blocksize at mount time
-	 */
-	set_blocksize(inode->i_bdev, CD_FRAMESIZE);
-
-done:
-	up(&pd->ctl_mutex);
+	up(&ctl_mutex);
 	return 0;
 
 out_dec:
 	pd->refcnt--;
-	if (pd->refcnt == 0) {
-		if (pd->bdev) {
-			blkdev_put(pd->bdev);
-			pd->bdev = NULL;
-		}
-	}
-	up(&pd->ctl_mutex);
 out:
 	VPRINTK("pktcdvd: failed open (%d)\n", ret);
+	up(&ctl_mutex);
 	return ret;
 }
 
 static int pkt_close(struct inode *inode, struct file *file)
 {
-	struct pktcdvd_device *pd = &pkt_devs[iminor(inode)];
+	struct pktcdvd_device *pd = pkt_find_dev_from_minor(iminor(inode));
 	int ret = 0;
 
-	down(&pd->ctl_mutex);
+	down(&ctl_mutex);
 	pd->refcnt--;
 	BUG_ON(pd->refcnt < 0);
-	if (pd->refcnt > 0)
-		goto out;
-	if (pd->dev) {
+	if (pd->refcnt == 0) {
 		int flush = test_bit(PACKET_WRITABLE, &pd->flags);
 		pkt_release_dev(pd, flush);
 	}
-out:
-	up(&pd->ctl_mutex);
+	up(&ctl_mutex);
 	return ret;
 }
 
@@ -2099,11 +2067,6 @@ static int pkt_make_request(request_queu
 		goto end_io;
 	}
 
-	if (!pd->dev) {
-		printk("pktcdvd: request received for non-active pd\n");
-		goto end_io;
-	}
-
 	/*
 	 * quick remap a READ
 	 */
@@ -2243,7 +2206,7 @@ end_io:
 
 static int pkt_merge_bvec(request_queue_t *q, struct bio *bio, struct bio_vec *bvec)
 {
-	struct pktcdvd_device *pd = &pkt_devs[MINOR(bio->bi_bdev->bd_dev)];
+	struct pktcdvd_device *pd = q->queuedata;
 	sector_t zone = ZONE(bio->bi_sector, pd);
 	int used = ((bio->bi_sector - zone) << 9) + bio->bi_size;
 	int remaining = (pd->settings.size << 9) - used;
@@ -2279,7 +2242,7 @@ static int pkt_seq_show(struct seq_file 
 	int states[PACKET_NUM_STATES];
 
 	seq_printf(m, "Writer %s mapped to %s:\n", pd->name,
-		   __bdevname(pd->dev, bdev_buf));
+		   bdevname(pd->bdev, bdev_buf));
 
 	seq_printf(m, "\nSettings:\n");
 	seq_printf(m, "\tpacket size:\t\t%dkB\n", pd->settings.size / 2);
@@ -2340,35 +2303,50 @@ static struct file_operations pkt_proc_f
 	.release = single_release
 };
 
-static int pkt_new_dev(struct pktcdvd_device *pd, struct block_device *bdev)
+static int pkt_new_dev(struct pktcdvd_device *pd, dev_t dev)
 {
-	dev_t dev = bdev->bd_dev;
 	int i;
 	int ret = 0;
 	char b[BDEVNAME_SIZE];
 	struct proc_dir_entry *proc;
+	struct block_device *bdev;
 
+	if (pd->pkt_dev == dev) {
+		printk("pktcdvd: Recursive setup not allowed\n");
+		return -EBUSY;
+	}
 	for (i = 0; i < MAX_WRITERS; i++) {
-		if (pkt_devs[i].dev == dev) {
-			printk("pktcdvd: %s already setup\n", bdevname(bdev, b));
+		struct pktcdvd_device *pd2 = pkt_devs[i];
+		if (!pd2)
+			continue;
+		if (pd2->bdev->bd_dev == dev) {
+			printk("pktcdvd: %s already setup\n", bdevname(pd2->bdev, b));
+			return -EBUSY;
+		}
+		if (pd2->pkt_dev == dev) {
+			printk("pktcdvd: Can't chain pktcdvd devices\n");
 			return -EBUSY;
 		}
 	}
 
+	bdev = bdget(dev);
+	if (!bdev)
+		return -ENOMEM;
+	ret = blkdev_get(bdev, FMODE_READ, O_RDONLY | O_NONBLOCK);
+	if (ret)
+		return ret;
+
 	/* This is safe, since we have a reference from open(). */
 	__module_get(THIS_MODULE);
 
-	memset(&pd->stats, 0, sizeof(struct packet_stats));
-
 	if (!pkt_grow_pktlist(pd, CONFIG_CDROM_PKTCDVD_BUFFERS)) {
 		printk("pktcdvd: not enough memory for buffers\n");
 		ret = -ENOMEM;
 		goto out_mem;
 	}
 
+	pd->bdev = bdev;
 	set_blocksize(bdev, CD_FRAMESIZE);
-	pd->dev = dev;
-	BUG_ON(pd->bdev);
 
 	pkt_init_queue(pd);
 
@@ -2390,60 +2368,43 @@ static int pkt_new_dev(struct pktcdvd_de
 out_thread:
 	pkt_shrink_pktlist(pd);
 out_mem:
+	blkdev_put(bdev);
 	/* This is safe: open() is still holding a reference. */
 	module_put(THIS_MODULE);
 	return ret;
 }
 
-/*
- * arg contains file descriptor of CD-ROM device.
- */
-static int pkt_setup_dev(struct pktcdvd_device *pd, unsigned int arg)
+static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
 {
-	struct inode *inode;
-	struct file *file;
-	int ret;
-
-	if ((file = fget(arg)) == NULL) {
-		printk("pktcdvd: bad file descriptor passed\n");
-		return -EBADF;
-	}
-
-	inode = file->f_dentry->d_inode;
-	ret = -ENOTBLK;
-	if (!S_ISBLK(inode->i_mode)) {
-		printk("pktcdvd: device is not a block device (duh)\n");
-		goto out;
-	}
-	ret = -EROFS;
-	if (IS_RDONLY(inode)) {
-		printk("pktcdvd: Can't write to read-only dev\n");
-		goto out;
-	}
-	ret = pkt_new_dev(pd, inode->i_bdev);
-
-out:
-	fput(file);
-	return ret;
-}
+	struct pktcdvd_device *pd = pkt_find_dev_from_minor(iminor(inode));
 
-static int pkt_remove_dev(struct pktcdvd_device *pd, struct block_device *bdev)
-{
-	if (!IS_ERR(pd->cdrw.thread))
-		kthread_stop(pd->cdrw.thread);
+	VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode));
+	BUG_ON(!pd);
 
+	switch (cmd) {
 	/*
-	 * will also invalidate buffers for CD-ROM
+	 * forward selected CDROM ioctls to CD-ROM, for UDF
 	 */
-	invalidate_bdev(bdev, 1);
+	case CDROMMULTISESSION:
+	case CDROMREADTOCENTRY:
+	case CDROM_LAST_WRITTEN:
+	case CDROM_SEND_PACKET:
+	case SCSI_IOCTL_SEND_COMMAND:
+		return ioctl_by_bdev(pd->bdev, cmd, arg);
 
-	pkt_shrink_pktlist(pd);
+	case CDROMEJECT:
+		/*
+		 * The door gets locked when the device is opened, so we
+		 * have to unlock it or else the eject command fails.
+		 */
+		pkt_lock_door(pd, 0);
+		return ioctl_by_bdev(pd->bdev, cmd, arg);
+
+	default:
+		printk("pktcdvd: Unknown ioctl for %s (%x)\n", pd->name, cmd);
+		return -ENOTTY;
+	}
 
-	remove_proc_entry(pd->name, pkt_proc);
-	pd->dev = 0;
-	DPRINTK("pktcdvd: writer %s unmapped\n", pd->name);
-	/* This is safe: open() is still holding a reference. */
-	module_put(THIS_MODULE);
 	return 0;
 }
 
@@ -2462,164 +2423,223 @@ static int pkt_media_changed(struct gend
 	return attached_disk->fops->media_changed(attached_disk);
 }
 
-static int pkt_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+static struct block_device_operations pktcdvd_ops = {
+	.owner =		THIS_MODULE,
+	.open =			pkt_open,
+	.release =		pkt_close,
+	.ioctl =		pkt_ioctl,
+	.media_changed =	pkt_media_changed,
+};
+
+/*
+ * Set up mapping from pktcdvd device to CD-ROM device.
+ */
+static int pkt_setup_dev(struct pkt_ctrl_command *ctrl_cmd)
 {
-	struct pktcdvd_device *pd = &pkt_devs[iminor(inode)];
-	int ret;
+	int idx;
+	int ret = -ENOMEM;
+	struct pktcdvd_device *pd;
+	struct gendisk *disk;
+	dev_t dev = new_decode_dev(ctrl_cmd->dev);
 
-	VPRINTK("pkt_ioctl: cmd %x, dev %d:%d\n", cmd, imajor(inode), iminor(inode));
+	for (idx = 0; idx < MAX_WRITERS; idx++)
+		if (!pkt_devs[idx])
+			break;
+	if (idx == MAX_WRITERS) {
+		printk("pktcdvd: max %d writers supported\n", MAX_WRITERS);
+		return -EBUSY;
+	}
+
+	pd = kmalloc(sizeof(struct pktcdvd_device), GFP_KERNEL);
+	if (!pd)
+		return ret;
+	memset(pd, 0, sizeof(struct pktcdvd_device));
+
+	disk = alloc_disk(1);
+	if (!disk)
+		goto out_mem;
+	pd->disk = disk;
+
+	spin_lock_init(&pd->lock);
+	spin_lock_init(&pd->iosched.lock);
+	sprintf(pd->name, "pktcdvd%d", idx);
+	init_waitqueue_head(&pd->wqueue);
+
+	disk->major = pkt_major;
+	disk->first_minor = idx;
+	disk->fops = &pktcdvd_ops;
+	disk->flags = GENHD_FL_REMOVABLE;
+	sprintf(disk->disk_name, "pktcdvd%d", idx);
+	disk->private_data = pd;
+	disk->queue = blk_alloc_queue(GFP_KERNEL);
+	if (!disk->queue)
+		goto out_mem2;
+
+	pd->pkt_dev = MKDEV(disk->major, disk->first_minor);
+	ret = pkt_new_dev(pd, dev);
+	if (ret)
+		goto out_new_dev;
+
+	add_disk(disk);
+	pkt_devs[idx] = pd;
+	ctrl_cmd->pkt_dev = new_encode_dev(pd->pkt_dev);
+	return 0;
+
+out_new_dev:
+	blk_put_queue(disk->queue);
+out_mem2:
+	put_disk(disk);
+out_mem:
+	kfree(pd);
+	return ret;
+}
+
+/*
+ * Tear down mapping from pktcdvd device to CD-ROM device.
+ */
+static int pkt_remove_dev(struct pkt_ctrl_command *ctrl_cmd)
+{
+	struct pktcdvd_device *pd;
+	int idx;
+	dev_t pkt_dev = new_decode_dev(ctrl_cmd->pkt_dev);
 
-	if ((cmd != PACKET_SETUP_DEV) && !pd->dev) {
+	for (idx = 0; idx < MAX_WRITERS; idx++) {
+		pd = pkt_devs[idx];
+		if (pd && (pd->pkt_dev == pkt_dev))
+			break;
+	}
+	if (idx == MAX_WRITERS) {
 		DPRINTK("pktcdvd: dev not setup\n");
 		return -ENXIO;
 	}
 
-	switch (cmd) {
-	case PACKET_GET_STATS:
-		if (copy_to_user(&arg, &pd->stats, sizeof(struct packet_stats)))
-			return -EFAULT;
-		break;
+	if (pd->refcnt > 0)
+		return -EBUSY;
 
-	case PACKET_SETUP_DEV:
-		if (pd->dev) {
-			printk("pktcdvd: dev already setup\n");
-			return -EBUSY;
-		}
-		if (!capable(CAP_SYS_ADMIN))
-			return -EPERM;
-		return pkt_setup_dev(pd, arg);
+	if (!IS_ERR(pd->cdrw.thread))
+		kthread_stop(pd->cdrw.thread);
 
-	case PACKET_TEARDOWN_DEV:
-		if (!capable(CAP_SYS_ADMIN))
-			return -EPERM;
-		down(&pd->ctl_mutex);
-		BUG_ON(pd->refcnt < 1);
-		if (pd->refcnt != 1)
-			ret = -EBUSY;
-		else
-			ret = pkt_remove_dev(pd, inode->i_bdev);
-		up(&pd->ctl_mutex);
-		return ret;
+	blkdev_put(pd->bdev);
 
-	/*
-	 * forward selected CDROM ioctls to CD-ROM, for UDF
-	 */
-	case CDROMMULTISESSION:
-	case CDROMREADTOCENTRY:
-	case CDROM_LAST_WRITTEN:
-	case CDROM_SEND_PACKET:
-	case SCSI_IOCTL_SEND_COMMAND:
-		if (!pd->bdev)
-			return -ENXIO;
-		return ioctl_by_bdev(pd->bdev, cmd, arg);
+	pkt_shrink_pktlist(pd);
 
-	case CDROMEJECT:
-		if (!pd->bdev)
-			return -ENXIO;
-		/*
-		 * The door gets locked when the device is opened, so we
-		 * have to unlock it or else the eject command fails.
-		 */
-		pkt_lock_door(pd, 0);
-		return ioctl_by_bdev(pd->bdev, cmd, arg);
+	remove_proc_entry(pd->name, pkt_proc);
+	DPRINTK("pktcdvd: writer %s unmapped\n", pd->name);
+
+	del_gendisk(pd->disk);
+	blk_put_queue(pd->disk->queue);
+	put_disk(pd->disk);
+
+	pkt_devs[idx] = NULL;
+	kfree(pd);
+
+	/* This is safe: open() is still holding a reference. */
+	module_put(THIS_MODULE);
+	return 0;
+}
+
+static void pkt_get_status(struct pkt_ctrl_command *ctrl_cmd)
+{
+	struct pktcdvd_device *pd = pkt_find_dev_from_minor(ctrl_cmd->dev_index);
+	if (pd) {
+		ctrl_cmd->dev = new_encode_dev(pd->bdev->bd_dev);
+		ctrl_cmd->pkt_dev = new_encode_dev(pd->pkt_dev);
+	} else {
+		ctrl_cmd->dev = 0;
+		ctrl_cmd->pkt_dev = 0;
+	}
+	ctrl_cmd->num_devices = MAX_WRITERS;
+}
+
+static int pkt_ctl_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
+{
+	void __user *argp = (void __user *)arg;
+	struct pkt_ctrl_command ctrl_cmd;
+	int ret = 0;
+
+	if (cmd != PACKET_CTRL_CMD)
+		return -ENOTTY;
 
+	if (copy_from_user(&ctrl_cmd, argp, sizeof(struct pkt_ctrl_command)))
+		return -EFAULT;
+
+	switch (ctrl_cmd.command) {
+	case PKT_CTRL_CMD_SETUP:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		down(&ctl_mutex);
+		ret = pkt_setup_dev(&ctrl_cmd);
+		up(&ctl_mutex);
+		break;
+	case PKT_CTRL_CMD_TEARDOWN:
+		if (!capable(CAP_SYS_ADMIN))
+			return -EPERM;
+		down(&ctl_mutex);
+		ret = pkt_remove_dev(&ctrl_cmd);
+		up(&ctl_mutex);
+		break;
+	case PKT_CTRL_CMD_STATUS:
+		down(&ctl_mutex);
+		pkt_get_status(&ctrl_cmd);
+		up(&ctl_mutex);
+		break;
 	default:
-		printk("pktcdvd: Unknown ioctl for %s (%x)\n", pd->name, cmd);
 		return -ENOTTY;
 	}
 
-	return 0;
+	if (copy_to_user(argp, &ctrl_cmd, sizeof(struct pkt_ctrl_command)))
+		return -EFAULT;
+	return ret;
 }
 
-static struct block_device_operations pktcdvd_ops = {
-	.owner =		THIS_MODULE,
-	.open =			pkt_open,
-	.release =		pkt_close,
-	.ioctl =		pkt_ioctl,
-	.media_changed =	pkt_media_changed,
+
+static struct file_operations pkt_ctl_fops = {
+	.ioctl	 = pkt_ctl_ioctl,
+	.owner	 = THIS_MODULE,
+};
+
+static struct miscdevice pkt_misc = {
+	.minor 		= MISC_DYNAMIC_MINOR,
+	.name  		= "pktcdvd",
+	.devfs_name 	= "pktcdvd/control",
+	.fops  		= &pkt_ctl_fops
 };
 
 int pkt_init(void)
 {
-	int i;
+	int ret;
 
-	devfs_mk_dir("pktcdvd");
-	if (register_blkdev(PACKET_MAJOR, "pktcdvd")) {
-		printk("unable to register pktcdvd device\n");
-		return -EIO;
+	ret = register_blkdev(pkt_major, "pktcdvd");
+	if (ret < 0) {
+		printk("pktcdvd: Unable to register block device\n");
+		return ret;
 	}
+	if (!pkt_major)
+		pkt_major = ret;
 
-	pkt_devs = kmalloc(MAX_WRITERS * sizeof(struct pktcdvd_device), GFP_KERNEL);
-	if (pkt_devs == NULL)
-		goto out_mem;
-	memset(pkt_devs, 0, MAX_WRITERS * sizeof(struct pktcdvd_device));
-
-	for (i = 0; i < MAX_WRITERS; i++) {
-		struct pktcdvd_device *pd = &pkt_devs[i];
-
-		pd->disk = alloc_disk(1);
-		if (!pd->disk)
-			goto out_mem2;
+	ret = misc_register(&pkt_misc);
+	if (ret) {
+		printk("pktcdvd: Unable to register misc device\n");
+		goto out;
 	}
 
-	for (i = 0; i < MAX_WRITERS; i++) {
-		struct pktcdvd_device *pd = &pkt_devs[i];
-		struct gendisk *disk = pd->disk;
-
-		spin_lock_init(&pd->lock);
-		spin_lock_init(&pd->iosched.lock);
-		sprintf(pd->name, "pktcdvd%d", i);
-		init_waitqueue_head(&pd->wqueue);
-		init_MUTEX(&pd->ctl_mutex);
-
-		disk->major = PACKET_MAJOR;
-		disk->first_minor = i;
-		disk->fops = &pktcdvd_ops;
-		disk->flags = GENHD_FL_REMOVABLE;
-		sprintf(disk->disk_name, "pktcdvd%d", i);
-		sprintf(disk->devfs_name, "pktcdvd/%d", i);
-		disk->private_data = pd;
-		disk->queue = blk_alloc_queue(GFP_KERNEL);
-		if (!disk->queue)
-			goto out_mem3;
-		add_disk(disk);
-	}
+	init_MUTEX(&ctl_mutex);
 
 	pkt_proc = proc_mkdir("pktcdvd", proc_root_driver);
 
 	DPRINTK("pktcdvd: %s\n", VERSION_CODE);
 	return 0;
 
-out_mem3:
-	while (i--)
-		blk_put_queue(pkt_devs[i].disk->queue);
-	i = MAX_WRITERS;
-out_mem2:
-	while (i--)
-		put_disk(pkt_devs[i].disk);
-	kfree(pkt_devs);
-out_mem:
-	printk("pktcdvd: out of memory\n");
-	devfs_remove("pktcdvd");
-	unregister_blkdev(PACKET_MAJOR, "pktcdvd");
-	return -ENOMEM;
+out:
+	unregister_blkdev(pkt_major, "pktcdvd");
+	return ret;
 }
 
 void pkt_exit(void)
 {
-	int i;
-	for (i = 0; i < MAX_WRITERS; i++) {
-		struct gendisk *disk = pkt_devs[i].disk;
-		del_gendisk(disk);
-		blk_put_queue(disk->queue);
-		put_disk(disk);
-	}
-
-	devfs_remove("pktcdvd");
-	unregister_blkdev(PACKET_MAJOR, "pktcdvd");
-
 	remove_proc_entry("pktcdvd", proc_root_driver);
-	kfree(pkt_devs);
+	misc_deregister(&pkt_misc);
+	unregister_blkdev(pkt_major, "pktcdvd");
 }
 
 MODULE_DESCRIPTION("Packet writing layer for CD/DVD drives");
@@ -2628,5 +2648,3 @@ MODULE_LICENSE("GPL");
 
 module_init(pkt_init);
 module_exit(pkt_exit);
-
-MODULE_ALIAS_BLOCKDEV_MAJOR(PACKET_MAJOR);
diff -puN include/linux/compat_ioctl.h~packet-char-dev include/linux/compat_ioctl.h
--- linux/include/linux/compat_ioctl.h~packet-char-dev	2004-07-14 01:02:38.251479656 +0200
+++ linux-petero/include/linux/compat_ioctl.h	2004-07-14 01:02:38.267477224 +0200
@@ -382,6 +382,8 @@ COMPATIBLE_IOCTL(CDROMREADALL)
 COMPATIBLE_IOCTL(DVD_READ_STRUCT)
 COMPATIBLE_IOCTL(DVD_WRITE_STRUCT)
 COMPATIBLE_IOCTL(DVD_AUTH)
+/* pktcdvd */
+COMPATIBLE_IOCTL(PACKET_CTRL_CMD)
 /* Big L */
 ULONG_IOCTL(LOOP_SET_FD)
 COMPATIBLE_IOCTL(LOOP_CLR_FD)
diff -puN include/linux/major.h~packet-char-dev include/linux/major.h
--- linux/include/linux/major.h~packet-char-dev	2004-07-14 01:02:38.258478592 +0200
+++ linux-petero/include/linux/major.h	2004-07-14 01:02:38.269476920 +0200
@@ -111,8 +111,6 @@
 
 #define MDISK_MAJOR		95
 
-#define PACKET_MAJOR		97
-
 #define UBD_MAJOR		98
 
 #define JSFD_MAJOR		99
diff -puN include/linux/pktcdvd.h~packet-char-dev include/linux/pktcdvd.h
--- linux/include/linux/pktcdvd.h~packet-char-dev	2004-07-14 01:02:38.253479352 +0200
+++ linux-petero/include/linux/pktcdvd.h	2004-07-14 01:02:38.268477072 +0200
@@ -12,6 +12,8 @@
 #ifndef __PKTCDVD_H
 #define __PKTCDVD_H
 
+#include <linux/types.h>
+
 /*
  * 1 for normal debug messages, 2 is very verbose. 0 to turn it off.
  */
@@ -40,18 +42,6 @@
  * No user-servicable parts beyond this point ->
  */
 
-#if PACKET_DEBUG
-#define DPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
-#else
-#define DPRINTK(fmt, args...)
-#endif
-
-#if PACKET_DEBUG > 1
-#define VPRINTK(fmt, args...) printk(KERN_NOTICE fmt, ##args)
-#else
-#define VPRINTK(fmt, args...)
-#endif
-
 /*
  * device types
  */
@@ -97,25 +87,24 @@
 
 #undef PACKET_USE_LS
 
-/*
- * Very crude stats for now
- */
-struct packet_stats
-{
-	unsigned long		pkt_started;
-	unsigned long		pkt_ended;
-	unsigned long		secs_w;
-	unsigned long		secs_rg;
-	unsigned long		secs_r;
+#define PKT_CTRL_CMD_SETUP	0
+#define PKT_CTRL_CMD_TEARDOWN	1
+#define PKT_CTRL_CMD_STATUS	2
+
+struct pkt_ctrl_command {
+	__u32 command;				/* in: Setup, teardown, status */
+	__u32 dev_index;			/* in/out: Device index */
+	__u32 dev;				/* in/out: Device nr for cdrw device */
+	__u32 pkt_dev;				/* in/out: Device nr for packet device */
+	__u32 num_devices;			/* out: Largest device index + 1 */
+	__u32 padding;				/* Not used */
 };
 
 /*
  * packet ioctls
  */
 #define PACKET_IOCTL_MAGIC	('X')
-#define PACKET_GET_STATS	_IOR(PACKET_IOCTL_MAGIC, 0, struct packet_stats)
-#define PACKET_SETUP_DEV	_IOW(PACKET_IOCTL_MAGIC, 1, unsigned int)
-#define PACKET_TEARDOWN_DEV	_IOW(PACKET_IOCTL_MAGIC, 2, unsigned int)
+#define PACKET_CTRL_CMD		_IOWR(PACKET_IOCTL_MAGIC, 1, struct pkt_ctrl_command)
 
 #ifdef __KERNEL__
 #include <linux/blkdev.h>
@@ -133,6 +122,18 @@ struct packet_settings
 	__u8			block_mode;
 };
 
+/*
+ * Very crude stats for now
+ */
+struct packet_stats
+{
+	unsigned long		pkt_started;
+	unsigned long		pkt_ended;
+	unsigned long		secs_w;
+	unsigned long		secs_rg;
+	unsigned long		secs_r;
+};
+
 struct packet_cdrw
 {
 	struct list_head	pkt_free_list;
@@ -229,11 +230,10 @@ struct packet_data
 struct pktcdvd_device
 {
 	struct block_device	*bdev;		/* dev attached */
-	dev_t			dev;		/* dev attached */
+	dev_t			pkt_dev;	/* our dev */
 	char			name[20];
 	struct packet_settings	settings;
 	struct packet_stats	stats;
-	struct semaphore	ctl_mutex;	/* Serialize access to refcnt */
 	int			refcnt;		/* Open count */
 	__u8			write_speed;	/* current write speed */
 	__u8			read_speed;	/* current read speed */
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

* Re: [RFC][PATCH] Control pktcdvd with an auxiliary character device
  2004-07-14  0:06             ` [RFC][PATCH] Control pktcdvd with an auxiliary character device Peter Osterlund
@ 2004-07-14  0:17               ` Peter Osterlund
  0 siblings, 0 replies; 27+ messages in thread
From: Peter Osterlund @ 2004-07-14  0:17 UTC (permalink / raw)
  To: linux-kernel; +Cc: Christoph Hellwig, Arnd Bergmann, Jens Axboe, Andrew Morton

Peter Osterlund <petero2@telia.com> writes:

> Peter Osterlund <petero2@telia.com> writes:
> 
> > Christoph Hellwig <hch@infradead.org> writes:
> > 
> > > On Sun, Jul 11, 2004 at 01:20:45AM +0200, Arnd Bergmann wrote:
> > > > These are actually incorrect definitions since the ioctl argument is
> > > > not a pointer to unsigned int but instead just an int. However, that's
> > > > too late to fix without breaking the existing tools.
> > > 
> > > The tools need to change anyway to get away from the broken behaviour to
> > > issue in ioctl on the actual block device to bind it..
> > 
> > OK, I'll create a patch that gets rid of the ioctl interface and uses
> > an auxiliary character device instead to control device bindings.
> 
> Here is a patch for 2.6.7-mm7 that does that. The driver creates a
> misc character device and bind/unbind of the block devices are
> controlled by ioctl commands on the char device.
> 
> This patch needs corresponding changes in the pktsetup user space
> program. I'll post a patch for pktsetup as a separate message.

And here is a patch for udftools-1.0.0b3 that updates the pktsetup
program to make it able to use the character device for block device
setup/teardown.

---

 udftools-1.0.0b3-petero/pktsetup/pktsetup.c |  245 +++++++++++++++++++++++++++-
 1 files changed, 238 insertions(+), 7 deletions(-)

diff -puN pktsetup/pktsetup.c~pktsetup-char-dev pktsetup/pktsetup.c
--- udftools-1.0.0b3/pktsetup/pktsetup.c~pktsetup-char-dev	2004-07-12 19:57:51.000000000 +0200
+++ udftools-1.0.0b3-petero/pktsetup/pktsetup.c	2004-07-14 00:34:02.471317888 +0200
@@ -1,5 +1,6 @@
 /*
  * Copyright (c) 1999,2000	Jens Axboe <axboe@suse.de>
+ * Copyright (c) 2004		Peter Osterlund <petero2@telia.com>
  *
  *   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
@@ -19,6 +20,7 @@
 #include <stdio.h>
 #include <fcntl.h>
 #include <sys/ioctl.h>
+#include <sys/stat.h>
 #include <unistd.h>
 #include <getopt.h>
 #include <bits/types.h>
@@ -33,8 +35,33 @@
 #define PACKET_SETUP_DEV	_IOW('X', 1, unsigned int)
 #define PACKET_TEARDOWN_DEV	_IOW('X', 2, unsigned int)
 #endif
+#ifndef PACKET_CTRL_CMD
+#define PACKET_CTRL_CMD		_IOWR('X', 1, struct pkt_ctrl_command)
+#endif
+
+#define MAJOR(dev)      ((dev & 0xfff00) >> 8)
+#define MINOR(dev)      ((dev & 0xff) | ((dev >> 12) & 0xfff00))
+#define MKDEV(ma,mi)    ((mi & 0xff) | (ma << 8) | ((mi & ~0xff) << 12))
+
+#define MISC_MAJOR		10
+#define CTL_DIR "/dev/pktcdvd"
+#define CTL_DEV "control"
+
+#define PKT_CTRL_CMD_SETUP	0
+#define PKT_CTRL_CMD_TEARDOWN	1
+#define PKT_CTRL_CMD_STATUS	2
+
+struct pkt_ctrl_command {
+	__u32 command;				/* in: Setup, teardown, status */
+	__u32 dev_index;			/* in/out: Device index */
+	__u32 dev;				/* in/out: Device nr for cdrw device */
+	__u32 pkt_dev;				/* out: Device nr for packet device */
+	__u32 num_devices;			/* out: Largest device index + 1 */
+	__u32 padding;
+};
+
 
-int init_cdrom(int fd)
+static int init_cdrom(int fd)
 {
 	if (ioctl(fd, CDROM_DRIVE_STATUS, CDSL_CURRENT) < 0) {
 		perror("drive not ready\n");
@@ -54,7 +81,7 @@ int init_cdrom(int fd)
 	return 0;
 }
 
-void setup_dev(char *pkt_device, char *device, int rem)
+static void setup_dev(char *pkt_device, char *device, int rem)
 {
 	int pkt_fd, dev_fd, cmd;
 
@@ -88,29 +115,233 @@ void setup_dev(char *pkt_device, char *d
 	close(pkt_fd);
 }
 
-int usage(void)
+static int usage(void)
 {
-	printf("pktsetup /dev/pktcdvd0 /dev/cdrom\tsetup device\n");
-	printf("pktsetup -d /dev/pktcdvd0\t\ttear down device\n");
+	printf("For pktcdvd < 0.2.0:\n");
+	printf("  pktsetup /dev/pktcdvd0 /dev/cdrom  setup device\n");
+	printf("  pktsetup -d /dev/pktcdvd0          tear down device\n");
+	printf("For pktcdvd >= 0.2.0:\n");
+	printf("  pktsetup dev_name /dev/cdrom       setup device\n");
+	printf("  pktsetup -d dev_name               tear down device\n");
+	printf("  pktsetup -d major:minor            tear down device\n");
+	printf("  pktsetup -s                        show device mappings\n");
 	return 1;
 }
 
+/*
+ * Find the minor device number for the pktcdvd control device.
+ */
+static int get_misc_minor(void)
+{
+	int minor;
+	char name[64];
+	FILE *f;
+
+	if ((f = fopen("/proc/misc", "r")) == NULL)
+		return -1;
+	while (fscanf(f, " %d %64s", &minor, name) == 2) {
+		if (strcmp(name, "pktcdvd") == 0) {
+			fclose(f);
+			return minor;
+		}
+	}
+	fclose(f);
+	return -1;
+}
+
+static const char *pkt_dev_name(const char *dev)
+{
+	static char buf[128];
+	snprintf(buf, sizeof(buf), "%s/%s", CTL_DIR, dev);
+	return buf;
+}
+
+static void create_ctl_dev(void)
+{
+	int misc_minor;
+	struct stat stat_buf;
+	int dev;
+
+	if ((misc_minor = get_misc_minor()) < 0) {
+		system("/sbin/modprobe pktcdvd");
+		misc_minor = get_misc_minor();
+	}
+	if (misc_minor < 0) {
+		fprintf(stderr, "Can't find pktcdvd character device\n");
+		return;
+	}
+	dev = MKDEV(MISC_MAJOR, misc_minor);
+
+	if ((stat(pkt_dev_name(CTL_DEV), &stat_buf) >= 0) &&
+	    S_ISCHR(stat_buf.st_mode) && (stat_buf.st_rdev == dev))
+		return;			    /* Already set up */
+
+	mkdir(CTL_DIR, 0755);
+	unlink(pkt_dev_name(CTL_DEV));
+	mknod(pkt_dev_name(CTL_DEV), S_IFCHR | 0644, dev);
+}
+
+static int remove_stale_dev_node(int ctl_fd, char *devname)
+{
+	struct stat stat_buf;
+	int i, dev;
+	struct pkt_ctrl_command c;
+
+	if (stat(pkt_dev_name(devname), &stat_buf) < 0)
+		return 0;
+	if (!S_ISBLK(stat_buf.st_mode))
+		return 1;
+	dev = stat_buf.st_rdev;
+	memset(&c, 0, sizeof(struct pkt_ctrl_command));
+	for (i = 0; ; i++) {
+		c.command = PKT_CTRL_CMD_STATUS;
+		c.dev_index = i;
+		if (ioctl(ctl_fd, PACKET_CTRL_CMD, &c) < 0) {
+			perror("ioctl");
+			return 1;
+		}
+		if (i >= c.num_devices)
+			break;
+		if (c.pkt_dev == dev)
+			return 1;	    /* busy */
+	}
+	unlink(pkt_dev_name(devname));
+	return 0;
+}
+
+static void setup_dev_chardev(char *pkt_device, char *device, int rem)
+{
+	struct pkt_ctrl_command c;
+	struct stat stat_buf;
+	int ctl_fd, dev_fd;
+
+	memset(&c, 0, sizeof(struct pkt_ctrl_command));
+
+	create_ctl_dev();
+	if ((ctl_fd = open(pkt_dev_name(CTL_DEV), O_RDONLY)) < 0) {
+		perror("ctl open");
+		return;
+	}
+
+	if (!rem) {
+		if ((dev_fd = open(device, O_RDONLY | O_NONBLOCK)) == -1) {
+			perror("open cd-rom");
+			goto out_close;
+		}
+		if (init_cdrom(dev_fd)) {
+			close(dev_fd);
+			goto out_close;
+		}
+		close(dev_fd);
+
+		if (stat(device, &stat_buf) < 0) {
+			perror("stat cd-rom");
+			goto out_close;
+		}
+		if (!S_ISBLK(stat_buf.st_mode)) {
+			fprintf(stderr, "Not a block device\n");
+			goto out_close;
+		}
+		c.command = PKT_CTRL_CMD_SETUP;
+		c.dev = stat_buf.st_rdev;
+
+		if (remove_stale_dev_node(ctl_fd, pkt_device) != 0) {
+			fprintf(stderr, "Device node '%s' already in use\n", pkt_device);
+			goto out_close;
+		}
+		if (ioctl(ctl_fd, PACKET_CTRL_CMD, &c) < 0) {
+			perror("ioctl");
+			goto out_close;
+		}
+		mknod(pkt_dev_name(pkt_device), S_IFBLK | 0640, c.pkt_dev);
+	} else {
+		int major, minor, remove_node;
+
+		if ((stat(pkt_dev_name(pkt_device), &stat_buf) >= 0) &&
+		    S_ISBLK(stat_buf.st_mode)) {
+			major = MAJOR(stat_buf.st_rdev);
+			minor = MINOR(stat_buf.st_rdev);
+			remove_node = 1;
+		} else if (sscanf(pkt_device, "%d:%d", &major, &minor) == 2) {
+			remove_node = 0;
+		} else {
+			fprintf(stderr, "Can't find major/minor numbers\n");
+			goto out_close;
+		}
+
+		c.command = PKT_CTRL_CMD_TEARDOWN;
+		c.pkt_dev = MKDEV(major, minor);
+		if (ioctl(ctl_fd, PACKET_CTRL_CMD, &c) < 0) {
+			perror("ioctl");
+			goto out_close;
+		}
+		if (remove_node)
+			unlink(pkt_dev_name(pkt_device));
+	}
+
+out_close:
+	close(ctl_fd);
+}
+
+static void show_mappings(void)
+{
+	struct pkt_ctrl_command c;
+	int ctl_fd, i;
+
+	memset(&c, 0, sizeof(struct pkt_ctrl_command));
+
+	create_ctl_dev();
+	if ((ctl_fd = open(pkt_dev_name(CTL_DEV), O_RDONLY)) < 0) {
+		perror("ctl open");
+		return;
+	}
+
+	for (i = 0; ; i++) {
+		c.command = PKT_CTRL_CMD_STATUS;
+		c.dev_index = i;
+		if (ioctl(ctl_fd, PACKET_CTRL_CMD, &c) < 0) {
+			perror("ioctl");
+			goto out_close;
+		}
+		if (i >= c.num_devices)
+			break;
+		if (c.dev) {
+			printf("%2d : %d:%d -> %d:%d\n",
+			       i, MAJOR(c.pkt_dev), MINOR(c.pkt_dev),
+			       MAJOR(c.dev), MINOR(c.dev));
+		}
+	}
+
+out_close:
+	close(ctl_fd);
+}
+
 int main(int argc, char **argv)
 {
 	int rem = 0, c;
+	char *pkt_device;
+	char *device;
 
 	if (argc == 1)
 		return usage();
 
-	while ((c = getopt(argc, argv, "d")) != EOF) {
+	while ((c = getopt(argc, argv, "ds?")) != EOF) {
 		switch (c) {
 			case 'd':
 				rem = 1;
 				break;
+			case 's':
+				show_mappings();
+				return 0;
 			default:
 				return usage();
 		}
 	}
-	setup_dev(argv[optind], argv[optind + 1], rem);
+	pkt_device = argv[optind];
+	device = argv[optind + 1];
+	if (strchr(pkt_device, '/'))
+		setup_dev(pkt_device, device, rem);
+	else
+		setup_dev_chardev(pkt_device, device, rem);
 	return 0;
 }
_

-- 
Peter Osterlund - petero2@telia.com
http://w1.894.telia.com/~u89404340

^ permalink raw reply	[flat|nested] 27+ messages in thread

end of thread, other threads:[~2004-07-14  0:17 UTC | newest]

Thread overview: 27+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2004-07-01 13:34 [PATCH] CDRW packet writing support for 2.6.7-bk13 Peter Osterlund
2004-07-02 21:52 ` Peter Osterlund
2004-07-02 22:08   ` Andrew Morton
2004-07-02 22:47     ` Greg KH
2004-07-02 22:59       ` Andrew Morton
2004-07-02 23:24         ` Peter Osterlund
2004-07-02 23:51           ` Andrew Morton
2004-07-04 11:57             ` Peter Osterlund
2004-07-04 20:58               ` Andrew Morton
2004-07-04 21:06                 ` Christoph Hellwig
2004-07-04 12:30 ` [PATCH] Fix race in pktcdvd kernel thread handling Peter Osterlund
2004-07-04 12:37 ` [PATCH] Fix open/close races in pktcdvd Peter Osterlund
2004-07-04 13:05 ` [PATCH] CDRW packet writing support for 2.6.7-bk13 Christoph Hellwig
2004-07-04 23:49   ` Peter Osterlund
2004-07-05  0:01     ` Peter Osterlund
2004-07-10 23:20       ` Arnd Bergmann
2004-07-10 23:27         ` Christoph Hellwig
2004-07-11  1:06           ` Peter Osterlund
2004-07-12 16:25             ` Arnd Bergmann
2004-07-12 16:34               ` Christoph Hellwig
2004-07-13  6:04                 ` Jens Axboe
2004-07-14  0:06             ` [RFC][PATCH] Control pktcdvd with an auxiliary character device Peter Osterlund
2004-07-14  0:17               ` Peter Osterlund
2004-07-05  8:17     ` [PATCH] CDRW packet writing support for 2.6.7-bk13 Jens Axboe
2004-07-05 10:26     ` Christoph Hellwig
2004-07-06  8:45     ` Peter Osterlund
2004-07-07 10:06   ` Peter Osterlund

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).