From 7fd097d42b90afadae4867db5d580bcd7b3b596d Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Wed, 26 Mar 2008 12:09:02 +0100 Subject: cdrom: use list_head for cdrom_device_info list Use list_head for cdrom_device_info list instead of opencoded singly list handling. Signed-off-by: Akinobu Mita Signed-off-by: Jens Axboe --- include/linux/cdrom.h | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/cdrom.h b/include/linux/cdrom.h index a5cd2047624e..40e05d0a6e45 100644 --- a/include/linux/cdrom.h +++ b/include/linux/cdrom.h @@ -910,6 +910,7 @@ struct mode_page_header { #ifdef __KERNEL__ #include /* not really needed, later.. */ #include +#include struct packet_command { @@ -934,7 +935,7 @@ struct packet_command /* Uniform cdrom data structures for cdrom.c */ struct cdrom_device_info { struct cdrom_device_ops *ops; /* link to device_ops */ - struct cdrom_device_info *next; /* next device_info for this major */ + struct list_head list; /* linked list of all device_info */ struct gendisk *disk; /* matching block layer disk */ void *handle; /* driver-dependent data */ /* specifications */ -- cgit v1.2.3 From 0a0c4114df4a6903bccb65b06cabb6ddc968f877 Mon Sep 17 00:00:00 2001 From: Akinobu Mita Date: Wed, 26 Mar 2008 12:09:02 +0100 Subject: cdrom: make unregister_cdrom() return void Now unregister_cdrom() always returns 0. Make it return void and update all callers that check the return value. Signed-off-by: Akinobu Mita Cc: Adrian McMenamin Cc: Borislav Petkov Signed-off-by: Jens Axboe --- Documentation/cdrom/cdrom-standard.tex | 2 +- drivers/cdrom/cdrom.c | 3 +-- drivers/cdrom/gdrom.c | 4 +++- drivers/cdrom/viocd.c | 5 +---- drivers/ide/ide-cd.c | 5 ++--- include/linux/cdrom.h | 2 +- 6 files changed, 9 insertions(+), 12 deletions(-) (limited to 'include') diff --git a/Documentation/cdrom/cdrom-standard.tex b/Documentation/cdrom/cdrom-standard.tex index c713aeb020c4..c06233fe52ac 100644 --- a/Documentation/cdrom/cdrom-standard.tex +++ b/Documentation/cdrom/cdrom-standard.tex @@ -777,7 +777,7 @@ Note that a driver must have one static structure, $_dops$, while it may have as many structures $_info$ as there are minor devices active. $Register_cdrom()$ builds a linked list from these. -\subsection{$Int\ unregister_cdrom(struct\ cdrom_device_info * cdi)$} +\subsection{$Void\ unregister_cdrom(struct\ cdrom_device_info * cdi)$} Unregistering device $cdi$ with minor number $MINOR(cdi\to dev)$ removes the minor device from the list. If it was the last registered minor for diff --git a/drivers/cdrom/cdrom.c b/drivers/cdrom/cdrom.c index c4213b7d0b20..663a7f7dc580 100644 --- a/drivers/cdrom/cdrom.c +++ b/drivers/cdrom/cdrom.c @@ -442,7 +442,7 @@ int register_cdrom(struct cdrom_device_info *cdi) } #undef ENSURE -int unregister_cdrom(struct cdrom_device_info *cdi) +void unregister_cdrom(struct cdrom_device_info *cdi) { cdinfo(CD_OPEN, "entering unregister_cdrom\n"); @@ -455,7 +455,6 @@ int unregister_cdrom(struct cdrom_device_info *cdi) cdi->ops->n_minors--; cdinfo(CD_REG_UNREG, "drive \"/dev/%s\" unregistered\n", cdi->name); - return 0; } int cdrom_get_media_event(struct cdrom_device_info *cdi, diff --git a/drivers/cdrom/gdrom.c b/drivers/cdrom/gdrom.c index 4e2bbcccc064..71ec426ecffc 100644 --- a/drivers/cdrom/gdrom.c +++ b/drivers/cdrom/gdrom.c @@ -827,7 +827,9 @@ static int __devexit remove_gdrom(struct platform_device *devptr) del_gendisk(gd.disk); if (gdrom_major) unregister_blkdev(gdrom_major, GDROM_DEV_NAME); - return unregister_cdrom(gd.cd_info); + unregister_cdrom(gd.cd_info); + + return 0; } static struct platform_driver gdrom_driver = { diff --git a/drivers/cdrom/viocd.c b/drivers/cdrom/viocd.c index cac06bc1754b..b74b6c2768a8 100644 --- a/drivers/cdrom/viocd.c +++ b/drivers/cdrom/viocd.c @@ -650,10 +650,7 @@ static int viocd_remove(struct vio_dev *vdev) { struct disk_info *d = &viocd_diskinfo[vdev->unit_address]; - if (unregister_cdrom(&d->viocd_info) != 0) - printk(VIOCD_KERN_WARNING - "Cannot unregister viocd CD-ROM %s!\n", - d->viocd_info.name); + unregister_cdrom(&d->viocd_info); del_gendisk(d->viocd_disk); blk_cleanup_queue(d->viocd_disk->queue); put_disk(d->viocd_disk); diff --git a/drivers/ide/ide-cd.c b/drivers/ide/ide-cd.c index 396000208f81..fe5aefbf8339 100644 --- a/drivers/ide/ide-cd.c +++ b/drivers/ide/ide-cd.c @@ -2032,9 +2032,8 @@ static void ide_cd_release(struct kref *kref) kfree(info->buffer); kfree(info->toc); - if (devinfo->handle == drive && unregister_cdrom(devinfo)) - printk(KERN_ERR "%s: %s failed to unregister device from the cdrom " - "driver.\n", __FUNCTION__, drive->name); + if (devinfo->handle == drive) + unregister_cdrom(devinfo); drive->dsc_overlap = 0; drive->driver_data = NULL; blk_queue_prep_rq(drive->queue, NULL); diff --git a/include/linux/cdrom.h b/include/linux/cdrom.h index 40e05d0a6e45..5db265ea60f6 100644 --- a/include/linux/cdrom.h +++ b/include/linux/cdrom.h @@ -995,7 +995,7 @@ extern int cdrom_ioctl(struct file *file, struct cdrom_device_info *cdi, extern int cdrom_media_changed(struct cdrom_device_info *); extern int register_cdrom(struct cdrom_device_info *cdi); -extern int unregister_cdrom(struct cdrom_device_info *cdi); +extern void unregister_cdrom(struct cdrom_device_info *cdi); typedef struct { int data; -- cgit v1.2.3 From c5dec1c3034f1ae3503efbf641ff3b0273b64797 Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 11 Apr 2008 12:56:49 +0200 Subject: block: convert bio_copy_user to bio_copy_user_iov This patch enables bio_copy_user to take struct sg_iovec (renamed bio_copy_user_iov). bio_copy_user uses bio_copy_user_iov internally as bio_map_user uses bio_map_user_iov. The major changes are: - adds sg_iovec array to struct bio_map_data - adds __bio_copy_iov that copy data between bio and sg_iovec. bio_copy_user_iov and bio_uncopy_user use it. Signed-off-by: FUJITA Tomonori Cc: Tejun Heo Cc: Mike Christie Cc: James Bottomley Signed-off-by: Jens Axboe --- fs/bio.c | 158 ++++++++++++++++++++++++++++++++++++++-------------- include/linux/bio.h | 2 + 2 files changed, 119 insertions(+), 41 deletions(-) (limited to 'include') diff --git a/fs/bio.c b/fs/bio.c index 553b5b7960ad..6e0b6f66df03 100644 --- a/fs/bio.c +++ b/fs/bio.c @@ -444,22 +444,27 @@ int bio_add_page(struct bio *bio, struct page *page, unsigned int len, struct bio_map_data { struct bio_vec *iovecs; - void __user *userptr; + int nr_sgvecs; + struct sg_iovec *sgvecs; }; -static void bio_set_map_data(struct bio_map_data *bmd, struct bio *bio) +static void bio_set_map_data(struct bio_map_data *bmd, struct bio *bio, + struct sg_iovec *iov, int iov_count) { memcpy(bmd->iovecs, bio->bi_io_vec, sizeof(struct bio_vec) * bio->bi_vcnt); + memcpy(bmd->sgvecs, iov, sizeof(struct sg_iovec) * iov_count); + bmd->nr_sgvecs = iov_count; bio->bi_private = bmd; } static void bio_free_map_data(struct bio_map_data *bmd) { kfree(bmd->iovecs); + kfree(bmd->sgvecs); kfree(bmd); } -static struct bio_map_data *bio_alloc_map_data(int nr_segs) +static struct bio_map_data *bio_alloc_map_data(int nr_segs, int iov_count) { struct bio_map_data *bmd = kmalloc(sizeof(*bmd), GFP_KERNEL); @@ -467,13 +472,71 @@ static struct bio_map_data *bio_alloc_map_data(int nr_segs) return NULL; bmd->iovecs = kmalloc(sizeof(struct bio_vec) * nr_segs, GFP_KERNEL); - if (bmd->iovecs) + if (!bmd->iovecs) { + kfree(bmd); + return NULL; + } + + bmd->sgvecs = kmalloc(sizeof(struct sg_iovec) * iov_count, GFP_KERNEL); + if (bmd->sgvecs) return bmd; + kfree(bmd->iovecs); kfree(bmd); return NULL; } +static int __bio_copy_iov(struct bio *bio, struct sg_iovec *iov, int iov_count, + int uncopy) +{ + int ret = 0, i; + struct bio_vec *bvec; + int iov_idx = 0; + unsigned int iov_off = 0; + int read = bio_data_dir(bio) == READ; + + __bio_for_each_segment(bvec, bio, i, 0) { + char *bv_addr = page_address(bvec->bv_page); + unsigned int bv_len = bvec->bv_len; + + while (bv_len && iov_idx < iov_count) { + unsigned int bytes; + char *iov_addr; + + bytes = min_t(unsigned int, + iov[iov_idx].iov_len - iov_off, bv_len); + iov_addr = iov[iov_idx].iov_base + iov_off; + + if (!ret) { + if (!read && !uncopy) + ret = copy_from_user(bv_addr, iov_addr, + bytes); + if (read && uncopy) + ret = copy_to_user(iov_addr, bv_addr, + bytes); + + if (ret) + ret = -EFAULT; + } + + bv_len -= bytes; + bv_addr += bytes; + iov_addr += bytes; + iov_off += bytes; + + if (iov[iov_idx].iov_len == iov_off) { + iov_idx++; + iov_off = 0; + } + } + + if (uncopy) + __free_page(bvec->bv_page); + } + + return ret; +} + /** * bio_uncopy_user - finish previously mapped bio * @bio: bio being terminated @@ -484,55 +547,56 @@ static struct bio_map_data *bio_alloc_map_data(int nr_segs) int bio_uncopy_user(struct bio *bio) { struct bio_map_data *bmd = bio->bi_private; - const int read = bio_data_dir(bio) == READ; - struct bio_vec *bvec; - int i, ret = 0; + int ret; - __bio_for_each_segment(bvec, bio, i, 0) { - char *addr = page_address(bvec->bv_page); - unsigned int len = bmd->iovecs[i].bv_len; + ret = __bio_copy_iov(bio, bmd->sgvecs, bmd->nr_sgvecs, 1); - if (read && !ret && copy_to_user(bmd->userptr, addr, len)) - ret = -EFAULT; - - __free_page(bvec->bv_page); - bmd->userptr += len; - } bio_free_map_data(bmd); bio_put(bio); return ret; } /** - * bio_copy_user - copy user data to bio + * bio_copy_user_iov - copy user data to bio * @q: destination block queue - * @uaddr: start of user address - * @len: length in bytes + * @iov: the iovec. + * @iov_count: number of elements in the iovec * @write_to_vm: bool indicating writing to pages or not * * Prepares and returns a bio for indirect user io, bouncing data * to/from kernel pages as necessary. Must be paired with * call bio_uncopy_user() on io completion. */ -struct bio *bio_copy_user(struct request_queue *q, unsigned long uaddr, - unsigned int len, int write_to_vm) +struct bio *bio_copy_user_iov(struct request_queue *q, struct sg_iovec *iov, + int iov_count, int write_to_vm) { - unsigned long end = (uaddr + len + PAGE_SIZE - 1) >> PAGE_SHIFT; - unsigned long start = uaddr >> PAGE_SHIFT; struct bio_map_data *bmd; struct bio_vec *bvec; struct page *page; struct bio *bio; int i, ret; + int nr_pages = 0; + unsigned int len = 0; - bmd = bio_alloc_map_data(end - start); + for (i = 0; i < iov_count; i++) { + unsigned long uaddr; + unsigned long end; + unsigned long start; + + uaddr = (unsigned long)iov[i].iov_base; + end = (uaddr + iov[i].iov_len + PAGE_SIZE - 1) >> PAGE_SHIFT; + start = uaddr >> PAGE_SHIFT; + + nr_pages += end - start; + len += iov[i].iov_len; + } + + bmd = bio_alloc_map_data(nr_pages, iov_count); if (!bmd) return ERR_PTR(-ENOMEM); - bmd->userptr = (void __user *) uaddr; - ret = -ENOMEM; - bio = bio_alloc(GFP_KERNEL, end - start); + bio = bio_alloc(GFP_KERNEL, nr_pages); if (!bio) goto out_bmd; @@ -564,22 +628,12 @@ struct bio *bio_copy_user(struct request_queue *q, unsigned long uaddr, * success */ if (!write_to_vm) { - char __user *p = (char __user *) uaddr; - - /* - * for a write, copy in data to kernel pages - */ - ret = -EFAULT; - bio_for_each_segment(bvec, bio, i) { - char *addr = page_address(bvec->bv_page); - - if (copy_from_user(addr, p, bvec->bv_len)) - goto cleanup; - p += bvec->bv_len; - } + ret = __bio_copy_iov(bio, iov, iov_count, 0); + if (ret) + goto cleanup; } - bio_set_map_data(bmd, bio); + bio_set_map_data(bmd, bio, iov, iov_count); return bio; cleanup: bio_for_each_segment(bvec, bio, i) @@ -591,6 +645,28 @@ out_bmd: return ERR_PTR(ret); } +/** + * bio_copy_user - copy user data to bio + * @q: destination block queue + * @uaddr: start of user address + * @len: length in bytes + * @write_to_vm: bool indicating writing to pages or not + * + * Prepares and returns a bio for indirect user io, bouncing data + * to/from kernel pages as necessary. Must be paired with + * call bio_uncopy_user() on io completion. + */ +struct bio *bio_copy_user(struct request_queue *q, unsigned long uaddr, + unsigned int len, int write_to_vm) +{ + struct sg_iovec iov; + + iov.iov_base = (void __user *)uaddr; + iov.iov_len = len; + + return bio_copy_user_iov(q, &iov, 1, write_to_vm); +} + static struct bio *__bio_map_user_iov(struct request_queue *q, struct block_device *bdev, struct sg_iovec *iov, int iov_count, diff --git a/include/linux/bio.h b/include/linux/bio.h index 4c59bdccd3ee..d259690863fb 100644 --- a/include/linux/bio.h +++ b/include/linux/bio.h @@ -327,6 +327,8 @@ extern struct bio *bio_map_kern(struct request_queue *, void *, unsigned int, extern void bio_set_pages_dirty(struct bio *bio); extern void bio_check_pages_dirty(struct bio *bio); extern struct bio *bio_copy_user(struct request_queue *, unsigned long, unsigned int, int); +extern struct bio *bio_copy_user_iov(struct request_queue *, struct sg_iovec *, + int, int); extern int bio_uncopy_user(struct bio *); void zero_fill_bio(struct bio *bio); -- cgit v1.2.3 From f18573abcc57844a7c3c12699d40eead8728cd8a Mon Sep 17 00:00:00 2001 From: FUJITA Tomonori Date: Fri, 11 Apr 2008 12:56:52 +0200 Subject: block: move the padding adjustment to blk_rq_map_sg blk_rq_map_user adjusts bi_size of the last bio. It breaks the rule that req->data_len (the true data length) is equal to sum(bio). It broke the scsi command completion code. commit e97a294ef6938512b655b1abf17656cf2b26f709 was introduced to fix the above issue. However, the partial completion code doesn't work with it. The commit is also a layer violation (scsi mid-layer should not know about the block layer's padding). This patch moves the padding adjustment to blk_rq_map_sg (suggested by James). The padding works like the drain buffer. This patch breaks the rule that req->data_len is equal to sum(sg), however, the drain buffer already broke it. So this patch just restores the rule that req->data_len is equal to sub(bio) without breaking anything new. Now when a low level driver needs padding, blk_rq_map_user and blk_rq_map_user_iov guarantee there's enough room for padding. blk_rq_map_sg can safely extend the last entry of a scatter list. blk_rq_map_sg must extend the last entry of a scatter list only for a request that got through bio_copy_user_iov. This patches introduces new REQ_COPY_USER flag. Signed-off-by: FUJITA Tomonori Cc: Tejun Heo Cc: Mike Christie Cc: James Bottomley Signed-off-by: Jens Axboe --- block/blk-map.c | 24 +++++------------------- block/blk-merge.c | 9 +++++++++ drivers/scsi/scsi.c | 2 +- include/linux/blkdev.h | 2 ++ 4 files changed, 17 insertions(+), 20 deletions(-) (limited to 'include') diff --git a/block/blk-map.c b/block/blk-map.c index ab43533ba641..3c942bd6422a 100644 --- a/block/blk-map.c +++ b/block/blk-map.c @@ -141,25 +141,8 @@ int blk_rq_map_user(struct request_queue *q, struct request *rq, ubuf += ret; } - /* - * __blk_rq_map_user() copies the buffers if starting address - * or length isn't aligned to dma_pad_mask. As the copied - * buffer is always page aligned, we know that there's enough - * room for padding. Extend the last bio and update - * rq->data_len accordingly. - * - * On unmap, bio_uncopy_user() will use unmodified - * bio_map_data pointed to by bio->bi_private. - */ - if (len & q->dma_pad_mask) { - unsigned int pad_len = (q->dma_pad_mask & ~len) + 1; - struct bio *tail = rq->biotail; - - tail->bi_io_vec[tail->bi_vcnt - 1].bv_len += pad_len; - tail->bi_size += pad_len; - - rq->extra_len += pad_len; - } + if (!bio_flagged(bio, BIO_USER_MAPPED)) + rq->cmd_flags |= REQ_COPY_USER; rq->buffer = rq->data = NULL; return 0; @@ -224,6 +207,9 @@ int blk_rq_map_user_iov(struct request_queue *q, struct request *rq, return -EINVAL; } + if (!bio_flagged(bio, BIO_USER_MAPPED)) + rq->cmd_flags |= REQ_COPY_USER; + bio_get(bio); blk_rq_bio_prep(q, rq, bio); rq->buffer = rq->data = NULL; diff --git a/block/blk-merge.c b/block/blk-merge.c index 0f58616bcd7f..b5c5c4a9e3f0 100644 --- a/block/blk-merge.c +++ b/block/blk-merge.c @@ -220,6 +220,15 @@ new_segment: bvprv = bvec; } /* segments in rq */ + + if (unlikely(rq->cmd_flags & REQ_COPY_USER) && + (rq->data_len & q->dma_pad_mask)) { + unsigned int pad_len = (q->dma_pad_mask & ~rq->data_len) + 1; + + sg->length += pad_len; + rq->extra_len += pad_len; + } + if (q->dma_drain_size && q->dma_drain_needed(rq)) { if (rq->cmd_flags & REQ_RW) memset(q->dma_drain_buffer, 0, q->dma_drain_size); diff --git a/drivers/scsi/scsi.c b/drivers/scsi/scsi.c index f6980bd9d8f9..12d69d7c8577 100644 --- a/drivers/scsi/scsi.c +++ b/drivers/scsi/scsi.c @@ -852,7 +852,7 @@ void scsi_finish_command(struct scsi_cmnd *cmd) "Notifying upper driver of completion " "(result %x)\n", cmd->result)); - good_bytes = scsi_bufflen(cmd) + cmd->request->extra_len; + good_bytes = scsi_bufflen(cmd); if (cmd->request->cmd_type != REQ_TYPE_BLOCK_PC) { drv = scsi_cmd_to_driver(cmd); if (drv->done) diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index 6f79d40dd3c0..b3a58adc4352 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -112,6 +112,7 @@ enum rq_flag_bits { __REQ_RW_SYNC, /* request is sync (O_DIRECT) */ __REQ_ALLOCED, /* request came from our alloc pool */ __REQ_RW_META, /* metadata io request */ + __REQ_COPY_USER, /* contains copies of user pages */ __REQ_NR_BITS, /* stops here */ }; @@ -133,6 +134,7 @@ enum rq_flag_bits { #define REQ_RW_SYNC (1 << __REQ_RW_SYNC) #define REQ_ALLOCED (1 << __REQ_ALLOCED) #define REQ_RW_META (1 << __REQ_RW_META) +#define REQ_COPY_USER (1 << __REQ_COPY_USER) #define BLK_MAX_CDB 16 -- cgit v1.2.3 From 2472892a3ce17b177cc0d8099a6391949c75abf2 Mon Sep 17 00:00:00 2001 From: Andi Kleen Date: Mon, 21 Apr 2008 09:51:05 +0200 Subject: block: fix memory hotplug and bouncing in block layer Only noticed this while hacking something else, no test case. blk_max_low_pfn is initialized once at bootup by the block layer from max_low_pfn. But max_low_pfn is not necessarily constant over the runtime of the system when you consider memory hotplug. What could happen if that someone adds memory later the block layer wouldn't get updated and then start bouncing memory unnecessarily. Also on 64bit blk_max_low_pfn actually isn't needed because it just disables bouncing essentially and there is no highmem. And nobody can pass pfns > max_low_pfn to the block layer, because those wouldn't have a struct page and I suspect block layer wouldn't be very happy without that. So set BLK_BOUNCE_HIGH to infinity (-1ULL) on 64bit. That avoids the problem of having to update it on memory hotadd. On 32bit I kept the same behaviour because at least on i386 memory hotadd only adds HIGHMEM, never lowmem. BLK_BOUNCE_ANY is always set to infinity on both 32 and 64bit. Signed-off-by: Andi Kleen Cc: Jens Axboe Acked-by: Yasunori Goto Signed-off-by: Andrew Morton Signed-off-by: Jens Axboe --- include/linux/blkdev.h | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) (limited to 'include') diff --git a/include/linux/blkdev.h b/include/linux/blkdev.h index b3a58adc4352..c5065e3d2ca9 100644 --- a/include/linux/blkdev.h +++ b/include/linux/blkdev.h @@ -535,8 +535,13 @@ extern unsigned long blk_max_low_pfn, blk_max_pfn; * BLK_BOUNCE_ANY : don't bounce anything * BLK_BOUNCE_ISA : bounce pages above ISA DMA boundary */ + +#if BITS_PER_LONG == 32 #define BLK_BOUNCE_HIGH ((u64)blk_max_low_pfn << PAGE_SHIFT) -#define BLK_BOUNCE_ANY ((u64)blk_max_pfn << PAGE_SHIFT) +#else +#define BLK_BOUNCE_HIGH -1ULL +#endif +#define BLK_BOUNCE_ANY (-1ULL) #define BLK_BOUNCE_ISA (ISA_DMA_THRESHOLD) /* -- cgit v1.2.3