zero-copy dma cd writing and ripping
Subsystems (7)
PATCH 1/1 zero-copy dma cd writing and ripping
Hi,
2.5 now has the very cool feature that you can write with an ATAPI CD-R device without having to use ide-scsi, but it has one problem at least. ide-cd has historically never used dma transfers for anything but requests originating from a file system. So while generic cd writing works now, it is done in pio. Needless to say, this is suboptimal.
This patch should make 2.5 ide-cd writing even faster than using ide-scsi. Data is read/written directly to/from user space pages, and it is done using dma whenever possible.
Patch also adds support to sd and sr for SG_IO. It's against 2.5.43-BK, please test it. You should grab the latest modified cdrecord and cdda2wav where announced the other day:
*.kernel.org/pub/linux/kernel/people/axboe/tools
If you compile from the tar ball, remember to patch with linus-cdr.diff or it wont work.
I'd appreciate reports on audio extraction with cdda2wav and burning with cdrecord. We need to have this working before 2.6/3.0, or cd writing will simply suck.
===== drivers/block/ll_rw_blk.c 1.122 vs edited =====
drivers/block/ll_rw_blk.c +15 -9
@@ -670,12 +670,10 @@ bit++; }while (bit < __REQ_NR_BITS); -if (rq->flags &REQ_CMD ) -printk ("sector %llu, nr/cnr %lu/%u\n" , (unsigned long long )rq->sector, +printk ("sector %llu, nr/cnr %lu/%u\n" , (unsigned long long )rq->sector, rq->nr_sectors, rq->current_nr_sectors); - -printk ("\n" ); +printk ("bio %p, biotail %p\n" , rq->bio, rq->biotail); }void blk_recount_segments (request_queue_t *q,struct bio *bio)
@@ -1927,7 +1925,7 @@inline void blk_recalc_rq_sectors (struct request *rq,int nsect) { -if (rq->flags &REQ_CMD ) { +if (rq->bio) { rq->hard_sector += nsect; rq->nr_sectors = rq->hard_nr_sectors -= nsect; rq->sector = rq->hard_sector;
@@ -1968,20 +1966,28 @@ req->errors =0 ;if (!uptodate) { -printk ("end_request: I/O error, dev %s, sector %llu\n" , -kdevname (req->rq_dev), (unsigned long long )req->sector); error = -EIO ; +if (!(req->flags &REQ_QUIET )) +printk ("end_request: I/O error, dev %s, sector %llu\n" , +kdevname (req->rq_dev), + (unsigned long long )req->sector); }while ((bio = req->bio)) { -const int nsect =bio_iovec (bio)->bv_len >>9 ; -int new_bio =0 ; +int new_bio =0 , nsect; + +if (unlikely (bio->bi_idx >= bio->bi_vcnt)) { +printk ("%s: bio idx %d >= vcnt %d\n" , __FUNCTION__, + bio->bi_idx, bio->bi_vcnt); +break ; + }BIO_BUG_ON (bio_iovec (bio)->bv_len > bio->bi_size);/* * not a complete bvec done */ + nsect =bio_iovec (bio)->bv_len >>9 ;if (unlikely (nsect > nr_sectors)) {int partial = nr_sectors <<9 ;
===== drivers/block/scsi_ioctl.c 1.12 vs edited =====
drivers/block/scsi_ioctl.c +103 -28
216 lines not on this page · raw message
===== drivers/cdrom/cdrom.c 1.29 vs edited =====
drivers/cdrom/cdrom.c +6 -0
@@ -267,6 +267,7 @@#include <linux/blkpg.h> #include <linux/init.h> #include <linux/fcntl.h> +#include <linux/blkdev.h> #include <asm/uaccess.h>
@@ -1464,6 +1465,11 @@struct cdrom_device_info *cdi =cdrom_find_device (dev);struct cdrom_device_ops *cdo = cdi->ops;int ret; + +/* Try the generic SCSI command ioctl's first.. */ + ret =scsi_cmd_ioctl (ip->i_bdev, cmd, arg); +if (ret != -ENOTTY ) +return ret;/* the first few commands do not deal with audio drive_info, but only with routines in cdrom device operations. */
===== drivers/ide/ide-cd.c 1.25 vs edited =====
drivers/ide/ide-cd.c +72 -36
248 lines not on this page · raw message
===== drivers/md/linear.c 1.20 vs edited =====
drivers/md/linear.c +5 -3
@@ -52,8 +52,7 @@* @bio: the buffer head that's been built up so far * @biovec: the request that could be merged to it. * -* Return 1 if the merge is not permitted (because the -* result would cross a device boundary), 0 otherwise. +* FIXME: return amount we can take at this offset, not a bool */ static int linear_mergeable_bvec (request_queue_t *q,struct bio *bio,struct bio_vec *biovec) {
@@ -64,7 +63,10 @@ dev1 =which_dev (mddev, bio->bi_sector + ((bio->bi_size + biovec->bv_len -1 ) >>9 )); -return dev0 != dev1; +if (dev0 == dev1) +return biovec->bv_len; + +return 0 ; }static int linear_run (mddev_t *mddev)
===== drivers/md/raid0.c 1.18 vs edited =====
drivers/md/raid0.c +2 -3
@@ -168,8 +168,7 @@* @bio: the buffer head that's been built up so far * @biovec: the request that could be merged to it. * -* Return 1 if the merge is not permitted (because the -* result would cross a chunk boundary), 0 otherwise. +* Return amount of bytes we can accept at this offset */ static int raid0_mergeable_bvec (request_queue_t *q,struct bio *bio,struct bio_vec *biovec) {
@@ -182,7 +181,7 @@ block = bio->bi_sector >>1 ; bio_sz = (bio->bi_size + biovec->bv_len) >>10 ; -return chunk_size < ((block & (chunk_size -1 )) + bio_sz); +return chunk_size - ((block & (chunk_size -1 )) + bio_sz); }static int raid0_run (mddev_t *mddev)
===== drivers/scsi/scsi_lib.c 1.34 vs edited =====
drivers/scsi/scsi_lib.c +2 -2
@@ -857,7 +857,7 @@scsi_init_cmd_from_req (SCpnt, SRpnt); } - }else if (req->flags &REQ_CMD ) { + }else if (req->flags & (REQ_CMD |REQ_BLOCK_PC )) { SRpnt =NULL ; STpnt =scsi_get_request_dev (req);if (!STpnt) {
@@ -919,7 +919,7 @@ req =NULL ;spin_unlock_irq (q->queue_lock); -if (SCpnt->request->flags &REQ_CMD ) { +if (SCpnt->request->flags & (REQ_CMD |REQ_BLOCK_PC )) {/* * This will do a couple of things: * 1) Fill in the actual SCSI command.
===== drivers/scsi/scsi_merge.c 1.23 vs edited =====
drivers/scsi/scsi_merge.c +16 -3
@@ -62,16 +62,28 @@int count, gfp_mask;/* -* First we need to know how many scatter gather segments are needed. +* non-sg block request. FIXME: check bouncing for isa hosts! */ - count = req->nr_phys_segments; +if ((req->flags &REQ_BLOCK_PC ) && !req->bio) { +/* +* FIXME: isa bouncing +*/ +if (SCpnt->host->unchecked_isa_dma) + goto fail; + + SCpnt->request_bufflen = req->data_len; + SCpnt->request_buffer = req->data; + req->buffer = req->data; + SCpnt->use_sg =0 ; +return 1 ; + }/* * we used to not use scatter-gather for single segment request, * but now we do (it makes highmem I/O easier to support without * kmapping pages) */ - SCpnt->use_sg = count; + SCpnt->use_sg = req->nr_phys_segments; gfp_mask =GFP_NOIO ;if (in_interrupt ()) {
@@ -111,6 +123,7 @@/* * kill it. there should be no leftover blocks in this request */ +fail: SCpnt =scsi_end_request (SCpnt,0 , req->nr_sectors);BUG_ON (SCpnt);return 0 ;
===== drivers/scsi/sd.c 1.71 vs edited =====
drivers/scsi/sd.c +39 -6
84 lines not on this page · raw message
===== drivers/scsi/sr.c 1.51 vs edited =====
drivers/scsi/sr.c +27 -2
@@ -262,7 +262,7 @@static int sr_init_command (Scsi_Cmnd * SCpnt) { -int dev, devm, block=0 , this_count, s_size; +int dev, devm, block=0 , this_count, s_size, timeout =SR_TIMEOUT ;Scsi_CD *cd; devm =minor (SCpnt->request->rq_dev);
@@ -285,6 +285,30 @@return 0 ; } +/* +* these are already setup, just copy cdb basically +*/ +if (SCpnt->request->flags &REQ_BLOCK_PC ) { +struct request *rq = SCpnt->request; + +if (sizeof (rq->cmd) >sizeof (SCpnt->cmnd)) +return 0 ; + +memcpy (SCpnt->cmnd, rq->cmd,sizeof (SCpnt->cmnd)); +if (rq_data_dir (rq) ==WRITE ) + SCpnt->sc_data_direction =SCSI_DATA_WRITE ; +else if (rq->data_len) + SCpnt->sc_data_direction =SCSI_DATA_READ ; +else + SCpnt->sc_data_direction =SCSI_DATA_NONE ; + + this_count = rq->data_len; +if (rq->timeout) + timeout = rq->timeout; + + goto queue; + } +if (!(SCpnt->request->flags &REQ_CMD )) {blk_dump_rq_flags (SCpnt->request,"sr unsup command" );return 0 ;
@@ -355,11 +379,12 @@* host adapter, it's safe to assume that we can at least transfer * this many bytes between each connect / disconnect. */ +queue: SCpnt->transfersize = cd->device->sector_size; SCpnt->underflow = this_count <<9 ; SCpnt->allowed =MAX_RETRIES ; - SCpnt->timeout_per_command =SR_TIMEOUT ; + SCpnt->timeout_per_command = timeout;/* * This is the completion routine we use. This is matched in terms
===== drivers/scsi/sym53c8xx_2/sym_glue.c 1.8 vs edited =====
drivers/scsi/sym53c8xx_2/sym_glue.c +6 -9
@@ -1793,16 +1793,12 @@hcb_p np =0 ;int retv; -for (host = first_host; host; host = host->next) { -if (host->hostt != first_host->hostt) -continue ; -if (host->host_no == hostno) { - host_data = (struct host_data *) host->hostdata; - np = host_data->ncb; -break ; - } - } + host =scsi_host_hn_get (hostno); +if (!host) +return -EINVAL ; + host_data = (struct host_data *) host->hostdata; + np = host_data->ncb;if (!np)return -EINVAL ;
@@ -1823,6 +1819,7 @@ #endif } +scsi_host_put (host);return retv; }#endif /* SYM_LINUX_PROC_INFO_SUPPORT */
===== fs/bio.c 1.30 vs edited =====
fs/bio.c +118 -11
176 lines not on this page · raw message
===== fs/direct-io.c 1.14 vs edited =====
fs/direct-io.c +1 -1
@@ -417,7 +417,7 @@/* Take a ref against the page each time it is placed into a BIO */ page_cache_get (page); -if (bio_add_page (dio->bio, page, bv_len, bv_offset)) { +if (bio_add_page (dio->bio, page, bv_len, bv_offset) < bv_len) {dio_bio_submit (dio); ret =dio_new_bio (dio, blkno);if (ret ==0 ) {
===== fs/mpage.c 1.25 vs edited =====
fs/mpage.c +6 -2
@@ -176,6 +176,7 @@unsigned first_hole = blocks_per_page;struct block_device *bdev =NULL ;struct buffer_head bh; +int length;if (page_has_buffers (page)) goto confused;
@@ -233,7 +234,8 @@ goto confused; } -if (bio_add_page (bio, page, first_hole << blkbits,0 )) { + length = first_hole << blkbits; +if (bio_add_page (bio, page, length,0 ) < length) { bio =mpage_bio_submit (READ , bio); goto alloc_new; }
@@ -334,6 +336,7 @@int boundary =0 ;sector_t boundary_block =0 ;struct block_device *boundary_bdev =NULL ; +int length;if (page_has_buffers (page)) {struct buffer_head *head =page_buffers (page);
@@ -467,7 +470,8 @@try_to_free_buffers (page); } -if (bio_add_page (bio, page, first_unmapped << blkbits,0 )) { + length = first_unmapped << blkbits; +if (bio_add_page (bio, page, length,0 ) < length) { bio =mpage_bio_submit (WRITE , bio); goto alloc_new; }
===== fs/xfs/pagebuf/page_buf.c 1.15 vs edited =====
fs/xfs/pagebuf/page_buf.c +1 -1
@@ -1448,7 +1448,7 @@if (nbytes > size) nbytes = size; -if (bio_add_page (bio, pb->pb_pages[map_i], nbytes, offset)) +if (bio_add_page (bio, pb->pb_pages[map_i], nbytes, offset) < nbytes)break ; offset =0 ;
===== include/linux/bio.h 1.22 vs edited =====
include/linux/bio.h +3 -0
@@ -131,6 +131,7 @@#define bio_page (bio) bio_iovec((bio))->bv_page#define bio_offset (bio) bio_iovec((bio))->bv_offset#define bio_sectors (bio) ((bio)->bi_size >> 9) +#define bio_cur_sectors (bio) (bio_iovec(bio)->bv_len >> 9)#define bio_data (bio) (page_address(bio_page((bio))) + bio_offset((bio)))#define bio_barrier (bio) ((bio)->bi_rw & (1 << BIO_RW_BARRIER))
@@ -214,6 +215,8 @@extern int bio_add_page (struct bio *,struct page *,unsigned int ,unsigned int );extern int bio_get_nr_vecs (struct block_device *); +extern struct bio *bio_map_user (struct block_device *,unsigned long , +unsigned int ,int );#ifdef CONFIG_HIGHMEM /*
===== include/linux/blkdev.h 1.75 vs edited =====
include/linux/blkdev.h +7 -0
@@ -219,6 +219,12 @@wait_queue_head_t queue_wait;struct blk_queue_tag *queue_tags; + +/* +* sg stuff +*/ +unsigned int sg_timeout; +unsigned int sg_reserved_size; };#define RQ_INACTIVE (-1)
@@ -235,6 +241,7 @@#define blk_queue_tagged (q) test_bit(QUEUE_FLAG_QUEUED, &(q)->queue_flags)#define blk_queue_empty (q) elv_queue_empty(q)#define blk_fs_request (rq) ((rq)->flags & REQ_CMD) +#define blk_pc_request (rq) ((rq)->flags & REQ_BLOCK_PC)#define list_entry_rq (ptr) list_entry((ptr), struct request, queuelist)#define rq_data_dir (rq) ((rq)->flags & 1)
Signature · 1 line
Jens Axboe
This work reminds me that get_user_pages() (or it's callers) need to be doing some flush_dcache_page()
Quoted 3 lines · David S. Miller
This work reminds me that
get_user_pages()(or it's callers) need to be doing someflush_dcache_page()
Was wondering about that. Can you tell me what it needs? And what about bio_unmap_user(), surely that needs to flush cache as well for reads?
Signature · 1 line
Jens Axboe
Quoted 5 lines · Jens Axboe
Quoted 2 lines
This work reminds me that
get_user_pages()(or it's callers) need to be doing someflush_dcache_page()Was wondering about that. Can you tell me what it needs? And what about
bio_unmap_user(), surely that needs to flush cache as well for reads?
Documentation/cachetlb.txt describes where flush_dcache_page is needed. If that doesn't describe it enough for you, that is a bug and please tell me what part is confusing so I may make the document better.
Quoted 10 lines · David S. Miller
Quoted 5 lines · Jens Axboe
Quoted 2 lines
This work reminds me that
get_user_pages()(or it's callers) need to be doing someflush_dcache_page()Was wondering about that. Can you tell me what it needs? And what about
bio_unmap_user(), surely that needs to flush cache as well for reads?
Documentation/cachetlb.txtdescribes whereflush_dcache_pageis needed. If that doesn't describe it enough for you, that is a bug and please tell me what part is confusing so I may make the document better.
Didn't know about that document, will read it first.
Signature · 1 line
Jens Axboe
Quoted 10 lines · David S. Miller
Quoted 5 lines · Jens Axboe
Quoted 2 lines
This work reminds me that
get_user_pages()(or it's callers) need to be doing someflush_dcache_page()Was wondering about that. Can you tell me what it needs? And what about
bio_unmap_user(), surely that needs to flush cache as well for reads?
Documentation/cachetlb.txtdescribes whereflush_dcache_pageis needed. If that doesn't describe it enough for you, that is a bug and please tell me what part is confusing so I may make the document better.
Ok what I make of this is that from bio_map_user() (which does a get_user_pages() I need to do a
if (write_to_vm) flush_dcache_page(page);
and in bio_unmap_user() I do
if (!write_to_vm) flush_dcache_page(page);
is that correct?
Signature · 1 line
Jens Axboe
Quoted 6 lines · Jens Axboe
Ok what I make of this is that from
bio_map_user()(which does aget_user_pages()I need to do aif (write_to_vm) flush_dcache_page(page);
Quoted 6 lines
and in
bio_unmap_user()I doif (!write_to_vm) flush_dcache_page(page);is that correct?
Ho hum, it is tricky :-)))
At bio_map_user() you need to see the user's most recent write to the page if you are going "user --> device". So if "user --> device" bio_map_user() must flush_dcache_page().
I find the write_to_vm condition confusion which is probably why I am sitting here spelling this out :-)
At bio_unmap_user(), if we are going "device --> user" you have to flush_dcache_page(). And actually, this flush could just as legitimately occur at bio_map_user() time.
Therefore, the easiest thing to do is always flush_dcache_page() at bio_map_user().
All the other cases are going to be like this, so we might as well cut to the chase and flush_dcache_page() for all the pages inside of get_user_pages().
Whoever made get_user_pages() and didn't carry over the flush_dcache_page calls from the mechanism it is meant to replace should be spanked :-)
Quoted 12 lines · David S. Miller
Quoted 11 lines · Jens Axboe
Ok what I make of this is that from
bio_map_user()(which does aget_user_pages()I need to do aif (write_to_vm) flush_dcache_page(page);and in
bio_unmap_user()I doif (!write_to_vm) flush_dcache_page(page);
Quoted 7 lines
Quoted 1 line
is that correct?
Ho hum, it is tricky :-)))
At
bio_map_user()you need to see the user's most recent write to the page if you are going "user --> device". So if "user --> device"bio_map_user()mustflush_dcache_page().
Yes, that that is the
if (write_to_vm) flush_dcache_page(page);
Quoted 2 lines
I find the
write_to_vmcondition confusion which is probably why I am sitting here spelling this out :-)
Hehe, actually these are confusing, but write_to_vm is probably the least confusing wording I think. It means we are reading from the device, thus writing to vm pages.
Quoted 3 lines
At
bio_unmap_user(), if we are going "device --> user" you have toflush_dcache_page(). And actually, this flush could just as legitimately occur atbio_map_user()time.
Thus
if (!write_to_vm) flush_dcache_page(page);
Quoted 10 lines
Therefore, the easiest thing to do is always
flush_dcache_page()atbio_map_user().All the other cases are going to be like this, so we might as well cut to the chase and
flush_dcache_page()for all the pages inside ofget_user_pages().Whoever made
get_user_pages()and didn't carry over theflush_dcache_pagecalls from the mechanism it is meant to replace should be spanked :-)
I agree then. I will remove references to flush_dcache_page() in bio_map_user() and bio_unmap_user(), and we'll just unconditionally do flush_dcache_page() on every page mapped in get_user_pages(). That sounds far better to me. Do you really expect users of get_user_pages() to get this right (remember, we are often talking about device drivers :-). I sure am not :)
Signature · 1 line
Jens Axboe
Quoted 6 lines · Jens Axboe
I agree then. I will remove references to
flush_dcache_page()inbio_map_user()andbio_unmap_user(), and we'll just unconditionally doflush_dcache_page()on every page mapped inget_user_pages(). That sounds far better to me. Do you really expect users ofget_user_pages()to get this right (remember, we are often talking about device drivers :-). I sure am not :)
I have no expectations whatsoever :-)
I totally agree with you.
Quoted 10 lines · David S. Miller
Quoted 6 lines · Jens Axboe
I agree then. I will remove references to
flush_dcache_page()inbio_map_user()andbio_unmap_user(), and we'll just unconditionally doflush_dcache_page()on every page mapped inget_user_pages(). That sounds far better to me. Do you really expect users ofget_user_pages()to get this right (remember, we are often talking about device drivers :-). I sure am not :)I have no expectations whatsoever :-)
I totally agree with you.
Good, so it shall be then :-)
A related question. get_user_pages() does page_cache_get() on the page, except if it's reserved. First question is 'why' doesn't it do that on a reserved page? We get this conditional when mapping, and the unmapping needs to check for reserved as well before doing page_cache_release(). Surely the extra reference would be ok to hold for PageReserved pages as well?
And it not, there's a bug if get_user_pages() fails:
if (!PageReserved(pages[i])) page_cache_get(pages[i]);
and if we ever fail:
while (i--) page_cache_release(pages[i]);
Signature · 1 line
Jens Axboe
From: Jens Axboe <[email protected]>
Date: Mon, 28 Oct 2002 17:18:57 +0100
A related question. get_user_pages() does page_cache_get() on the page, except if it's reserved. First question is 'why' doesn't it do that on a reserved page? We get this conditional when mapping, and the unmapping needs to check for reserved as well before doing page_cache_release(). Surely the extra reference would be ok to hold for PageReserved pages as well?
The basic behavior of the whole MM for reserved pages is supposed to be to not do any kind of reference counting, COW'ing, etc. on them.
Any deviation from this behavior is going to lead to problems and can be considered a bug until some later time at which we decide to change this behavior.
Look at how copy_page_range() deals with reserved pages, for example. This logic is all over the VM.